From 757294d769a7defe6a531a09fba9674cc6b388f7 Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Thu, 27 Oct 2011 19:53:48 -0400 Subject: Import boxbackup_0.11.1~r2837.orig.tar.gz [dgit import orig boxbackup_0.11.1~r2837.orig.tar.gz] --- .hgignore | 37 + .svnrevision | 1 + BUGS.txt | 15 + COPYING.txt | 491 ++ LICENSE-DUAL.txt | 59 + LICENSE-GPL.txt | 41 + VERSION.txt | 2 + bin/bbackupctl/bbackupctl.cpp | 366 ++ bin/bbackupd/BackupClientContext.cpp | 578 ++ bin/bbackupd/BackupClientContext.h | 237 + bin/bbackupd/BackupClientDeleteList.cpp | 229 + bin/bbackupd/BackupClientDeleteList.h | 75 + bin/bbackupd/BackupClientDirectoryRecord.cpp | 1876 ++++++ bin/bbackupd/BackupClientDirectoryRecord.h | 171 + bin/bbackupd/BackupClientInodeToIDMap.cpp | 327 ++ bin/bbackupd/BackupClientInodeToIDMap.h | 73 + bin/bbackupd/BackupDaemon.cpp | 2883 ++++++++++ bin/bbackupd/BackupDaemon.h | 525 ++ bin/bbackupd/BackupDaemonInterface.h | 167 + bin/bbackupd/ClientException.txt | 11 + bin/bbackupd/Makefile.extra | 7 + bin/bbackupd/Win32BackupService.cpp | 48 + bin/bbackupd/Win32BackupService.h | 21 + bin/bbackupd/Win32ServiceFunctions.cpp | 384 ++ bin/bbackupd/Win32ServiceFunctions.h | 19 + bin/bbackupd/bbackupd-config.in | 601 ++ bin/bbackupd/bbackupd.cpp | 56 + bin/bbackupd/win32/NotifySysAdmin.vbs | 113 + bin/bbackupd/win32/bbackupd.conf | 238 + bin/bbackupd/win32/installer.iss | 51 + bin/bbackupobjdump/bbackupobjdump.cpp | 83 + bin/bbackupquery/BackupQueries.cpp | 2340 ++++++++ bin/bbackupquery/BackupQueries.h | 346 ++ bin/bbackupquery/BoxBackupCompareParams.h | 107 + bin/bbackupquery/Makefile.extra | 6 + bin/bbackupquery/bbackupquery.cpp | 441 ++ bin/bbackupquery/documentation.txt | 187 + bin/bbackupquery/makedocumentation.pl.in | 75 + bin/bbstoreaccounts/bbstoreaccounts.cpp | 626 ++ bin/bbstored/BBStoreDHousekeeping.cpp | 240 + bin/bbstored/BackupCommands.cpp | 970 ++++ bin/bbstored/BackupConstants.h | 21 + bin/bbstored/BackupStoreContext.cpp | 1785 ++++++ bin/bbstored/BackupStoreContext.h | 186 + bin/bbstored/BackupStoreDaemon.cpp | 371 ++ bin/bbstored/BackupStoreDaemon.h | 100 + bin/bbstored/HousekeepStoreAccount.cpp | 1067 ++++ bin/bbstored/HousekeepStoreAccount.h | 111 + bin/bbstored/Makefile.extra | 9 + bin/bbstored/backupprotocol.txt | 234 + bin/bbstored/bbstored-certs.in | 319 ++ bin/bbstored/bbstored-config.in | 245 + bin/bbstored/bbstored.cpp | 37 + bin/s3simulator/s3simulator.cpp | 32 + bootstrap | 5 + cleanupforcvs.pl | 196 + config.guess | 1456 +++++ config.sub | 1549 +++++ configure.ac | 420 ++ contrib/bbadmin/accounts.cgi | 580 ++ contrib/bbadmin/apache.conf | 14 + contrib/bbadmin/bb.css | 70 + contrib/bbreporter/LICENSE | 674 +++ contrib/bbreporter/bbreporter.py | 538 ++ contrib/debian/README.txt | 9 + contrib/debian/bbackupd.in | 59 + contrib/debian/bbstored.in | 59 + contrib/mac_osx/org.boxbackup.bbackupd.plist.in | 20 + contrib/mac_osx/org.boxbackup.bbstored.plist.in | 21 + contrib/redhat/README.txt | 7 + contrib/redhat/bbackupd.in | 83 + contrib/redhat/bbstored.in | 83 + contrib/rpm/README.txt | 16 + contrib/rpm/boxbackup.spec | 244 + contrib/solaris/bbackupd-manifest.xml.in | 59 + contrib/solaris/bbackupd-smf-method.in | 24 + contrib/solaris/bbstored-manifest.xml.in | 60 + contrib/solaris/bbstored-smf-method.in | 23 + contrib/suse/README.txt | 5 + contrib/suse/bbackupd.in | 103 + contrib/suse/bbstored.in | 104 + contrib/windows/installer/boxbackup.mpi.in | 3392 +++++++++++ contrib/windows/installer/tools/InstallService.bat | 3 + .../windows/installer/tools/KillBackupProcess.bat | 3 + contrib/windows/installer/tools/QueryOutputAll.bat | 5 + .../windows/installer/tools/QueryOutputCurrent.bat | 5 + contrib/windows/installer/tools/ReloadConfig.bat | 3 + contrib/windows/installer/tools/RemoteControl.exe | Bin 0 -> 156536 bytes contrib/windows/installer/tools/RemoveService.bat | 3 + contrib/windows/installer/tools/RestartService.bat | 5 + contrib/windows/installer/tools/ShowUsage.bat | 3 + contrib/windows/installer/tools/StartService.bat | 3 + contrib/windows/installer/tools/StopService.bat | 3 + contrib/windows/installer/tools/Sync.bat | 3 + distribution/COMMON-MANIFEST.txt | 47 + distribution/boxbackup/CONTACT.txt | 6 + distribution/boxbackup/DISTRIBUTION-MANIFEST.txt | 95 + distribution/boxbackup/DOCUMENTATION.txt | 6 + distribution/boxbackup/LINUX.txt | 29 + distribution/boxbackup/NETBSD.txt | 8 + distribution/boxbackup/THANKS.txt | 69 + distribution/boxbackup/VERSION.txt | 2 + docs/Makefile | 183 + docs/api-notes/INDEX.txt | 61 + docs/api-notes/Win32_Clients.txt | 13 + docs/api-notes/backup_encryption.txt | 109 + docs/api-notes/bin_bbackupd.txt | 88 + docs/api-notes/bin_bbstored.txt | 54 + docs/api-notes/common/lib_common.txt | 52 + docs/api-notes/common/lib_common/BoxTime.txt | 7 + .../common/lib_common/CollectInBufferStream.txt | 26 + docs/api-notes/common/lib_common/Configuration.txt | 102 + docs/api-notes/common/lib_common/Conversion.txt | 14 + docs/api-notes/common/lib_common/ExcludeList.txt | 21 + docs/api-notes/common/lib_common/FdGetLine.txt | 11 + docs/api-notes/common/lib_common/Guards.txt | 5 + docs/api-notes/common/lib_common/IOStream.txt | 89 + .../common/lib_common/IOStreamGetLine.txt | 29 + docs/api-notes/common/lib_common/MainHelper.txt | 4 + docs/api-notes/common/lib_common/WaitForEvent.txt | 16 + docs/api-notes/common/lib_common/xStream.txt | 40 + docs/api-notes/common/lib_compress.txt | 8 + .../common/lib_compress/CompressStream.txt | 27 + docs/api-notes/common/lib_crypto.txt | 28 + docs/api-notes/common/lib_crypto/CipherContext.txt | 28 + .../common/lib_crypto/RollingChecksum.txt | 36 + docs/api-notes/common/lib_server.txt | 9 + docs/api-notes/common/lib_server/Daemon.txt | 96 + docs/api-notes/common/lib_server/Protocol.txt | 120 + docs/api-notes/common/lib_server/ServerStream.txt | 29 + docs/api-notes/common/lib_server/ServerTLS.txt | 6 + docs/api-notes/common/lib_server/SocketStream.txt | 8 + .../common/lib_server/SocketStreamTLS.txt | 11 + docs/api-notes/common/lib_server/TLSContext.txt | 16 + docs/api-notes/common/memory_leaks.txt | 44 + docs/api-notes/encrypt_rsync.txt | 66 + docs/api-notes/lib_backupclient.txt | 46 + docs/api-notes/lib_backupstore.txt | 30 + docs/api-notes/raidfile/RaidFileRead.txt | 14 + docs/api-notes/raidfile/RaidFileWrite.txt | 36 + docs/api-notes/raidfile/lib_raidfile.txt | 73 + .../win32_build_on_cygwin_using_mingw.txt | 113 + .../api-notes/win32_build_on_linux_using_mingw.txt | 108 + docs/api-notes/windows_porting.txt | 100 + docs/docbook/adminguide.xml | 1981 +++++++ docs/docbook/bb-book.xsl | 17 + docs/docbook/bb-man.xsl | 9 + docs/docbook/bb-nochunk-book.xsl | 17 + docs/docbook/bbackupctl.xml | 205 + docs/docbook/bbackupd-config.xml | 153 + docs/docbook/bbackupd.conf.xml | 479 ++ docs/docbook/bbackupd.xml | 209 + docs/docbook/bbackupquery.xml | 506 ++ docs/docbook/bbstoreaccounts.xml | 386 ++ docs/docbook/bbstored-certs.xml | 180 + docs/docbook/bbstored-config.xml | 148 + docs/docbook/bbstored.conf.xml | 211 + docs/docbook/bbstored.xml | 90 + docs/docbook/html/bbdoc-man.css | 104 + docs/docbook/html/bbdoc.css | 112 + docs/docbook/html/favicon.ico | Bin 0 -> 67646 bytes docs/docbook/html/images/arrow.png | Bin 0 -> 197 bytes docs/docbook/html/images/bblogo.png | Bin 0 -> 5882 bytes docs/docbook/html/images/stepahead.png | Bin 0 -> 298 bytes docs/docbook/instguide.xml | 766 +++ docs/docbook/raidfile-config.xml | 198 + docs/docbook/raidfile.conf.xml | 143 + docs/images/bblogo-alpha.xcf | Bin 0 -> 93980 bytes docs/tools/generate_except_xml.pl | 74 + docs/xsl-generic/VERSION | 113 + docs/xsl-generic/common/af.xml | 1223 ++++ docs/xsl-generic/common/am.xml | 1223 ++++ docs/xsl-generic/common/ar.xml | 1223 ++++ docs/xsl-generic/common/autoidx-kimber.xsl | 43 + docs/xsl-generic/common/autoidx-kosek.xsl | 150 + docs/xsl-generic/common/az.xml | 666 +++ docs/xsl-generic/common/bg.xml | 718 +++ docs/xsl-generic/common/bn.xml | 1223 ++++ docs/xsl-generic/common/bs.xml | 656 +++ docs/xsl-generic/common/ca.xml | 1223 ++++ docs/xsl-generic/common/charmap.xml | 185 + docs/xsl-generic/common/charmap.xsl | 221 + docs/xsl-generic/common/common.xml | 622 ++ docs/xsl-generic/common/common.xsl | 1981 +++++++ docs/xsl-generic/common/cs.xml | 694 +++ docs/xsl-generic/common/cy.xml | 1239 ++++ docs/xsl-generic/common/da.xml | 658 +++ docs/xsl-generic/common/de.xml | 660 +++ docs/xsl-generic/common/el.xml | 1223 ++++ docs/xsl-generic/common/en.xml | 1223 ++++ docs/xsl-generic/common/entities.ent | 47 + docs/xsl-generic/common/eo.xml | 1223 ++++ docs/xsl-generic/common/es.xml | 670 +++ docs/xsl-generic/common/et.xml | 1223 ++++ docs/xsl-generic/common/eu.xml | 1223 ++++ docs/xsl-generic/common/fa.xml | 1223 ++++ docs/xsl-generic/common/fi.xml | 664 +++ docs/xsl-generic/common/fr.xml | 684 +++ docs/xsl-generic/common/ga.xml | 1223 ++++ docs/xsl-generic/common/gentext.xsl | 831 +++ docs/xsl-generic/common/gu.xml | 1223 ++++ docs/xsl-generic/common/he.xml | 1223 ++++ docs/xsl-generic/common/hi.xml | 1223 ++++ docs/xsl-generic/common/hr.xml | 1223 ++++ docs/xsl-generic/common/hu.xml | 1223 ++++ docs/xsl-generic/common/id.xml | 1223 ++++ docs/xsl-generic/common/insertfile.xsl | 111 + docs/xsl-generic/common/it.xml | 1223 ++++ docs/xsl-generic/common/ja.xml | 1223 ++++ docs/xsl-generic/common/kn.xml | 1223 ++++ docs/xsl-generic/common/ko.xml | 1223 ++++ docs/xsl-generic/common/l10n.dtd | 63 + docs/xsl-generic/common/l10n.xml | 127 + docs/xsl-generic/common/l10n.xsl | 441 ++ docs/xsl-generic/common/la.xml | 1223 ++++ docs/xsl-generic/common/labels.xsl | 869 +++ docs/xsl-generic/common/lt.xml | 672 +++ docs/xsl-generic/common/lv.xml | 1223 ++++ docs/xsl-generic/common/mn.xml | 724 +++ docs/xsl-generic/common/nl.xml | 1223 ++++ docs/xsl-generic/common/nn.xml | 1223 ++++ docs/xsl-generic/common/no.xml | 1223 ++++ docs/xsl-generic/common/olink.xsl | 1149 ++++ docs/xsl-generic/common/or.xml | 1223 ++++ docs/xsl-generic/common/pa.xml | 1223 ++++ docs/xsl-generic/common/pi.xsl | 346 ++ docs/xsl-generic/common/pl.xml | 1223 ++++ docs/xsl-generic/common/pt.xml | 1223 ++++ docs/xsl-generic/common/pt_br.xml | 1223 ++++ docs/xsl-generic/common/refentry.xml | 781 +++ docs/xsl-generic/common/refentry.xsl | 1277 +++++ docs/xsl-generic/common/ro.xml | 1223 ++++ docs/xsl-generic/common/ru.xml | 720 +++ docs/xsl-generic/common/sk.xml | 1223 ++++ docs/xsl-generic/common/sl.xml | 1223 ++++ docs/xsl-generic/common/sq.xml | 1223 ++++ docs/xsl-generic/common/sr.xml | 714 +++ docs/xsl-generic/common/sr_Latn.xml | 673 +++ docs/xsl-generic/common/stripns.xsl | 342 ++ docs/xsl-generic/common/subtitles.xsl | 155 + docs/xsl-generic/common/sv.xml | 658 +++ docs/xsl-generic/common/ta.xml | 1223 ++++ docs/xsl-generic/common/table.xsl | 514 ++ docs/xsl-generic/common/targetdatabase.dtd | 49 + docs/xsl-generic/common/targets.xsl | 272 + docs/xsl-generic/common/th.xml | 1223 ++++ docs/xsl-generic/common/titles.xsl | 740 +++ docs/xsl-generic/common/tl.xml | 1223 ++++ docs/xsl-generic/common/tr.xml | 660 +++ docs/xsl-generic/common/uk.xml | 1223 ++++ docs/xsl-generic/common/utility.xml | 259 + docs/xsl-generic/common/utility.xsl | 290 + docs/xsl-generic/common/vi.xml | 1223 ++++ docs/xsl-generic/common/xh.xml | 1223 ++++ docs/xsl-generic/common/zh_cn.xml | 654 +++ docs/xsl-generic/common/zh_tw.xml | 1223 ++++ docs/xsl-generic/highlighting/c-hl.xml | 105 + docs/xsl-generic/highlighting/common.xsl | 62 + docs/xsl-generic/highlighting/delphi-hl.xml | 174 + docs/xsl-generic/highlighting/ini-hl.xml | 43 + docs/xsl-generic/highlighting/java-hl.xml | 98 + docs/xsl-generic/highlighting/m2-hl.xml | 86 + docs/xsl-generic/highlighting/myxml-hl.xml | 131 + docs/xsl-generic/highlighting/php-hl.xml | 127 + docs/xsl-generic/highlighting/xslthl-config.xml | 11 + docs/xsl-generic/html/admon.xsl | 132 + docs/xsl-generic/html/annotations.xsl | 169 + docs/xsl-generic/html/autoidx-kimber.xsl | 168 + docs/xsl-generic/html/autoidx-kosek.xsl | 125 + docs/xsl-generic/html/autoidx-ng.xsl | 20 + docs/xsl-generic/html/autoidx.xsl | 645 +++ docs/xsl-generic/html/autotoc.xsl | 675 +++ docs/xsl-generic/html/biblio-iso690.xsl | 1300 +++++ docs/xsl-generic/html/biblio.xsl | 1228 ++++ docs/xsl-generic/html/block.xsl | 434 ++ docs/xsl-generic/html/callout.xsl | 201 + docs/xsl-generic/html/changebars.xsl | 100 + docs/xsl-generic/html/chunk-code.xsl | 670 +++ docs/xsl-generic/html/chunk-common.xsl | 1886 ++++++ docs/xsl-generic/html/chunk.xsl | 52 + docs/xsl-generic/html/chunker.xsl | 439 ++ docs/xsl-generic/html/chunkfast.xsl | 72 + docs/xsl-generic/html/chunktoc.xsl | 468 ++ docs/xsl-generic/html/component.xsl | 401 ++ docs/xsl-generic/html/division.xsl | 228 + docs/xsl-generic/html/docbook.xsl | 479 ++ docs/xsl-generic/html/ebnf.xsl | 329 ++ docs/xsl-generic/html/footnote.xsl | 299 + docs/xsl-generic/html/formal.xsl | 400 ++ docs/xsl-generic/html/glossary.xsl | 482 ++ docs/xsl-generic/html/graphics.xsl | 1489 +++++ docs/xsl-generic/html/highlight.xsl | 54 + docs/xsl-generic/html/html-rtf.xsl | 336 ++ docs/xsl-generic/html/html.xsl | 241 + docs/xsl-generic/html/htmltbl.xsl | 55 + docs/xsl-generic/html/index.xsl | 229 + docs/xsl-generic/html/info.xsl | 43 + docs/xsl-generic/html/inline.xsl | 1439 +++++ docs/xsl-generic/html/keywords.xsl | 35 + docs/xsl-generic/html/lists.xsl | 1103 ++++ docs/xsl-generic/html/maketoc.xsl | 86 + docs/xsl-generic/html/manifest.xsl | 22 + docs/xsl-generic/html/math.xsl | 270 + docs/xsl-generic/html/oldchunker.xsl | 202 + docs/xsl-generic/html/onechunk.xsl | 37 + docs/xsl-generic/html/param.xsl | 412 ++ docs/xsl-generic/html/pi.xsl | 1240 ++++ docs/xsl-generic/html/profile-chunk-code.xsl | 609 ++ docs/xsl-generic/html/profile-chunk.xsl | 52 + docs/xsl-generic/html/profile-docbook.xsl | 411 ++ docs/xsl-generic/html/profile-onechunk.xsl | 37 + docs/xsl-generic/html/qandaset.xsl | 389 ++ docs/xsl-generic/html/refentry.xsl | 309 + docs/xsl-generic/html/sections.xsl | 622 ++ docs/xsl-generic/html/synop.xsl | 1596 ++++++ docs/xsl-generic/html/table.xsl | 1120 ++++ docs/xsl-generic/html/task.xsl | 76 + docs/xsl-generic/html/titlepage.templates.xml | 662 +++ docs/xsl-generic/html/titlepage.templates.xsl | 3622 ++++++++++++ docs/xsl-generic/html/titlepage.xsl | 1031 ++++ docs/xsl-generic/html/toc.xsl | 173 + docs/xsl-generic/html/verbatim.xsl | 376 ++ docs/xsl-generic/html/xref.xsl | 1348 +++++ docs/xsl-generic/lib/lib.xsl | 480 ++ docs/xsl-generic/manpages/block.xsl | 296 + docs/xsl-generic/manpages/charmap.groff.xsl | 5985 ++++++++++++++++++++ docs/xsl-generic/manpages/docbook.xsl | 293 + docs/xsl-generic/manpages/endnotes.xsl | 535 ++ docs/xsl-generic/manpages/html-synop.xsl | 1605 ++++++ docs/xsl-generic/manpages/info.xsl | 630 +++ docs/xsl-generic/manpages/inline.xsl | 190 + docs/xsl-generic/manpages/lists.xsl | 368 ++ docs/xsl-generic/manpages/other.xsl | 674 +++ docs/xsl-generic/manpages/param.xsl | 167 + docs/xsl-generic/manpages/profile-docbook.xsl | 259 + docs/xsl-generic/manpages/refentry.xsl | 256 + docs/xsl-generic/manpages/synop.xsl | 305 + docs/xsl-generic/manpages/table.xsl | 633 +++ docs/xsl-generic/manpages/utility.xsl | 452 ++ infrastructure/BoxPlatform.pm.in | 132 + infrastructure/buildenv-testmain-template.cpp | 390 ++ infrastructure/m4/ac_cxx_exceptions.m4 | 24 + infrastructure/m4/ac_cxx_namespaces.m4 | 25 + infrastructure/m4/ax_bswap64.m4 | 52 + infrastructure/m4/ax_check_bdb_v1.m4 | 43 + infrastructure/m4/ax_check_define_pragma.m4 | 25 + infrastructure/m4/ax_check_dirent_d_type.m4 | 45 + infrastructure/m4/ax_check_llong_minmax.m4 | 76 + infrastructure/m4/ax_check_malloc_workaround.m4 | 38 + infrastructure/m4/ax_check_mount_point.m4 | 60 + infrastructure/m4/ax_check_nonaligned_access.m4 | 57 + infrastructure/m4/ax_check_ssl.m4 | 37 + infrastructure/m4/ax_check_syscall_lseek.m4 | 69 + infrastructure/m4/ax_compare_version.m4 | 162 + infrastructure/m4/ax_config_scripts.m4 | 16 + infrastructure/m4/ax_func_syscall.m4 | 50 + infrastructure/m4/ax_path_bdb.m4 | 615 ++ infrastructure/m4/ax_random_device.m4 | 31 + infrastructure/m4/ax_split_version.m4 | 19 + infrastructure/m4/vl_lib_readline.m4 | 135 + infrastructure/makebuildenv.pl.in | 973 ++++ infrastructure/makedistribution.pl.in | 363 ++ infrastructure/makeparcels.pl.in | 396 ++ infrastructure/mingw/configure.sh | 38 + infrastructure/msvc/2003/bbackupctl.vcproj | 159 + infrastructure/msvc/2003/bbackupd.vcproj | 219 + infrastructure/msvc/2003/boxbackup.sln | 57 + infrastructure/msvc/2003/boxquery.vcproj | 174 + infrastructure/msvc/2003/common.vcproj | 672 +++ infrastructure/msvc/2003/win32test.vcproj | 148 + infrastructure/msvc/2005/bbackupctl.vcproj | 222 + infrastructure/msvc/2005/bbackupd.vcproj | 299 + infrastructure/msvc/2005/boxbackup.sln | 55 + infrastructure/msvc/2005/boxbackup.suo | Bin 0 -> 58880 bytes infrastructure/msvc/2005/boxquery.vcproj | 246 + infrastructure/msvc/2005/common.vcproj | 896 +++ infrastructure/msvc/2005/win32test.vcproj | 218 + infrastructure/msvc/getversion.pl | 19 + infrastructure/parcelpath.pl | 17 + infrastructure/printversion.pl | 12 + infrastructure/setupexternal.pl | 55 + lib/backupclient/BackupClientCryptoKeys.cpp | 85 + lib/backupclient/BackupClientCryptoKeys.h | 55 + lib/backupclient/BackupClientFileAttributes.cpp | 1186 ++++ lib/backupclient/BackupClientFileAttributes.h | 78 + lib/backupclient/BackupClientMakeExcludeList.cpp | 75 + lib/backupclient/BackupClientMakeExcludeList.h | 48 + lib/backupclient/BackupClientRestore.cpp | 918 +++ lib/backupclient/BackupClientRestore.h | 36 + lib/backupclient/BackupDaemonConfigVerify.cpp | 132 + lib/backupclient/BackupDaemonConfigVerify.h | 18 + lib/backupclient/BackupStoreConstants.h | 44 + lib/backupclient/BackupStoreDirectory.cpp | 568 ++ lib/backupclient/BackupStoreDirectory.h | 268 + lib/backupclient/BackupStoreException.h | 17 + lib/backupclient/BackupStoreException.txt | 71 + lib/backupclient/BackupStoreFile.cpp | 1556 +++++ lib/backupclient/BackupStoreFile.h | 228 + lib/backupclient/BackupStoreFileCmbDiff.cpp | 326 ++ lib/backupclient/BackupStoreFileCmbIdx.cpp | 324 ++ lib/backupclient/BackupStoreFileCombine.cpp | 410 ++ lib/backupclient/BackupStoreFileCryptVar.cpp | 31 + lib/backupclient/BackupStoreFileCryptVar.h | 39 + lib/backupclient/BackupStoreFileDiff.cpp | 1046 ++++ lib/backupclient/BackupStoreFileEncodeStream.cpp | 715 +++ lib/backupclient/BackupStoreFileEncodeStream.h | 135 + lib/backupclient/BackupStoreFileRevDiff.cpp | 258 + lib/backupclient/BackupStoreFileWire.h | 74 + lib/backupclient/BackupStoreFilename.cpp | 281 + lib/backupclient/BackupStoreFilename.h | 107 + lib/backupclient/BackupStoreFilenameClear.cpp | 335 ++ lib/backupclient/BackupStoreFilenameClear.h | 60 + lib/backupclient/BackupStoreObjectDump.cpp | 227 + lib/backupclient/BackupStoreObjectMagic.h | 31 + lib/backupclient/Makefile.extra | 16 + lib/backupclient/RunStatusProvider.h | 29 + lib/backupstore/BackupStoreAccountDatabase.cpp | 373 ++ lib/backupstore/BackupStoreAccountDatabase.h | 75 + lib/backupstore/BackupStoreAccounts.cpp | 170 + lib/backupstore/BackupStoreAccounts.h | 52 + lib/backupstore/BackupStoreCheck.cpp | 776 +++ lib/backupstore/BackupStoreCheck.h | 199 + lib/backupstore/BackupStoreCheck2.cpp | 916 +++ lib/backupstore/BackupStoreCheckData.cpp | 208 + lib/backupstore/BackupStoreConfigVerify.cpp | 57 + lib/backupstore/BackupStoreConfigVerify.h | 18 + lib/backupstore/BackupStoreInfo.cpp | 593 ++ lib/backupstore/BackupStoreInfo.h | 111 + lib/backupstore/BackupStoreRefCountDatabase.cpp | 321 ++ lib/backupstore/BackupStoreRefCountDatabase.h | 128 + lib/backupstore/StoreStructure.cpp | 95 + lib/backupstore/StoreStructure.h | 32 + lib/common/Archive.h | 161 + lib/common/BannerText.h | 18 + lib/common/BeginStructPackForWire.h | 23 + lib/common/Box.h | 185 + lib/common/BoxConfig-MSVC.h | 402 ++ lib/common/BoxException.cpp | 21 + lib/common/BoxException.h | 38 + lib/common/BoxPlatform.h | 201 + lib/common/BoxPortsAndFiles.h.in | 44 + lib/common/BoxTime.cpp | 96 + lib/common/BoxTime.h | 46 + lib/common/BoxTimeToText.cpp | 76 + lib/common/BoxTimeToText.h | 19 + lib/common/BoxTimeToUnix.h | 34 + lib/common/BufferedStream.cpp | 207 + lib/common/BufferedStream.h | 43 + lib/common/BufferedWriteStream.cpp | 181 + lib/common/BufferedWriteStream.h | 44 + lib/common/CollectInBufferStream.cpp | 274 + lib/common/CollectInBufferStream.h | 60 + lib/common/CommonException.h | 17 + lib/common/CommonException.txt | 47 + lib/common/Configuration.cpp | 920 +++ lib/common/Configuration.h | 147 + lib/common/Conversion.h | 98 + lib/common/ConversionException.txt | 8 + lib/common/ConversionString.cpp | 129 + lib/common/DebugAssertFailed.cpp | 37 + lib/common/DebugMemLeakFinder.cpp | 552 ++ lib/common/DebugPrintf.cpp | 83 + lib/common/EndStructPackForWire.h | 23 + lib/common/EventWatchFilesystemObject.cpp | 112 + lib/common/EventWatchFilesystemObject.h | 48 + lib/common/ExcludeList.cpp | 481 ++ lib/common/ExcludeList.h | 76 + lib/common/FdGetLine.cpp | 228 + lib/common/FdGetLine.h | 65 + lib/common/FileModificationTime.cpp | 64 + lib/common/FileModificationTime.h | 22 + lib/common/FileStream.cpp | 447 ++ lib/common/FileStream.h | 66 + lib/common/Guards.h | 121 + lib/common/IOStream.cpp | 251 + lib/common/IOStream.h | 73 + lib/common/IOStreamGetLine.cpp | 227 + lib/common/IOStreamGetLine.h | 71 + lib/common/InvisibleTempFileStream.cpp | 39 + lib/common/InvisibleTempFileStream.h | 35 + lib/common/Logging.cpp | 518 ++ lib/common/Logging.h | 346 ++ lib/common/MainHelper.h | 43 + lib/common/Makefile.extra | 11 + lib/common/MemBlockStream.cpp | 235 + lib/common/MemBlockStream.h | 52 + lib/common/MemLeakFindOff.h | 27 + lib/common/MemLeakFindOn.h | 25 + lib/common/MemLeakFinder.h | 63 + lib/common/NamedLock.cpp | 170 + lib/common/NamedLock.h | 41 + lib/common/PartialReadStream.cpp | 138 + lib/common/PartialReadStream.h | 46 + lib/common/PathUtils.cpp | 34 + lib/common/PathUtils.h | 26 + lib/common/ReadGatherStream.cpp | 263 + lib/common/ReadGatherStream.h | 67 + lib/common/ReadLoggingStream.cpp | 203 + lib/common/ReadLoggingStream.h | 58 + lib/common/SelfFlushingStream.h | 71 + lib/common/StreamableMemBlock.cpp | 364 ++ lib/common/StreamableMemBlock.h | 71 + lib/common/TemporaryDirectory.h | 46 + lib/common/Test.cpp | 486 ++ lib/common/Test.h | 167 + lib/common/Timer.cpp | 626 ++ lib/common/Timer.h | 89 + lib/common/UnixUser.cpp | 123 + lib/common/UnixUser.h | 37 + lib/common/Utils.cpp | 315 ++ lib/common/Utils.h | 44 + lib/common/WaitForEvent.cpp | 197 + lib/common/WaitForEvent.h | 152 + lib/common/ZeroStream.cpp | 170 + lib/common/ZeroStream.h | 39 + lib/common/makeexception.pl.in | 283 + lib/compress/Compress.h | 197 + lib/compress/CompressException.h | 17 + lib/compress/CompressException.txt | 12 + lib/compress/CompressStream.cpp | 425 ++ lib/compress/CompressStream.h | 62 + lib/compress/Makefile.extra | 7 + lib/crypto/CipherAES.cpp | 163 + lib/crypto/CipherAES.h | 50 + lib/crypto/CipherBlowfish.cpp | 220 + lib/crypto/CipherBlowfish.h | 60 + lib/crypto/CipherContext.cpp | 620 ++ lib/crypto/CipherContext.h | 83 + lib/crypto/CipherDescription.cpp | 73 + lib/crypto/CipherDescription.h | 59 + lib/crypto/CipherException.h | 17 + lib/crypto/CipherException.txt | 18 + lib/crypto/MD5Digest.cpp | 82 + lib/crypto/MD5Digest.h | 57 + lib/crypto/Makefile.extra | 7 + lib/crypto/Random.cpp | 128 + lib/crypto/Random.h | 25 + lib/crypto/RollingChecksum.cpp | 62 + lib/crypto/RollingChecksum.h | 107 + lib/httpserver/HTTPException.txt | 16 + lib/httpserver/HTTPQueryDecoder.cpp | 159 + lib/httpserver/HTTPQueryDecoder.h | 47 + lib/httpserver/HTTPRequest.cpp | 780 +++ lib/httpserver/HTTPRequest.h | 189 + lib/httpserver/HTTPResponse.cpp | 648 +++ lib/httpserver/HTTPResponse.h | 175 + lib/httpserver/HTTPServer.cpp | 247 + lib/httpserver/HTTPServer.h | 81 + lib/httpserver/Makefile.extra | 7 + lib/httpserver/S3Client.cpp | 243 + lib/httpserver/S3Client.h | 72 + lib/httpserver/S3Simulator.cpp | 309 + lib/httpserver/S3Simulator.h | 40 + lib/httpserver/cdecode.cpp | 92 + lib/httpserver/cdecode.h | 28 + lib/httpserver/cencode.cpp | 113 + lib/httpserver/cencode.h | 32 + lib/httpserver/decode.h | 77 + lib/httpserver/encode.h | 87 + lib/intercept/intercept.cpp | 673 +++ lib/intercept/intercept.h | 54 + lib/raidfile/Makefile.extra | 7 + lib/raidfile/RaidFileController.cpp | 227 + lib/raidfile/RaidFileController.h | 108 + lib/raidfile/RaidFileException.h | 17 + lib/raidfile/RaidFileException.txt | 28 + lib/raidfile/RaidFileRead.cpp | 1724 ++++++ lib/raidfile/RaidFileRead.h | 73 + lib/raidfile/RaidFileUtil.cpp | 210 + lib/raidfile/RaidFileUtil.h | 97 + lib/raidfile/RaidFileWrite.cpp | 930 +++ lib/raidfile/RaidFileWrite.h | 68 + lib/raidfile/raidfile-config.in | 100 + lib/server/ConnectionException.txt | 27 + lib/server/Daemon.cpp | 1024 ++++ lib/server/Daemon.h | 112 + lib/server/LocalProcessStream.cpp | 180 + lib/server/LocalProcessStream.h | 20 + lib/server/Makefile.extra | 11 + lib/server/OverlappedIO.h | 42 + lib/server/Protocol.cpp | 1160 ++++ lib/server/Protocol.h | 201 + lib/server/ProtocolObject.cpp | 125 + lib/server/ProtocolObject.h | 41 + lib/server/ProtocolUncertainStream.cpp | 206 + lib/server/ProtocolUncertainStream.h | 47 + lib/server/ProtocolWire.h | 43 + lib/server/SSLLib.cpp | 111 + lib/server/SSLLib.h | 36 + lib/server/ServerControl.cpp | 228 + lib/server/ServerControl.h | 18 + lib/server/ServerException.h | 46 + lib/server/ServerException.txt | 39 + lib/server/ServerStream.h | 418 ++ lib/server/ServerTLS.h | 80 + lib/server/Socket.cpp | 184 + lib/server/Socket.h | 56 + lib/server/SocketListen.h | 312 + lib/server/SocketStream.cpp | 514 ++ lib/server/SocketStream.h | 75 + lib/server/SocketStreamTLS.cpp | 492 ++ lib/server/SocketStreamTLS.h | 61 + lib/server/TLSContext.cpp | 131 + lib/server/TLSContext.h | 41 + lib/server/WinNamedPipeListener.h | 232 + lib/server/WinNamedPipeStream.cpp | 620 ++ lib/server/WinNamedPipeStream.h | 67 + lib/server/makeprotocol.pl.in | 1093 ++++ lib/win32/MSG00001.bin | Bin 0 -> 32 bytes lib/win32/emu.cpp | 1843 ++++++ lib/win32/emu.h | 424 ++ lib/win32/getopt.h | 98 + lib/win32/getopt_long.cpp | 550 ++ lib/win32/messages.h | 57 + lib/win32/messages.mc | 22 + lib/win32/messages.rc | 2 + modules.txt | 50 + parcels.txt | 86 + runtest.pl.in | 144 + test/backupdiff/difftestfiles.cpp | 295 + test/backupdiff/testbackupdiff.cpp | 605 ++ test/backupdiff/testextra | 2 + test/backupstore/Makefile.extra | 1 + test/backupstore/testbackupstore.cpp | 2178 +++++++ test/backupstore/testextra | 4 + test/backupstore/testfiles/accounts.txt | 0 test/backupstore/testfiles/bbackupd.keys | Bin 0 -> 1024 bytes test/backupstore/testfiles/bbstored.conf | 17 + test/backupstore/testfiles/bbstored_multi.conf | 16 + test/backupstore/testfiles/clientCerts.pem | 11 + test/backupstore/testfiles/clientPrivKey.pem | 15 + test/backupstore/testfiles/clientReq.pem | 10 + test/backupstore/testfiles/clientTrustedCAs.pem | 11 + test/backupstore/testfiles/query.conf | 34 + test/backupstore/testfiles/raidfile.conf | 10 + test/backupstore/testfiles/root.pem | 26 + test/backupstore/testfiles/root.srl | 1 + test/backupstore/testfiles/rootcert.pem | 11 + test/backupstore/testfiles/rootkey.pem | 15 + test/backupstore/testfiles/rootreq.pem | 10 + test/backupstore/testfiles/serverCerts.pem | 11 + test/backupstore/testfiles/serverPrivKey.pem | 15 + test/backupstore/testfiles/serverReq.pem | 10 + test/backupstore/testfiles/serverTrustedCAs.pem | 11 + test/backupstorefix/testbackupstorefix.cpp | 614 ++ test/backupstorefix/testextra | 5 + .../testfiles/testbackupstorefix.pl.in | 221 + test/backupstorepatch/testbackupstorepatch.cpp | 670 +++ test/backupstorepatch/testextra | 6 + test/basicserver/Makefile.extra | 21 + test/basicserver/TestCommands.cpp | 101 + test/basicserver/TestContext.cpp | 16 + test/basicserver/TestContext.h | 7 + test/basicserver/testbasicserver.cpp | 772 +++ test/basicserver/testfiles/clientCerts.pem | 14 + test/basicserver/testfiles/clientPrivKey.pem | 15 + test/basicserver/testfiles/clientReq.pem | 11 + test/basicserver/testfiles/clientTrustedCAs.pem | 14 + test/basicserver/testfiles/key-creation.txt | 83 + test/basicserver/testfiles/root.pem | 29 + test/basicserver/testfiles/root.srl | 1 + test/basicserver/testfiles/rootcert.pem | 14 + test/basicserver/testfiles/rootkey.pem | 15 + test/basicserver/testfiles/rootreq.pem | 11 + test/basicserver/testfiles/serverCerts.pem | 14 + test/basicserver/testfiles/serverPrivKey.pem | 15 + test/basicserver/testfiles/serverReq.pem | 11 + test/basicserver/testfiles/serverTrustedCAs.pem | 14 + test/basicserver/testfiles/srv1.conf | 6 + test/basicserver/testfiles/srv1b.conf | 6 + test/basicserver/testfiles/srv2.conf | 6 + test/basicserver/testfiles/srv3.conf | 9 + test/basicserver/testfiles/srv4.conf | 6 + test/basicserver/testprotocol.txt | 42 + test/bbackupd/Makefile.extra | 14 + test/bbackupd/testbbackupd.cpp | 4077 +++++++++++++ test/bbackupd/testextra | 4 + test/bbackupd/testfiles/accounts.txt | 0 test/bbackupd/testfiles/bbackupd-exclude.conf.in | 47 + test/bbackupd/testfiles/bbackupd-snapshot.conf.in | 56 + test/bbackupd/testfiles/bbackupd-symlink.conf.in | 55 + test/bbackupd/testfiles/bbackupd-temploc.conf | 55 + test/bbackupd/testfiles/bbackupd.conf.in | 55 + test/bbackupd/testfiles/bbackupd.keys | Bin 0 -> 1024 bytes test/bbackupd/testfiles/bbstored.conf | 17 + test/bbackupd/testfiles/clientCerts.pem | 11 + test/bbackupd/testfiles/clientPrivKey.pem | 15 + test/bbackupd/testfiles/clientTrustedCAs.pem | 11 + test/bbackupd/testfiles/extcheck1.pl.in | 58 + test/bbackupd/testfiles/extcheck2.pl.in | 50 + test/bbackupd/testfiles/notifyscript.pl.in | 24 + test/bbackupd/testfiles/raidfile.conf | 10 + test/bbackupd/testfiles/serverCerts.pem | 11 + test/bbackupd/testfiles/serverPrivKey.pem | 15 + test/bbackupd/testfiles/serverTrustedCAs.pem | 11 + test/bbackupd/testfiles/spacetest1.tgz | Bin 0 -> 288 bytes test/bbackupd/testfiles/spacetest2.tgz | Bin 0 -> 203 bytes test/bbackupd/testfiles/syncallowscript.pl.in | 33 + test/bbackupd/testfiles/test2.tgz | Bin 0 -> 25190 bytes test/bbackupd/testfiles/test3.tgz | Bin 0 -> 44957 bytes test/bbackupd/testfiles/test_base.tgz | Bin 0 -> 14950 bytes test/bbackupd/testfiles/testexclude.tgz | Bin 0 -> 377 bytes test/common/testcommon.cpp | 882 +++ test/common/testfiles/config1.txt | 40 + test/common/testfiles/config10.txt | 37 + test/common/testfiles/config11.txt | 39 + test/common/testfiles/config12.txt | 33 + test/common/testfiles/config13.txt | 15 + test/common/testfiles/config14.txt | 41 + test/common/testfiles/config15.txt | 45 + test/common/testfiles/config16.txt | 42 + test/common/testfiles/config2.txt | 39 + test/common/testfiles/config3.txt | 39 + test/common/testfiles/config4.txt | 40 + test/common/testfiles/config5.txt | 37 + test/common/testfiles/config6.txt | 39 + test/common/testfiles/config7.txt | 39 + test/common/testfiles/config8.txt | 37 + test/common/testfiles/config9.txt | 38 + test/common/testfiles/config9b.txt | 38 + test/common/testfiles/config9c.txt | 38 + test/common/testfiles/config9d.txt | 38 + test/common/testfiles/fdgetlinetest.txt | 20 + test/compress/testcompress.cpp | 261 + test/crypto/testcrypto.cpp | 314 + test/httpserver/testfiles/httpserver.conf | 8 + test/httpserver/testfiles/photos/puppy.jpg | 1 + test/httpserver/testfiles/s3simulator.conf | 10 + test/httpserver/testfiles/testrequests.pl | 143 + test/httpserver/testhttpserver.cpp | 480 ++ test/raidfile/testextra | 7 + test/raidfile/testfiles/raidfile.conf | 30 + test/raidfile/testraidfile.cpp | 981 ++++ test/win32/Makefile | 5 + test/win32/testlibwin32.cpp | 345 ++ test/win32/timezone.cpp | 87 + win32.bat | 33 + 737 files changed, 230534 insertions(+) create mode 100644 .hgignore create mode 100644 .svnrevision create mode 100644 BUGS.txt create mode 100644 COPYING.txt create mode 100644 LICENSE-DUAL.txt create mode 100644 LICENSE-GPL.txt create mode 100644 VERSION.txt create mode 100644 bin/bbackupctl/bbackupctl.cpp create mode 100644 bin/bbackupd/BackupClientContext.cpp create mode 100644 bin/bbackupd/BackupClientContext.h create mode 100644 bin/bbackupd/BackupClientDeleteList.cpp create mode 100644 bin/bbackupd/BackupClientDeleteList.h create mode 100644 bin/bbackupd/BackupClientDirectoryRecord.cpp create mode 100644 bin/bbackupd/BackupClientDirectoryRecord.h create mode 100644 bin/bbackupd/BackupClientInodeToIDMap.cpp create mode 100644 bin/bbackupd/BackupClientInodeToIDMap.h create mode 100644 bin/bbackupd/BackupDaemon.cpp create mode 100644 bin/bbackupd/BackupDaemon.h create mode 100644 bin/bbackupd/BackupDaemonInterface.h create mode 100644 bin/bbackupd/ClientException.txt create mode 100644 bin/bbackupd/Makefile.extra create mode 100644 bin/bbackupd/Win32BackupService.cpp create mode 100644 bin/bbackupd/Win32BackupService.h create mode 100644 bin/bbackupd/Win32ServiceFunctions.cpp create mode 100644 bin/bbackupd/Win32ServiceFunctions.h create mode 100755 bin/bbackupd/bbackupd-config.in create mode 100644 bin/bbackupd/bbackupd.cpp create mode 100644 bin/bbackupd/win32/NotifySysAdmin.vbs create mode 100644 bin/bbackupd/win32/bbackupd.conf create mode 100644 bin/bbackupd/win32/installer.iss create mode 100644 bin/bbackupobjdump/bbackupobjdump.cpp create mode 100644 bin/bbackupquery/BackupQueries.cpp create mode 100644 bin/bbackupquery/BackupQueries.h create mode 100644 bin/bbackupquery/BoxBackupCompareParams.h create mode 100644 bin/bbackupquery/Makefile.extra create mode 100644 bin/bbackupquery/bbackupquery.cpp create mode 100644 bin/bbackupquery/documentation.txt create mode 100755 bin/bbackupquery/makedocumentation.pl.in create mode 100644 bin/bbstoreaccounts/bbstoreaccounts.cpp create mode 100644 bin/bbstored/BBStoreDHousekeeping.cpp create mode 100644 bin/bbstored/BackupCommands.cpp create mode 100644 bin/bbstored/BackupConstants.h create mode 100644 bin/bbstored/BackupStoreContext.cpp create mode 100644 bin/bbstored/BackupStoreContext.h create mode 100644 bin/bbstored/BackupStoreDaemon.cpp create mode 100644 bin/bbstored/BackupStoreDaemon.h create mode 100644 bin/bbstored/HousekeepStoreAccount.cpp create mode 100644 bin/bbstored/HousekeepStoreAccount.h create mode 100644 bin/bbstored/Makefile.extra create mode 100644 bin/bbstored/backupprotocol.txt create mode 100755 bin/bbstored/bbstored-certs.in create mode 100755 bin/bbstored/bbstored-config.in create mode 100644 bin/bbstored/bbstored.cpp create mode 100644 bin/s3simulator/s3simulator.cpp create mode 100755 bootstrap create mode 100755 cleanupforcvs.pl create mode 100755 config.guess create mode 100755 config.sub create mode 100644 configure.ac create mode 100755 contrib/bbadmin/accounts.cgi create mode 100644 contrib/bbadmin/apache.conf create mode 100644 contrib/bbadmin/bb.css create mode 100644 contrib/bbreporter/LICENSE create mode 100755 contrib/bbreporter/bbreporter.py create mode 100644 contrib/debian/README.txt create mode 100644 contrib/debian/bbackupd.in create mode 100644 contrib/debian/bbstored.in create mode 100644 contrib/mac_osx/org.boxbackup.bbackupd.plist.in create mode 100644 contrib/mac_osx/org.boxbackup.bbstored.plist.in create mode 100644 contrib/redhat/README.txt create mode 100644 contrib/redhat/bbackupd.in create mode 100644 contrib/redhat/bbstored.in create mode 100644 contrib/rpm/README.txt create mode 100644 contrib/rpm/boxbackup.spec create mode 100644 contrib/solaris/bbackupd-manifest.xml.in create mode 100755 contrib/solaris/bbackupd-smf-method.in create mode 100644 contrib/solaris/bbstored-manifest.xml.in create mode 100755 contrib/solaris/bbstored-smf-method.in create mode 100644 contrib/suse/README.txt create mode 100644 contrib/suse/bbackupd.in create mode 100644 contrib/suse/bbstored.in create mode 100755 contrib/windows/installer/boxbackup.mpi.in create mode 100755 contrib/windows/installer/tools/InstallService.bat create mode 100755 contrib/windows/installer/tools/KillBackupProcess.bat create mode 100755 contrib/windows/installer/tools/QueryOutputAll.bat create mode 100755 contrib/windows/installer/tools/QueryOutputCurrent.bat create mode 100755 contrib/windows/installer/tools/ReloadConfig.bat create mode 100755 contrib/windows/installer/tools/RemoteControl.exe create mode 100755 contrib/windows/installer/tools/RemoveService.bat create mode 100755 contrib/windows/installer/tools/RestartService.bat create mode 100755 contrib/windows/installer/tools/ShowUsage.bat create mode 100755 contrib/windows/installer/tools/StartService.bat create mode 100755 contrib/windows/installer/tools/StopService.bat create mode 100755 contrib/windows/installer/tools/Sync.bat create mode 100644 distribution/COMMON-MANIFEST.txt create mode 100644 distribution/boxbackup/CONTACT.txt create mode 100644 distribution/boxbackup/DISTRIBUTION-MANIFEST.txt create mode 100644 distribution/boxbackup/DOCUMENTATION.txt create mode 100644 distribution/boxbackup/LINUX.txt create mode 100644 distribution/boxbackup/NETBSD.txt create mode 100644 distribution/boxbackup/THANKS.txt create mode 100644 distribution/boxbackup/VERSION.txt create mode 100644 docs/Makefile create mode 100644 docs/api-notes/INDEX.txt create mode 100644 docs/api-notes/Win32_Clients.txt create mode 100644 docs/api-notes/backup_encryption.txt create mode 100644 docs/api-notes/bin_bbackupd.txt create mode 100644 docs/api-notes/bin_bbstored.txt create mode 100644 docs/api-notes/common/lib_common.txt create mode 100644 docs/api-notes/common/lib_common/BoxTime.txt create mode 100644 docs/api-notes/common/lib_common/CollectInBufferStream.txt create mode 100644 docs/api-notes/common/lib_common/Configuration.txt create mode 100644 docs/api-notes/common/lib_common/Conversion.txt create mode 100644 docs/api-notes/common/lib_common/ExcludeList.txt create mode 100644 docs/api-notes/common/lib_common/FdGetLine.txt create mode 100644 docs/api-notes/common/lib_common/Guards.txt create mode 100644 docs/api-notes/common/lib_common/IOStream.txt create mode 100644 docs/api-notes/common/lib_common/IOStreamGetLine.txt create mode 100644 docs/api-notes/common/lib_common/MainHelper.txt create mode 100644 docs/api-notes/common/lib_common/WaitForEvent.txt create mode 100644 docs/api-notes/common/lib_common/xStream.txt create mode 100644 docs/api-notes/common/lib_compress.txt create mode 100644 docs/api-notes/common/lib_compress/CompressStream.txt create mode 100644 docs/api-notes/common/lib_crypto.txt create mode 100644 docs/api-notes/common/lib_crypto/CipherContext.txt create mode 100644 docs/api-notes/common/lib_crypto/RollingChecksum.txt create mode 100644 docs/api-notes/common/lib_server.txt create mode 100644 docs/api-notes/common/lib_server/Daemon.txt create mode 100644 docs/api-notes/common/lib_server/Protocol.txt create mode 100644 docs/api-notes/common/lib_server/ServerStream.txt create mode 100644 docs/api-notes/common/lib_server/ServerTLS.txt create mode 100644 docs/api-notes/common/lib_server/SocketStream.txt create mode 100644 docs/api-notes/common/lib_server/SocketStreamTLS.txt create mode 100644 docs/api-notes/common/lib_server/TLSContext.txt create mode 100644 docs/api-notes/common/memory_leaks.txt create mode 100644 docs/api-notes/encrypt_rsync.txt create mode 100644 docs/api-notes/lib_backupclient.txt create mode 100644 docs/api-notes/lib_backupstore.txt create mode 100644 docs/api-notes/raidfile/RaidFileRead.txt create mode 100644 docs/api-notes/raidfile/RaidFileWrite.txt create mode 100644 docs/api-notes/raidfile/lib_raidfile.txt create mode 100644 docs/api-notes/win32_build_on_cygwin_using_mingw.txt create mode 100644 docs/api-notes/win32_build_on_linux_using_mingw.txt create mode 100644 docs/api-notes/windows_porting.txt create mode 100644 docs/docbook/adminguide.xml create mode 100644 docs/docbook/bb-book.xsl create mode 100644 docs/docbook/bb-man.xsl create mode 100644 docs/docbook/bb-nochunk-book.xsl create mode 100644 docs/docbook/bbackupctl.xml create mode 100644 docs/docbook/bbackupd-config.xml create mode 100644 docs/docbook/bbackupd.conf.xml create mode 100644 docs/docbook/bbackupd.xml create mode 100644 docs/docbook/bbackupquery.xml create mode 100644 docs/docbook/bbstoreaccounts.xml create mode 100644 docs/docbook/bbstored-certs.xml create mode 100644 docs/docbook/bbstored-config.xml create mode 100644 docs/docbook/bbstored.conf.xml create mode 100644 docs/docbook/bbstored.xml create mode 100644 docs/docbook/html/bbdoc-man.css create mode 100644 docs/docbook/html/bbdoc.css create mode 100644 docs/docbook/html/favicon.ico create mode 100644 docs/docbook/html/images/arrow.png create mode 100644 docs/docbook/html/images/bblogo.png create mode 100644 docs/docbook/html/images/stepahead.png create mode 100644 docs/docbook/instguide.xml create mode 100644 docs/docbook/raidfile-config.xml create mode 100644 docs/docbook/raidfile.conf.xml create mode 100644 docs/images/bblogo-alpha.xcf create mode 100644 docs/tools/generate_except_xml.pl create mode 100644 docs/xsl-generic/VERSION create mode 100644 docs/xsl-generic/common/af.xml create mode 100644 docs/xsl-generic/common/am.xml create mode 100644 docs/xsl-generic/common/ar.xml create mode 100644 docs/xsl-generic/common/autoidx-kimber.xsl create mode 100644 docs/xsl-generic/common/autoidx-kosek.xsl create mode 100644 docs/xsl-generic/common/az.xml create mode 100644 docs/xsl-generic/common/bg.xml create mode 100644 docs/xsl-generic/common/bn.xml create mode 100644 docs/xsl-generic/common/bs.xml create mode 100644 docs/xsl-generic/common/ca.xml create mode 100644 docs/xsl-generic/common/charmap.xml create mode 100644 docs/xsl-generic/common/charmap.xsl create mode 100644 docs/xsl-generic/common/common.xml create mode 100644 docs/xsl-generic/common/common.xsl create mode 100644 docs/xsl-generic/common/cs.xml create mode 100644 docs/xsl-generic/common/cy.xml create mode 100644 docs/xsl-generic/common/da.xml create mode 100644 docs/xsl-generic/common/de.xml create mode 100644 docs/xsl-generic/common/el.xml create mode 100644 docs/xsl-generic/common/en.xml create mode 100644 docs/xsl-generic/common/entities.ent create mode 100644 docs/xsl-generic/common/eo.xml create mode 100644 docs/xsl-generic/common/es.xml create mode 100644 docs/xsl-generic/common/et.xml create mode 100644 docs/xsl-generic/common/eu.xml create mode 100644 docs/xsl-generic/common/fa.xml create mode 100644 docs/xsl-generic/common/fi.xml create mode 100644 docs/xsl-generic/common/fr.xml create mode 100644 docs/xsl-generic/common/ga.xml create mode 100644 docs/xsl-generic/common/gentext.xsl create mode 100644 docs/xsl-generic/common/gu.xml create mode 100644 docs/xsl-generic/common/he.xml create mode 100644 docs/xsl-generic/common/hi.xml create mode 100644 docs/xsl-generic/common/hr.xml create mode 100644 docs/xsl-generic/common/hu.xml create mode 100644 docs/xsl-generic/common/id.xml create mode 100644 docs/xsl-generic/common/insertfile.xsl create mode 100644 docs/xsl-generic/common/it.xml create mode 100644 docs/xsl-generic/common/ja.xml create mode 100644 docs/xsl-generic/common/kn.xml create mode 100644 docs/xsl-generic/common/ko.xml create mode 100644 docs/xsl-generic/common/l10n.dtd create mode 100644 docs/xsl-generic/common/l10n.xml create mode 100644 docs/xsl-generic/common/l10n.xsl create mode 100644 docs/xsl-generic/common/la.xml create mode 100644 docs/xsl-generic/common/labels.xsl create mode 100644 docs/xsl-generic/common/lt.xml create mode 100644 docs/xsl-generic/common/lv.xml create mode 100644 docs/xsl-generic/common/mn.xml create mode 100644 docs/xsl-generic/common/nl.xml create mode 100644 docs/xsl-generic/common/nn.xml create mode 100644 docs/xsl-generic/common/no.xml create mode 100644 docs/xsl-generic/common/olink.xsl create mode 100644 docs/xsl-generic/common/or.xml create mode 100644 docs/xsl-generic/common/pa.xml create mode 100644 docs/xsl-generic/common/pi.xsl create mode 100644 docs/xsl-generic/common/pl.xml create mode 100644 docs/xsl-generic/common/pt.xml create mode 100644 docs/xsl-generic/common/pt_br.xml create mode 100644 docs/xsl-generic/common/refentry.xml create mode 100644 docs/xsl-generic/common/refentry.xsl create mode 100644 docs/xsl-generic/common/ro.xml create mode 100644 docs/xsl-generic/common/ru.xml create mode 100644 docs/xsl-generic/common/sk.xml create mode 100644 docs/xsl-generic/common/sl.xml create mode 100644 docs/xsl-generic/common/sq.xml create mode 100644 docs/xsl-generic/common/sr.xml create mode 100644 docs/xsl-generic/common/sr_Latn.xml create mode 100644 docs/xsl-generic/common/stripns.xsl create mode 100644 docs/xsl-generic/common/subtitles.xsl create mode 100644 docs/xsl-generic/common/sv.xml create mode 100644 docs/xsl-generic/common/ta.xml create mode 100644 docs/xsl-generic/common/table.xsl create mode 100644 docs/xsl-generic/common/targetdatabase.dtd create mode 100644 docs/xsl-generic/common/targets.xsl create mode 100644 docs/xsl-generic/common/th.xml create mode 100644 docs/xsl-generic/common/titles.xsl create mode 100644 docs/xsl-generic/common/tl.xml create mode 100644 docs/xsl-generic/common/tr.xml create mode 100644 docs/xsl-generic/common/uk.xml create mode 100644 docs/xsl-generic/common/utility.xml create mode 100644 docs/xsl-generic/common/utility.xsl create mode 100644 docs/xsl-generic/common/vi.xml create mode 100644 docs/xsl-generic/common/xh.xml create mode 100644 docs/xsl-generic/common/zh_cn.xml create mode 100644 docs/xsl-generic/common/zh_tw.xml create mode 100644 docs/xsl-generic/highlighting/c-hl.xml create mode 100644 docs/xsl-generic/highlighting/common.xsl create mode 100644 docs/xsl-generic/highlighting/delphi-hl.xml create mode 100644 docs/xsl-generic/highlighting/ini-hl.xml create mode 100644 docs/xsl-generic/highlighting/java-hl.xml create mode 100644 docs/xsl-generic/highlighting/m2-hl.xml create mode 100644 docs/xsl-generic/highlighting/myxml-hl.xml create mode 100644 docs/xsl-generic/highlighting/php-hl.xml create mode 100644 docs/xsl-generic/highlighting/xslthl-config.xml create mode 100644 docs/xsl-generic/html/admon.xsl create mode 100644 docs/xsl-generic/html/annotations.xsl create mode 100644 docs/xsl-generic/html/autoidx-kimber.xsl create mode 100644 docs/xsl-generic/html/autoidx-kosek.xsl create mode 100644 docs/xsl-generic/html/autoidx-ng.xsl create mode 100644 docs/xsl-generic/html/autoidx.xsl create mode 100644 docs/xsl-generic/html/autotoc.xsl create mode 100644 docs/xsl-generic/html/biblio-iso690.xsl create mode 100644 docs/xsl-generic/html/biblio.xsl create mode 100644 docs/xsl-generic/html/block.xsl create mode 100644 docs/xsl-generic/html/callout.xsl create mode 100644 docs/xsl-generic/html/changebars.xsl create mode 100644 docs/xsl-generic/html/chunk-code.xsl create mode 100644 docs/xsl-generic/html/chunk-common.xsl create mode 100644 docs/xsl-generic/html/chunk.xsl create mode 100644 docs/xsl-generic/html/chunker.xsl create mode 100644 docs/xsl-generic/html/chunkfast.xsl create mode 100644 docs/xsl-generic/html/chunktoc.xsl create mode 100644 docs/xsl-generic/html/component.xsl create mode 100644 docs/xsl-generic/html/division.xsl create mode 100644 docs/xsl-generic/html/docbook.xsl create mode 100644 docs/xsl-generic/html/ebnf.xsl create mode 100644 docs/xsl-generic/html/footnote.xsl create mode 100644 docs/xsl-generic/html/formal.xsl create mode 100644 docs/xsl-generic/html/glossary.xsl create mode 100644 docs/xsl-generic/html/graphics.xsl create mode 100644 docs/xsl-generic/html/highlight.xsl create mode 100644 docs/xsl-generic/html/html-rtf.xsl create mode 100644 docs/xsl-generic/html/html.xsl create mode 100644 docs/xsl-generic/html/htmltbl.xsl create mode 100644 docs/xsl-generic/html/index.xsl create mode 100644 docs/xsl-generic/html/info.xsl create mode 100644 docs/xsl-generic/html/inline.xsl create mode 100644 docs/xsl-generic/html/keywords.xsl create mode 100644 docs/xsl-generic/html/lists.xsl create mode 100644 docs/xsl-generic/html/maketoc.xsl create mode 100644 docs/xsl-generic/html/manifest.xsl create mode 100644 docs/xsl-generic/html/math.xsl create mode 100644 docs/xsl-generic/html/oldchunker.xsl create mode 100644 docs/xsl-generic/html/onechunk.xsl create mode 100644 docs/xsl-generic/html/param.xsl create mode 100644 docs/xsl-generic/html/pi.xsl create mode 100644 docs/xsl-generic/html/profile-chunk-code.xsl create mode 100644 docs/xsl-generic/html/profile-chunk.xsl create mode 100644 docs/xsl-generic/html/profile-docbook.xsl create mode 100644 docs/xsl-generic/html/profile-onechunk.xsl create mode 100644 docs/xsl-generic/html/qandaset.xsl create mode 100644 docs/xsl-generic/html/refentry.xsl create mode 100644 docs/xsl-generic/html/sections.xsl create mode 100644 docs/xsl-generic/html/synop.xsl create mode 100644 docs/xsl-generic/html/table.xsl create mode 100644 docs/xsl-generic/html/task.xsl create mode 100644 docs/xsl-generic/html/titlepage.templates.xml create mode 100644 docs/xsl-generic/html/titlepage.templates.xsl create mode 100644 docs/xsl-generic/html/titlepage.xsl create mode 100644 docs/xsl-generic/html/toc.xsl create mode 100644 docs/xsl-generic/html/verbatim.xsl create mode 100644 docs/xsl-generic/html/xref.xsl create mode 100644 docs/xsl-generic/lib/lib.xsl create mode 100644 docs/xsl-generic/manpages/block.xsl create mode 100644 docs/xsl-generic/manpages/charmap.groff.xsl create mode 100644 docs/xsl-generic/manpages/docbook.xsl create mode 100644 docs/xsl-generic/manpages/endnotes.xsl create mode 100644 docs/xsl-generic/manpages/html-synop.xsl create mode 100644 docs/xsl-generic/manpages/info.xsl create mode 100644 docs/xsl-generic/manpages/inline.xsl create mode 100644 docs/xsl-generic/manpages/lists.xsl create mode 100644 docs/xsl-generic/manpages/other.xsl create mode 100644 docs/xsl-generic/manpages/param.xsl create mode 100644 docs/xsl-generic/manpages/profile-docbook.xsl create mode 100644 docs/xsl-generic/manpages/refentry.xsl create mode 100644 docs/xsl-generic/manpages/synop.xsl create mode 100644 docs/xsl-generic/manpages/table.xsl create mode 100644 docs/xsl-generic/manpages/utility.xsl create mode 100644 infrastructure/BoxPlatform.pm.in create mode 100644 infrastructure/buildenv-testmain-template.cpp create mode 100644 infrastructure/m4/ac_cxx_exceptions.m4 create mode 100644 infrastructure/m4/ac_cxx_namespaces.m4 create mode 100644 infrastructure/m4/ax_bswap64.m4 create mode 100644 infrastructure/m4/ax_check_bdb_v1.m4 create mode 100644 infrastructure/m4/ax_check_define_pragma.m4 create mode 100644 infrastructure/m4/ax_check_dirent_d_type.m4 create mode 100644 infrastructure/m4/ax_check_llong_minmax.m4 create mode 100644 infrastructure/m4/ax_check_malloc_workaround.m4 create mode 100644 infrastructure/m4/ax_check_mount_point.m4 create mode 100644 infrastructure/m4/ax_check_nonaligned_access.m4 create mode 100644 infrastructure/m4/ax_check_ssl.m4 create mode 100644 infrastructure/m4/ax_check_syscall_lseek.m4 create mode 100644 infrastructure/m4/ax_compare_version.m4 create mode 100644 infrastructure/m4/ax_config_scripts.m4 create mode 100644 infrastructure/m4/ax_func_syscall.m4 create mode 100644 infrastructure/m4/ax_path_bdb.m4 create mode 100644 infrastructure/m4/ax_random_device.m4 create mode 100644 infrastructure/m4/ax_split_version.m4 create mode 100644 infrastructure/m4/vl_lib_readline.m4 create mode 100755 infrastructure/makebuildenv.pl.in create mode 100755 infrastructure/makedistribution.pl.in create mode 100755 infrastructure/makeparcels.pl.in create mode 100755 infrastructure/mingw/configure.sh create mode 100644 infrastructure/msvc/2003/bbackupctl.vcproj create mode 100644 infrastructure/msvc/2003/bbackupd.vcproj create mode 100644 infrastructure/msvc/2003/boxbackup.sln create mode 100644 infrastructure/msvc/2003/boxquery.vcproj create mode 100644 infrastructure/msvc/2003/common.vcproj create mode 100644 infrastructure/msvc/2003/win32test.vcproj create mode 100644 infrastructure/msvc/2005/bbackupctl.vcproj create mode 100644 infrastructure/msvc/2005/bbackupd.vcproj create mode 100644 infrastructure/msvc/2005/boxbackup.sln create mode 100644 infrastructure/msvc/2005/boxbackup.suo create mode 100644 infrastructure/msvc/2005/boxquery.vcproj create mode 100644 infrastructure/msvc/2005/common.vcproj create mode 100644 infrastructure/msvc/2005/win32test.vcproj create mode 100644 infrastructure/msvc/getversion.pl create mode 100644 infrastructure/parcelpath.pl create mode 100644 infrastructure/printversion.pl create mode 100755 infrastructure/setupexternal.pl create mode 100644 lib/backupclient/BackupClientCryptoKeys.cpp create mode 100644 lib/backupclient/BackupClientCryptoKeys.h create mode 100644 lib/backupclient/BackupClientFileAttributes.cpp create mode 100644 lib/backupclient/BackupClientFileAttributes.h create mode 100644 lib/backupclient/BackupClientMakeExcludeList.cpp create mode 100644 lib/backupclient/BackupClientMakeExcludeList.h create mode 100644 lib/backupclient/BackupClientRestore.cpp create mode 100644 lib/backupclient/BackupClientRestore.h create mode 100644 lib/backupclient/BackupDaemonConfigVerify.cpp create mode 100644 lib/backupclient/BackupDaemonConfigVerify.h create mode 100644 lib/backupclient/BackupStoreConstants.h create mode 100644 lib/backupclient/BackupStoreDirectory.cpp create mode 100644 lib/backupclient/BackupStoreDirectory.h create mode 100644 lib/backupclient/BackupStoreException.h create mode 100644 lib/backupclient/BackupStoreException.txt create mode 100644 lib/backupclient/BackupStoreFile.cpp create mode 100644 lib/backupclient/BackupStoreFile.h create mode 100644 lib/backupclient/BackupStoreFileCmbDiff.cpp create mode 100644 lib/backupclient/BackupStoreFileCmbIdx.cpp create mode 100644 lib/backupclient/BackupStoreFileCombine.cpp create mode 100644 lib/backupclient/BackupStoreFileCryptVar.cpp create mode 100644 lib/backupclient/BackupStoreFileCryptVar.h create mode 100644 lib/backupclient/BackupStoreFileDiff.cpp create mode 100644 lib/backupclient/BackupStoreFileEncodeStream.cpp create mode 100644 lib/backupclient/BackupStoreFileEncodeStream.h create mode 100644 lib/backupclient/BackupStoreFileRevDiff.cpp create mode 100644 lib/backupclient/BackupStoreFileWire.h create mode 100644 lib/backupclient/BackupStoreFilename.cpp create mode 100644 lib/backupclient/BackupStoreFilename.h create mode 100644 lib/backupclient/BackupStoreFilenameClear.cpp create mode 100644 lib/backupclient/BackupStoreFilenameClear.h create mode 100644 lib/backupclient/BackupStoreObjectDump.cpp create mode 100644 lib/backupclient/BackupStoreObjectMagic.h create mode 100644 lib/backupclient/Makefile.extra create mode 100644 lib/backupclient/RunStatusProvider.h create mode 100644 lib/backupstore/BackupStoreAccountDatabase.cpp create mode 100644 lib/backupstore/BackupStoreAccountDatabase.h create mode 100644 lib/backupstore/BackupStoreAccounts.cpp create mode 100644 lib/backupstore/BackupStoreAccounts.h create mode 100644 lib/backupstore/BackupStoreCheck.cpp create mode 100644 lib/backupstore/BackupStoreCheck.h create mode 100644 lib/backupstore/BackupStoreCheck2.cpp create mode 100644 lib/backupstore/BackupStoreCheckData.cpp create mode 100644 lib/backupstore/BackupStoreConfigVerify.cpp create mode 100644 lib/backupstore/BackupStoreConfigVerify.h create mode 100644 lib/backupstore/BackupStoreInfo.cpp create mode 100644 lib/backupstore/BackupStoreInfo.h create mode 100644 lib/backupstore/BackupStoreRefCountDatabase.cpp create mode 100644 lib/backupstore/BackupStoreRefCountDatabase.h create mode 100644 lib/backupstore/StoreStructure.cpp create mode 100644 lib/backupstore/StoreStructure.h create mode 100644 lib/common/Archive.h create mode 100644 lib/common/BannerText.h create mode 100644 lib/common/BeginStructPackForWire.h create mode 100644 lib/common/Box.h create mode 100644 lib/common/BoxConfig-MSVC.h create mode 100644 lib/common/BoxException.cpp create mode 100644 lib/common/BoxException.h create mode 100644 lib/common/BoxPlatform.h create mode 100644 lib/common/BoxPortsAndFiles.h.in create mode 100644 lib/common/BoxTime.cpp create mode 100644 lib/common/BoxTime.h create mode 100644 lib/common/BoxTimeToText.cpp create mode 100644 lib/common/BoxTimeToText.h create mode 100644 lib/common/BoxTimeToUnix.h create mode 100644 lib/common/BufferedStream.cpp create mode 100644 lib/common/BufferedStream.h create mode 100644 lib/common/BufferedWriteStream.cpp create mode 100644 lib/common/BufferedWriteStream.h create mode 100644 lib/common/CollectInBufferStream.cpp create mode 100644 lib/common/CollectInBufferStream.h create mode 100644 lib/common/CommonException.h create mode 100644 lib/common/CommonException.txt create mode 100644 lib/common/Configuration.cpp create mode 100644 lib/common/Configuration.h create mode 100644 lib/common/Conversion.h create mode 100644 lib/common/ConversionException.txt create mode 100644 lib/common/ConversionString.cpp create mode 100644 lib/common/DebugAssertFailed.cpp create mode 100644 lib/common/DebugMemLeakFinder.cpp create mode 100644 lib/common/DebugPrintf.cpp create mode 100644 lib/common/EndStructPackForWire.h create mode 100644 lib/common/EventWatchFilesystemObject.cpp create mode 100644 lib/common/EventWatchFilesystemObject.h create mode 100644 lib/common/ExcludeList.cpp create mode 100644 lib/common/ExcludeList.h create mode 100644 lib/common/FdGetLine.cpp create mode 100644 lib/common/FdGetLine.h create mode 100644 lib/common/FileModificationTime.cpp create mode 100644 lib/common/FileModificationTime.h create mode 100644 lib/common/FileStream.cpp create mode 100644 lib/common/FileStream.h create mode 100644 lib/common/Guards.h create mode 100644 lib/common/IOStream.cpp create mode 100644 lib/common/IOStream.h create mode 100644 lib/common/IOStreamGetLine.cpp create mode 100644 lib/common/IOStreamGetLine.h create mode 100644 lib/common/InvisibleTempFileStream.cpp create mode 100644 lib/common/InvisibleTempFileStream.h create mode 100644 lib/common/Logging.cpp create mode 100644 lib/common/Logging.h create mode 100644 lib/common/MainHelper.h create mode 100644 lib/common/Makefile.extra create mode 100644 lib/common/MemBlockStream.cpp create mode 100644 lib/common/MemBlockStream.h create mode 100644 lib/common/MemLeakFindOff.h create mode 100644 lib/common/MemLeakFindOn.h create mode 100644 lib/common/MemLeakFinder.h create mode 100644 lib/common/NamedLock.cpp create mode 100644 lib/common/NamedLock.h create mode 100644 lib/common/PartialReadStream.cpp create mode 100644 lib/common/PartialReadStream.h create mode 100644 lib/common/PathUtils.cpp create mode 100644 lib/common/PathUtils.h create mode 100644 lib/common/ReadGatherStream.cpp create mode 100644 lib/common/ReadGatherStream.h create mode 100644 lib/common/ReadLoggingStream.cpp create mode 100644 lib/common/ReadLoggingStream.h create mode 100644 lib/common/SelfFlushingStream.h create mode 100644 lib/common/StreamableMemBlock.cpp create mode 100644 lib/common/StreamableMemBlock.h create mode 100644 lib/common/TemporaryDirectory.h create mode 100644 lib/common/Test.cpp create mode 100644 lib/common/Test.h create mode 100644 lib/common/Timer.cpp create mode 100644 lib/common/Timer.h create mode 100644 lib/common/UnixUser.cpp create mode 100644 lib/common/UnixUser.h create mode 100644 lib/common/Utils.cpp create mode 100644 lib/common/Utils.h create mode 100644 lib/common/WaitForEvent.cpp create mode 100644 lib/common/WaitForEvent.h create mode 100644 lib/common/ZeroStream.cpp create mode 100644 lib/common/ZeroStream.h create mode 100755 lib/common/makeexception.pl.in create mode 100644 lib/compress/Compress.h create mode 100644 lib/compress/CompressException.h create mode 100644 lib/compress/CompressException.txt create mode 100644 lib/compress/CompressStream.cpp create mode 100644 lib/compress/CompressStream.h create mode 100644 lib/compress/Makefile.extra create mode 100644 lib/crypto/CipherAES.cpp create mode 100644 lib/crypto/CipherAES.h create mode 100644 lib/crypto/CipherBlowfish.cpp create mode 100644 lib/crypto/CipherBlowfish.h create mode 100644 lib/crypto/CipherContext.cpp create mode 100644 lib/crypto/CipherContext.h create mode 100644 lib/crypto/CipherDescription.cpp create mode 100644 lib/crypto/CipherDescription.h create mode 100644 lib/crypto/CipherException.h create mode 100644 lib/crypto/CipherException.txt create mode 100644 lib/crypto/MD5Digest.cpp create mode 100644 lib/crypto/MD5Digest.h create mode 100644 lib/crypto/Makefile.extra create mode 100644 lib/crypto/Random.cpp create mode 100644 lib/crypto/Random.h create mode 100644 lib/crypto/RollingChecksum.cpp create mode 100644 lib/crypto/RollingChecksum.h create mode 100644 lib/httpserver/HTTPException.txt create mode 100644 lib/httpserver/HTTPQueryDecoder.cpp create mode 100644 lib/httpserver/HTTPQueryDecoder.h create mode 100644 lib/httpserver/HTTPRequest.cpp create mode 100644 lib/httpserver/HTTPRequest.h create mode 100644 lib/httpserver/HTTPResponse.cpp create mode 100644 lib/httpserver/HTTPResponse.h create mode 100644 lib/httpserver/HTTPServer.cpp create mode 100644 lib/httpserver/HTTPServer.h create mode 100644 lib/httpserver/Makefile.extra create mode 100644 lib/httpserver/S3Client.cpp create mode 100644 lib/httpserver/S3Client.h create mode 100644 lib/httpserver/S3Simulator.cpp create mode 100644 lib/httpserver/S3Simulator.h create mode 100644 lib/httpserver/cdecode.cpp create mode 100644 lib/httpserver/cdecode.h create mode 100644 lib/httpserver/cencode.cpp create mode 100644 lib/httpserver/cencode.h create mode 100644 lib/httpserver/decode.h create mode 100644 lib/httpserver/encode.h create mode 100644 lib/intercept/intercept.cpp create mode 100644 lib/intercept/intercept.h create mode 100644 lib/raidfile/Makefile.extra create mode 100644 lib/raidfile/RaidFileController.cpp create mode 100644 lib/raidfile/RaidFileController.h create mode 100644 lib/raidfile/RaidFileException.h create mode 100644 lib/raidfile/RaidFileException.txt create mode 100644 lib/raidfile/RaidFileRead.cpp create mode 100644 lib/raidfile/RaidFileRead.h create mode 100644 lib/raidfile/RaidFileUtil.cpp create mode 100644 lib/raidfile/RaidFileUtil.h create mode 100644 lib/raidfile/RaidFileWrite.cpp create mode 100644 lib/raidfile/RaidFileWrite.h create mode 100755 lib/raidfile/raidfile-config.in create mode 100644 lib/server/ConnectionException.txt create mode 100644 lib/server/Daemon.cpp create mode 100644 lib/server/Daemon.h create mode 100644 lib/server/LocalProcessStream.cpp create mode 100644 lib/server/LocalProcessStream.h create mode 100644 lib/server/Makefile.extra create mode 100644 lib/server/OverlappedIO.h create mode 100644 lib/server/Protocol.cpp create mode 100644 lib/server/Protocol.h create mode 100644 lib/server/ProtocolObject.cpp create mode 100644 lib/server/ProtocolObject.h create mode 100644 lib/server/ProtocolUncertainStream.cpp create mode 100644 lib/server/ProtocolUncertainStream.h create mode 100644 lib/server/ProtocolWire.h create mode 100644 lib/server/SSLLib.cpp create mode 100644 lib/server/SSLLib.h create mode 100644 lib/server/ServerControl.cpp create mode 100644 lib/server/ServerControl.h create mode 100644 lib/server/ServerException.h create mode 100644 lib/server/ServerException.txt create mode 100644 lib/server/ServerStream.h create mode 100644 lib/server/ServerTLS.h create mode 100644 lib/server/Socket.cpp create mode 100644 lib/server/Socket.h create mode 100644 lib/server/SocketListen.h create mode 100644 lib/server/SocketStream.cpp create mode 100644 lib/server/SocketStream.h create mode 100644 lib/server/SocketStreamTLS.cpp create mode 100644 lib/server/SocketStreamTLS.h create mode 100644 lib/server/TLSContext.cpp create mode 100644 lib/server/TLSContext.h create mode 100644 lib/server/WinNamedPipeListener.h create mode 100644 lib/server/WinNamedPipeStream.cpp create mode 100644 lib/server/WinNamedPipeStream.h create mode 100755 lib/server/makeprotocol.pl.in create mode 100755 lib/win32/MSG00001.bin create mode 100644 lib/win32/emu.cpp create mode 100644 lib/win32/emu.h create mode 100755 lib/win32/getopt.h create mode 100755 lib/win32/getopt_long.cpp create mode 100755 lib/win32/messages.h create mode 100644 lib/win32/messages.mc create mode 100755 lib/win32/messages.rc create mode 100644 modules.txt create mode 100644 parcels.txt create mode 100755 runtest.pl.in create mode 100644 test/backupdiff/difftestfiles.cpp create mode 100644 test/backupdiff/testbackupdiff.cpp create mode 100644 test/backupdiff/testextra create mode 100644 test/backupstore/Makefile.extra create mode 100644 test/backupstore/testbackupstore.cpp create mode 100644 test/backupstore/testextra create mode 100644 test/backupstore/testfiles/accounts.txt create mode 100644 test/backupstore/testfiles/bbackupd.keys create mode 100644 test/backupstore/testfiles/bbstored.conf create mode 100644 test/backupstore/testfiles/bbstored_multi.conf create mode 100644 test/backupstore/testfiles/clientCerts.pem create mode 100644 test/backupstore/testfiles/clientPrivKey.pem create mode 100644 test/backupstore/testfiles/clientReq.pem create mode 100644 test/backupstore/testfiles/clientTrustedCAs.pem create mode 100644 test/backupstore/testfiles/query.conf create mode 100644 test/backupstore/testfiles/raidfile.conf create mode 100644 test/backupstore/testfiles/root.pem create mode 100644 test/backupstore/testfiles/root.srl create mode 100644 test/backupstore/testfiles/rootcert.pem create mode 100644 test/backupstore/testfiles/rootkey.pem create mode 100644 test/backupstore/testfiles/rootreq.pem create mode 100644 test/backupstore/testfiles/serverCerts.pem create mode 100644 test/backupstore/testfiles/serverPrivKey.pem create mode 100644 test/backupstore/testfiles/serverReq.pem create mode 100644 test/backupstore/testfiles/serverTrustedCAs.pem create mode 100644 test/backupstorefix/testbackupstorefix.cpp create mode 100644 test/backupstorefix/testextra create mode 100755 test/backupstorefix/testfiles/testbackupstorefix.pl.in create mode 100644 test/backupstorepatch/testbackupstorepatch.cpp create mode 100644 test/backupstorepatch/testextra create mode 100644 test/basicserver/Makefile.extra create mode 100644 test/basicserver/TestCommands.cpp create mode 100644 test/basicserver/TestContext.cpp create mode 100644 test/basicserver/TestContext.h create mode 100644 test/basicserver/testbasicserver.cpp create mode 100644 test/basicserver/testfiles/clientCerts.pem create mode 100644 test/basicserver/testfiles/clientPrivKey.pem create mode 100644 test/basicserver/testfiles/clientReq.pem create mode 100644 test/basicserver/testfiles/clientTrustedCAs.pem create mode 100644 test/basicserver/testfiles/key-creation.txt create mode 100644 test/basicserver/testfiles/root.pem create mode 100644 test/basicserver/testfiles/root.srl create mode 100644 test/basicserver/testfiles/rootcert.pem create mode 100644 test/basicserver/testfiles/rootkey.pem create mode 100644 test/basicserver/testfiles/rootreq.pem create mode 100644 test/basicserver/testfiles/serverCerts.pem create mode 100644 test/basicserver/testfiles/serverPrivKey.pem create mode 100644 test/basicserver/testfiles/serverReq.pem create mode 100644 test/basicserver/testfiles/serverTrustedCAs.pem create mode 100644 test/basicserver/testfiles/srv1.conf create mode 100644 test/basicserver/testfiles/srv1b.conf create mode 100644 test/basicserver/testfiles/srv2.conf create mode 100644 test/basicserver/testfiles/srv3.conf create mode 100644 test/basicserver/testfiles/srv4.conf create mode 100644 test/basicserver/testprotocol.txt create mode 100644 test/bbackupd/Makefile.extra create mode 100644 test/bbackupd/testbbackupd.cpp create mode 100644 test/bbackupd/testextra create mode 100644 test/bbackupd/testfiles/accounts.txt create mode 100644 test/bbackupd/testfiles/bbackupd-exclude.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-snapshot.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-symlink.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-temploc.conf create mode 100644 test/bbackupd/testfiles/bbackupd.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd.keys create mode 100644 test/bbackupd/testfiles/bbstored.conf create mode 100644 test/bbackupd/testfiles/clientCerts.pem create mode 100644 test/bbackupd/testfiles/clientPrivKey.pem create mode 100644 test/bbackupd/testfiles/clientTrustedCAs.pem create mode 100755 test/bbackupd/testfiles/extcheck1.pl.in create mode 100755 test/bbackupd/testfiles/extcheck2.pl.in create mode 100755 test/bbackupd/testfiles/notifyscript.pl.in create mode 100644 test/bbackupd/testfiles/raidfile.conf create mode 100644 test/bbackupd/testfiles/serverCerts.pem create mode 100644 test/bbackupd/testfiles/serverPrivKey.pem create mode 100644 test/bbackupd/testfiles/serverTrustedCAs.pem create mode 100644 test/bbackupd/testfiles/spacetest1.tgz create mode 100644 test/bbackupd/testfiles/spacetest2.tgz create mode 100755 test/bbackupd/testfiles/syncallowscript.pl.in create mode 100644 test/bbackupd/testfiles/test2.tgz create mode 100644 test/bbackupd/testfiles/test3.tgz create mode 100644 test/bbackupd/testfiles/test_base.tgz create mode 100644 test/bbackupd/testfiles/testexclude.tgz create mode 100644 test/common/testcommon.cpp create mode 100644 test/common/testfiles/config1.txt create mode 100644 test/common/testfiles/config10.txt create mode 100644 test/common/testfiles/config11.txt create mode 100644 test/common/testfiles/config12.txt create mode 100644 test/common/testfiles/config13.txt create mode 100644 test/common/testfiles/config14.txt create mode 100644 test/common/testfiles/config15.txt create mode 100644 test/common/testfiles/config16.txt create mode 100644 test/common/testfiles/config2.txt create mode 100644 test/common/testfiles/config3.txt create mode 100644 test/common/testfiles/config4.txt create mode 100644 test/common/testfiles/config5.txt create mode 100644 test/common/testfiles/config6.txt create mode 100644 test/common/testfiles/config7.txt create mode 100644 test/common/testfiles/config8.txt create mode 100644 test/common/testfiles/config9.txt create mode 100644 test/common/testfiles/config9b.txt create mode 100644 test/common/testfiles/config9c.txt create mode 100644 test/common/testfiles/config9d.txt create mode 100644 test/common/testfiles/fdgetlinetest.txt create mode 100644 test/compress/testcompress.cpp create mode 100644 test/crypto/testcrypto.cpp create mode 100644 test/httpserver/testfiles/httpserver.conf create mode 100644 test/httpserver/testfiles/photos/puppy.jpg create mode 100644 test/httpserver/testfiles/s3simulator.conf create mode 100755 test/httpserver/testfiles/testrequests.pl create mode 100644 test/httpserver/testhttpserver.cpp create mode 100644 test/raidfile/testextra create mode 100644 test/raidfile/testfiles/raidfile.conf create mode 100644 test/raidfile/testraidfile.cpp create mode 100644 test/win32/Makefile create mode 100644 test/win32/testlibwin32.cpp create mode 100644 test/win32/timezone.cpp create mode 100644 win32.bat diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..c21cefc6 --- /dev/null +++ b/.hgignore @@ -0,0 +1,37 @@ +.svn +BoxConfig.h +BoxConfig.h.in +BoxPlatform.pm +BoxPortsAndFiles.h +ExceptionCodes.txt +Makefile +_main.cpp +_t +_t-gdb +aclocal.m4 +autogen_* +autom4te.cache +bbackupd-config +bbackupd.conf +bbstored-certs +bbstored-config +config.log +config.status +configure +debug +extcheck1.pl +extcheck2.pl +local +makebuildenv.pl +makedistribution.pl +makedocumentation.pl +makeexception.pl +makeparcels.pl +makeprotocol.pl +notifyscript.pl +parcels +raidfile-config +release +runtest.pl +syncallowscript.pl +testbackupstorefix.pl diff --git a/.svnrevision b/.svnrevision new file mode 100644 index 00000000..f1293228 --- /dev/null +++ b/.svnrevision @@ -0,0 +1 @@ +2837 diff --git a/BUGS.txt b/BUGS.txt new file mode 100644 index 00000000..e0569113 --- /dev/null +++ b/BUGS.txt @@ -0,0 +1,15 @@ +================================================================================================================ +Bugs +================================================================================================================ + +* need a test to check that small files aren't tracked +* things like object ids don't have proper typedefs +* if a file changes while it's being streamed to the server, bad things will happen (exception in bbackupd, or corrupt file on server) +* if bbackupd gets an error then a signal, it may not wait it's full 100 seconds before retrying. And then won't stop the cycle... +* bbackupquery restore, if not root, then won't do file ownership properly, but won't alert the user to this fact +* empty (real) directories in the store aren't deleted when they're empty (and will never be used again) -- uses up disc space unnecessarily +* need unit tests for SSL keepalives and state saving (serialisation) +* make Archive derive from Protocol +* more automated tests for win32 +* change off_t to box_off_t in preparation for win32 large file support +* support large files on win32 by using native *i64 functions instead of posix diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 00000000..54d27b62 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,491 @@ +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 follows: +--------------------------------------------------------------------- +The Box Backup GPL is based on the GPLv2 (GNU General Public License +version 2 or later) as published by the Free Software Foundation. +However, it includes optional exceptions which allow linking with +OpenSSL and Microsoft VSS, listed below. This means that Box Backup +is not under pure GPLv2 as published by the Free Software Foundation. +These are the only differences between the Box Backup GPL license and +the original GPL from the Free Software Foundation. + +Please note that while the Box Backup GPL is similar in spirit to the +original GPL, it is not fully compatible. Because of these optional +exceptions, you may not include or combine fully GPL source code +(such as the GNU readline library) with Box Backup, or distribute the +resulting binaries, under the Box Backup GPL license, without +specific permission from the authors of such code. You may do so +under the pure GPL license, by omitting the optional exclusion clauses +below, however you may not legally link such code with OpenSSL or +Microsoft VSS, which may limit the usefulness or functionality of the +resulting code. + +The license exemption section below was based on the license of the +Bacula project +[http://bacula.git.sourceforge.net/git/gitweb.cgi?p=bacula/bacula;a=blob_plain;f=bacula/LICENSE;hb=HEAD]. + +For the most part, Box Backup is licensed under the GPL version 2 +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]. +The sections that follow are optional exemptions to the GPL version 2 +license, that apply to code that is copyrighted by Ben Summers and +Contributors. + +Linking: +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any software that can be downloaded from +the OpenSSL website [http://www.openssl.org] under either the +"OpenSSL License" or the "Original SSLeay License", and to distribute +the linked executables under the terms of the "Box Backup GPL" license. + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK +or Microsoft Windows Software Development Kit (SDK), including +vssapi.lib, that can be downloaded from the Microsoft website +[*.microsoft.com], and to distribute the linked executables under the +terms of the "Box Backup GPL" license. + +The sections above are optional, and you may distribute the code that +falls under this license under the original GPLv2 license by omitting +them. The original GPLv2 license follows. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. +--------------------------------------------------------------------- + +Unless stated otherwise in the file or in the list of directories above, +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 BSD license follows: +--------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the Box Backup nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. +--------------------------------------------------------------------- +[http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29] + diff --git a/LICENSE-DUAL.txt b/LICENSE-DUAL.txt new file mode 100644 index 00000000..6aa31da2 --- /dev/null +++ b/LICENSE-DUAL.txt @@ -0,0 +1,59 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2010, Ben Summers and contributors. +All rights reserved. + +Note that this project uses mixed licensing. Any file with this license +attached, or where the code LICENSE-DUAL appears on the first line, falls +under this license. See the file COPYING.txt for more information. + +This file is dual licensed. You may use and distribute it 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. + +The BSD license option follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the Box Backup nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. + +[http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29] + +The GPL license option follows: + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4] diff --git a/LICENSE-GPL.txt b/LICENSE-GPL.txt new file mode 100644 index 00000000..3e84b646 --- /dev/null +++ b/LICENSE-GPL.txt @@ -0,0 +1,41 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2010, Ben Summers and contributors. +All rights reserved. + +Note that this project uses mixed licensing. Any file with this license +attached, or where the code LICENSE-GPL appears on the first line, falls +under the "Box Backup GPL" license. See the file COPYING.txt for more +information about this license. + +--------------------------------------------------------------------- +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4] + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any software that can be downloaded from +the OpenSSL website [http://www.openssl.org] under either the +"OpenSSL License" or the "Original SSLeay License", and to distribute +the linked executables under the terms of the "Box Backup GPL" license. + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK +or Microsoft Windows Software Development Kit (SDK), including +vssapi.lib, that can be downloaded from the Microsoft website +[*.microsoft.com], and to distribute the linked executables under the +terms of the "Box Backup GPL" license. diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 00000000..c29321a1 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1,2 @@ +USE_SVN_VERSION +boxbackup diff --git a/bin/bbackupctl/bbackupctl.cpp b/bin/bbackupctl/bbackupctl.cpp new file mode 100644 index 00000000..8dc8f30e --- /dev/null +++ b/bin/bbackupctl/bbackupctl.cpp @@ -0,0 +1,366 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupctl.cpp +// Purpose: bbackupd daemon control program +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include + +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupDaemonConfigVerify.h" +#include "Socket.h" +#include "SocketStream.h" +#include "IOStreamGetLine.h" + +#ifdef WIN32 + #include "WinNamedPipeStream.h" +#endif + +#include "MemLeakFindOn.h" + +enum Command +{ + Default, + WaitForSyncStart, + WaitForSyncEnd, + SyncAndWaitForEnd, +}; + +void PrintUsageAndExit() +{ + printf("Usage: bbackupctl [-q] [-c config_file] \n" + "Commands are:\n" + " sync -- start a synchronisation (backup) run now\n" + " force-sync -- force the start of a synchronisation run, " + "even if SyncAllowScript says no\n" + " reload -- reload daemon configuration\n" + " terminate -- terminate daemon now\n" + " wait-for-sync -- wait until the next sync starts, then exit\n" + " wait-for-end -- wait until the next sync finishes, then exit\n" + " sync-and-wait -- start sync, wait until it finishes, then exit\n" + ); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int returnCode = 0; + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupctl.memleaks", + "bbackupctl") + + MAINHELPER_START + + 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 + + // Quiet? + bool quiet = false; + + // See if there's another entry on the command line + int c; + while((c = getopt(argc, (char * const *)argv, "qc:l:")) != -1) + { + switch(c) + { + case 'q': + // Quiet mode + quiet = true; + break; + + case 'c': + // store argument + configFilename = optarg; + break; + + case '?': + default: + PrintUsageAndExit(); + } + } + // Adjust arguments + argc -= optind; + argv += optind; + + // Check there's a command + if(argc != 1) + { + PrintUsageAndExit(); + } + + // Read in the configuration file + if(!quiet) BOX_NOTICE("Using configuration file " << configFilename); + + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_ERROR("Invalid configuration file: " << errs); + return 1; + } + // Easier coding + const Configuration &conf(*config); + + // Check there's a socket defined in the config file + if(!conf.KeyExists("CommandSocket")) + { + BOX_ERROR("Daemon isn't using a control socket, " + "could not execute command.\n" + "Add a CommandSocket declaration to the " + "bbackupd.conf file."); + return 1; + } + + // Connect to socket + +#ifndef WIN32 + SocketStream connection; +#else /* WIN32 */ + WinNamedPipeStream connection; +#endif /* ! WIN32 */ + + try + { +#ifdef WIN32 + std::string socket = conf.GetKeyValue("CommandSocket"); + connection.Connect(socket); +#else + connection.Open(Socket::TypeUNIX, conf.GetKeyValue("CommandSocket").c_str()); +#endif + } + catch(...) + { + BOX_ERROR("Failed to connect to daemon control socket.\n" + "Possible causes:\n" + " * Daemon not running\n" + " * Daemon busy syncing with store server\n" + " * Another bbackupctl process is communicating with the daemon\n" + " * Daemon is waiting to recover from an error" + ); + + return 1; + } + + // For receiving data + IOStreamGetLine getLine(connection); + + // Wait for the configuration summary + std::string configSummary; + if(!getLine.GetLine(configSummary)) + { + BOX_ERROR("Failed to receive configuration summary " + "from daemon"); + return 1; + } + + // Was the connection rejected by the server? + if(getLine.IsEOF()) + { + BOX_ERROR("Server rejected the connection. Are you running " + "bbackupctl as the same user as the daemon?"); + return 1; + } + + // Decode it + int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; + if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, + &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) + { + BOX_ERROR("Config summary didn't decode."); + return 1; + } + // Print summary? + if(!quiet) + { + BOX_INFO("Daemon configuration summary:\n" + " AutomaticBackup = " << + (autoBackup?"true":"false") << "\n" + " UpdateStoreInterval = " << updateStoreInterval << + " seconds\n" + " MinimumFileAge = " << minimumFileAge << " seconds\n" + " MaxUploadWait = " << maxUploadWait << " seconds"); + } + + std::string stateLine; + if(!getLine.GetLine(stateLine) || getLine.IsEOF()) + { + BOX_ERROR("Failed to receive state line from daemon"); + return 1; + } + + // Decode it + int currentState; + if(::sscanf(stateLine.c_str(), "state %d", ¤tState) != 1) + { + BOX_ERROR("Received invalid state line from daemon"); + return 1; + } + + Command command = Default; + std::string commandName(argv[0]); + + if (commandName == "wait-for-sync") + { + command = WaitForSyncStart; + } + else if (commandName == "wait-for-end") + { + command = WaitForSyncEnd; + } + else if (commandName == "sync-and-wait") + { + command = SyncAndWaitForEnd; + } + + switch (command) + { + case WaitForSyncStart: + case WaitForSyncEnd: + { + // Check that it's in automatic mode, + // because otherwise it'll never start + + if(!autoBackup) + { + BOX_ERROR("Daemon is not in automatic mode, " + "sync will never start!"); + return 1; + } + + } + break; + + case SyncAndWaitForEnd: + { + // send a sync command + commandName = "force-sync"; + std::string cmd = commandName + "\n"; + connection.Write(cmd.c_str(), cmd.size()); + connection.WriteAllBuffered(); + + if (currentState != 0) + { + BOX_INFO("Waiting for current sync/error state " + "to finish..."); + } + } + break; + + default: + { + // Normal case, just send the command given + // plus a quit command. + std::string cmd = commandName; + cmd += "\nquit\n"; + connection.Write(cmd.c_str(), cmd.size()); + } + } + + // Read the response + std::string line; + bool syncIsRunning = false; + bool finished = false; + + while(!finished && !getLine.IsEOF() && getLine.GetLine(line)) + { + switch (command) + { + case WaitForSyncStart: + { + // Need to wait for the state change... + if(line == "start-sync") + { + // Send a quit command to finish nicely + connection.Write("quit\n", 5); + + // And we're done + finished = true; + } + } + break; + + case WaitForSyncEnd: + case SyncAndWaitForEnd: + { + if(line == "start-sync") + { + if (!quiet) BOX_INFO("Sync started..."); + syncIsRunning = true; + } + else if(line == "finish-sync") + { + if (syncIsRunning) + { + if (!quiet) BOX_INFO("Sync finished."); + // Send a quit command to finish nicely + connection.Write("quit\n", 5); + + // And we're done + finished = true; + } + else + { + if (!quiet) BOX_INFO("Previous sync finished."); + } + // daemon must still be busy + } + } + break; + + default: + { + // Is this an OK or error line? + if(line == "ok") + { + if(!quiet) + { + BOX_INFO("Control command " + "sent: " << + commandName); + } + finished = true; + } + else if(line == "error") + { + BOX_ERROR("Control command failed: " << + commandName << ". Check " + "command spelling."); + returnCode = 1; + finished = true; + } + } + } + } + + MAINHELPER_END + +#if defined WIN32 && ! defined BOX_RELEASE_BUILD + closelog(); +#endif + + return returnCode; +} diff --git a/bin/bbackupd/BackupClientContext.cpp b/bin/bbackupd/BackupClientContext.cpp new file mode 100644 index 00000000..6b51b9e8 --- /dev/null +++ b/bin/bbackupd/BackupClientContext.cpp @@ -0,0 +1,578 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientContext.cpp +// Purpose: Keep track of context +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#ifdef HAVE_SYS_TIME_H + #include +#endif + +#include "BoxPortsAndFiles.h" +#include "BoxTime.h" +#include "BackupClientContext.h" +#include "SocketStreamTLS.h" +#include "Socket.h" +#include "BackupStoreConstants.h" +#include "BackupStoreException.h" +#include "BackupDaemon.h" +#include "autogen_BackupProtocolClient.h" +#include "BackupStoreFile.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::BackupClientContext(BackupDaemon &, TLSContext &, const std::string &, int32_t, bool, bool, std::string) +// Purpose: Constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientContext::BackupClientContext +( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier& rProgressNotifier +) + : 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) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::~BackupClientContext() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientContext::~BackupClientContext() +{ + CloseAnyOpenConnection(); + + // Delete delete list + if(mpDeleteList != 0) + { + delete mpDeleteList; + mpDeleteList = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetConnection() +// Purpose: Returns the connection, making the connection and logging into +// the backup store if necessary. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupProtocolClient &BackupClientContext::GetConnection() +{ + // Already got it? Just return it. + if(mpConnection != 0) + { + return *mpConnection; + } + + // Get a socket connection + if(mpSocket == 0) + { + mpSocket = new SocketStreamTLS; + ASSERT(mpSocket != 0); // will have exceptioned if this was a problem + } + + try + { + // Defensive. + if(mpConnection != 0) + { + delete mpConnection; + mpConnection = 0; + } + + // Log intention + BOX_INFO("Opening connection to server '" << + mHostname << "'..."); + + // Connect! + mpSocket->Open(mrTLSContext, Socket::TypeINET, + mHostname.c_str(), mPort); + + // And create a procotol object + mpConnection = new BackupProtocolClient(*mpSocket); + + // Set logging option + mpConnection->SetLogToSysLog(mExtendedLogging); + + if (mExtendedLogToFile) + { + ASSERT(mpExtendedLogFileHandle == NULL); + + mpExtendedLogFileHandle = fopen( + mExtendedLogFile.c_str(), "a+"); + + if (!mpExtendedLogFileHandle) + { + BOX_LOG_SYS_ERROR("Failed to open extended " + "log file: " << mExtendedLogFile); + } + else + { + mpConnection->SetLogToFile(mpExtendedLogFileHandle); + } + } + + // Handshake + mpConnection->Handshake(); + + // Check the version of the server + { + std::auto_ptr serverVersion(mpConnection->QueryVersion(BACKUP_STORE_SERVER_VERSION)); + if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION) + { + THROW_EXCEPTION(BackupStoreException, WrongServerVersion) + } + } + + // Login -- if this fails, the Protocol will exception + std::auto_ptr loginConf(mpConnection->QueryLogin(mAccountNumber, 0 /* read/write */)); + + // Check that the client store marker is the one we expect + if(mClientStoreMarker != ClientStoreMarker_NotKnown) + { + if(loginConf->GetClientStoreMarker() != mClientStoreMarker) + { + // Not good... finish the connection, abort, etc, ignoring errors + try + { + mpConnection->QueryFinished(); + mpSocket->Shutdown(); + mpSocket->Close(); + } + catch(...) + { + // IGNORE + } + + // Then throw an exception about this + THROW_EXCEPTION(BackupStoreException, ClientMarkerNotAsExpected) + } + } + + // Log success + BOX_INFO("Connection made, login successful"); + + // Check to see if there is any space available on the server + if(loginConf->GetBlocksUsed() >= loginConf->GetBlocksHardLimit()) + { + // no -- flag so only things like deletions happen + mStorageLimitExceeded = true; + // Log + BOX_WARNING("Exceeded storage hard-limit on server, " + "not uploading changes to files"); + } + } + catch(...) + { + // Clean up. + delete mpConnection; + mpConnection = 0; + delete mpSocket; + mpSocket = 0; + throw; + } + + return *mpConnection; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::CloseAnyOpenConnection() +// Purpose: Closes a connection, if it's open +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupClientContext::CloseAnyOpenConnection() +{ + if(mpConnection) + { + try + { + // Need to set a client store marker? + if(mClientStoreMarker == ClientStoreMarker_NotKnown) + { + // Yes, choose one, the current time will do + box_time_t marker = GetCurrentBoxTime(); + + // Set it on the store + mpConnection->QuerySetClientStoreMarker(marker); + + // Record it so that it can be picked up later. + mClientStoreMarker = marker; + } + + // Quit nicely + mpConnection->QueryFinished(); + } + catch(...) + { + // Ignore errors here + } + + // Delete it anyway. + delete mpConnection; + mpConnection = 0; + } + + if(mpSocket) + { + try + { + // Be nice about closing the socket + mpSocket->Shutdown(); + mpSocket->Close(); + } + catch(...) + { + // Ignore errors + } + + // Delete object + delete mpSocket; + mpSocket = 0; + } + + // Delete any pending list + if(mpDeleteList != 0) + { + delete mpDeleteList; + mpDeleteList = 0; + } + + if (mpExtendedLogFileHandle != NULL) + { + fclose(mpExtendedLogFileHandle); + mpExtendedLogFileHandle = NULL; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetTimeout() +// Purpose: Gets the current timeout time. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +int BackupClientContext::GetTimeout() const +{ + if(mpConnection) + { + return mpConnection->GetTimeout(); + } + + return (15*60*1000); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetDeleteList() +// Purpose: Returns the delete list, creating one if necessary +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList &BackupClientContext::GetDeleteList() +{ + // Already created? + if(mpDeleteList == 0) + { + mpDeleteList = new BackupClientDeleteList; + } + + // Return reference to object + return *mpDeleteList; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::PerformDeletions() +// Purpose: Perform any pending file deletions. +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientContext::PerformDeletions() +{ + // Got a list? + if(mpDeleteList == 0) + { + // Nothing to do + return; + } + + // Delegate to the delete list object + mpDeleteList->PerformDeletions(*this); + + // Delete the object + delete mpDeleteList; + mpDeleteList = 0; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetCurrentIDMap() const +// Purpose: Return a (const) reference to the current ID map +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +const BackupClientInodeToIDMap &BackupClientContext::GetCurrentIDMap() const +{ + ASSERT(mpCurrentIDMap != 0); + if(mpCurrentIDMap == 0) + { + THROW_EXCEPTION(CommonException, Internal) + } + return *mpCurrentIDMap; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetNewIDMap() const +// Purpose: Return a reference to the new ID map +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap &BackupClientContext::GetNewIDMap() const +{ + ASSERT(mpNewIDMap != 0); + if(mpNewIDMap == 0) + { + THROW_EXCEPTION(CommonException, Internal) + } + return *mpNewIDMap; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::FindFilename(int64_t, int64_t, std::string &, bool &) const +// Purpose: Attempts to find the pathname of an object with a given ID on the server. +// Returns true if it can be found, in which case rPathOut is the local filename, +// and rIsDirectoryOut == true if the local object is a directory. +// Created: 12/11/03 +// +// -------------------------------------------------------------------------- +bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, + bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer, box_time_t *pAttributesHashOnServer, BackupStoreFilenameClear *pLeafname) +{ + // Make a connection to the server + BackupProtocolClient &connection(GetConnection()); + + // Request filenames from the server, in a "safe" manner to ignore errors properly + { + BackupProtocolClientGetObjectName send(ObjectID, ContainingDirectory); + connection.Send(send); + } + std::auto_ptr preply(connection.Receive()); + + // Is it of the right type? + if(preply->GetType() != BackupProtocolClientObjectName::TypeID) + { + // Was an error or something + return false; + } + + // Cast to expected type. + BackupProtocolClientObjectName *names = (BackupProtocolClientObjectName *)(preply.get()); + + // Anything found? + int32_t numElements = names->GetNumNameElements(); + if(numElements <= 0) + { + // No. + return false; + } + + // Get the stream containing all the names + std::auto_ptr nameStream(connection.ReceiveStream()); + + // Path + std::string path; + + // Remember this is in reverse order! + for(int l = 0; l < numElements; ++l) + { + BackupStoreFilenameClear elementName; + elementName.ReadFromStream(*nameStream, GetTimeout()); + + // Store leafname for caller? + if(l == 0 && pLeafname) + { + *pLeafname = elementName; + } + + // Is it part of the filename in the location? + if(l < (numElements - 1)) + { + // Part of filename within + path = (path.empty())?(elementName.GetClearFilename()):(elementName.GetClearFilename() + DIRECTORY_SEPARATOR_ASCHAR + path); + } + else + { + // Location name -- look up in daemon's records + std::string locPath; + if(!mrResolver.FindLocationPathName(elementName.GetClearFilename(), locPath)) + { + // Didn't find the location... so can't give the local filename + return false; + } + + // Add in location path + path = (path.empty())?(locPath):(locPath + DIRECTORY_SEPARATOR_ASCHAR + path); + } + } + + // Is it a directory? + rIsDirectoryOut = ((names->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir) == BackupProtocolClientListDirectory::Flags_Dir); + + // Is it the current version? + rIsCurrentVersionOut = ((names->GetFlags() & (BackupProtocolClientListDirectory::Flags_OldVersion | BackupProtocolClientListDirectory::Flags_Deleted)) == 0); + + // And other information which may be required + if(pModTimeOnServer) *pModTimeOnServer = names->GetModificationTime(); + if(pAttributesHashOnServer) *pAttributesHashOnServer = names->GetAttributesHash(); + + // Tell caller about the pathname + rPathOut = path; + + // Found + return true; +} + +void BackupClientContext::SetMaximumDiffingTime(int iSeconds) +{ + mMaximumDiffingTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set maximum diffing time to " << mMaximumDiffingTime << + " seconds"); +} + +void BackupClientContext::SetKeepAliveTime(int iSeconds) +{ + mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds"); + mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::ManageDiffProcess() +// Purpose: Initiates a file diff control timer +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::ManageDiffProcess() +{ + ASSERT(!mbIsManaged); + mbIsManaged = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::UnManageDiffProcess() +// Purpose: suspends file diff control timer +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::UnManageDiffProcess() +{ + // ASSERT(mbIsManaged); + mbIsManaged = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::DoKeepAlive() +// Purpose: Check whether it's time to send a KeepAlive +// message over the SSL link, and if so, send it. +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::DoKeepAlive() +{ + if (!mpConnection) + { + return; + } + + if (mKeepAliveTime == 0) + { + return; + } + + if (!mKeepAliveTimer.HasExpired()) + { + return; + } + + BOX_TRACE("KeepAliveTime reached, sending keep-alive message"); + mpConnection->QueryGetIsAlive(); + + mKeepAliveTimer = Timer(mKeepAliveTime, "KeepAliveTime"); +} + +int BackupClientContext::GetMaximumDiffingTime() +{ + return mMaximumDiffingTime; +} diff --git a/bin/bbackupd/BackupClientContext.h b/bin/bbackupd/BackupClientContext.h new file mode 100644 index 00000000..404d2d77 --- /dev/null +++ b/bin/bbackupd/BackupClientContext.h @@ -0,0 +1,237 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientContext.h +// Purpose: Keep track of context +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTCONTEXT__H +#define BACKUPCLIENTCONTEXT__H + +#include "BoxTime.h" +#include "BackupClientDeleteList.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupDaemonInterface.h" +#include "BackupStoreFile.h" +#include "ExcludeList.h" +#include "Timer.h" + +class TLSContext; +class BackupProtocolClient; +class SocketStreamTLS; +class BackupClientInodeToIDMap; +class BackupDaemon; +class BackupStoreFilenameClear; + +#include + + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientContext +// Purpose: +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupClientContext : public DiffTimer +{ +public: + BackupClientContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier + ); + virtual ~BackupClientContext(); +private: + BackupClientContext(const BackupClientContext &); +public: + + BackupProtocolClient &GetConnection(); + + void CloseAnyOpenConnection(); + + int GetTimeout() const; + + BackupClientDeleteList &GetDeleteList(); + void PerformDeletions(); + + enum + { + ClientStoreMarker_NotKnown = 0 + }; + + void SetClientStoreMarker(int64_t ClientStoreMarker) {mClientStoreMarker = ClientStoreMarker;} + int64_t GetClientStoreMarker() const {return mClientStoreMarker;} + + bool StorageLimitExceeded() {return mStorageLimitExceeded;} + void SetStorageLimitExceeded() {mStorageLimitExceeded = true;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetIDMaps(const BackupClientInodeToIDMap *, BackupClientInodeToIDMap *) + // Purpose: Store pointers to the Current and New ID maps + // Created: 11/11/03 + // + // -------------------------------------------------------------------------- + void SetIDMaps(const BackupClientInodeToIDMap *pCurrent, BackupClientInodeToIDMap *pNew) + { + ASSERT(pCurrent != 0); + ASSERT(pNew != 0); + mpCurrentIDMap = pCurrent; + mpNewIDMap = pNew; + } + const BackupClientInodeToIDMap &GetCurrentIDMap() const; + BackupClientInodeToIDMap &GetNewIDMap() const; + + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetExcludeLists(ExcludeList *, ExcludeList *) + // Purpose: Sets the exclude lists for the operation. Can be 0. + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + void SetExcludeLists(ExcludeList *pExcludeFiles, ExcludeList *pExcludeDirs) + { + mpExcludeFiles = pExcludeFiles; + mpExcludeDirs = pExcludeDirs; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ExcludeFile(const std::string &) + // Purpose: Returns true is this file should be excluded from the backup + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + inline bool ExcludeFile(const std::string &rFullFilename) + { + if(mpExcludeFiles != 0) + { + return mpExcludeFiles->IsExcluded(rFullFilename); + } + // If no list, don't exclude anything + return false; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ExcludeDir(const std::string &) + // Purpose: Returns true is this directory should be excluded from the backup + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + inline bool ExcludeDir(const std::string &rFullDirName) + { + if(mpExcludeDirs != 0) + { + return mpExcludeDirs->IsExcluded(rFullDirName); + } + // If no list, don't exclude anything + return false; + } + + // Utility functions -- may do a lot of work + bool FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, + bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer = 0, box_time_t *pAttributesHashOnServer = 0, + BackupStoreFilenameClear *pLeafname = 0); // not const as may connect to server + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetMaximumDiffingTime() + // Purpose: Sets the maximum time that will be spent diffing a file + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void SetMaximumDiffingTime(int iSeconds); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetKeepAliveTime() + // Purpose: Sets the time interval for repetitive keep-alive operation + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void SetKeepAliveTime(int iSeconds); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ManageDiffProcess() + // Purpose: Initiates an SSL connection/session keep-alive process + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void ManageDiffProcess(); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::UnManageDiffProcess() + // Purpose: Suspends an SSL connection/session keep-alive process + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void UnManageDiffProcess(); + + // ------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::DoKeepAlive() + // Purpose: Check whether it's time to send a KeepAlive + // message over the SSL link, and if so, send it. + // Created: 04/19/2005 + // + // ------------------------------------------------------------------- + virtual void DoKeepAlive(); + virtual int GetMaximumDiffingTime(); + virtual bool IsManaged() { return mbIsManaged; } + + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } + +private: + LocationResolver &mrResolver; + TLSContext &mrTLSContext; + std::string mHostname; + int mPort; + uint32_t mAccountNumber; + SocketStreamTLS *mpSocket; + BackupProtocolClient *mpConnection; + bool mExtendedLogging; + bool mExtendedLogToFile; + std::string mExtendedLogFile; + FILE* mpExtendedLogFileHandle; + int64_t mClientStoreMarker; + BackupClientDeleteList *mpDeleteList; + const BackupClientInodeToIDMap *mpCurrentIDMap; + BackupClientInodeToIDMap *mpNewIDMap; + bool mStorageLimitExceeded; + ExcludeList *mpExcludeFiles; + ExcludeList *mpExcludeDirs; + Timer mKeepAliveTimer; + bool mbIsManaged; + int mKeepAliveTime; + int mMaximumDiffingTime; + ProgressNotifier &mrProgressNotifier; +}; + +#endif // BACKUPCLIENTCONTEXT__H diff --git a/bin/bbackupd/BackupClientDeleteList.cpp b/bin/bbackupd/BackupClientDeleteList.cpp new file mode 100644 index 00000000..b9b5b53e --- /dev/null +++ b/bin/bbackupd/BackupClientDeleteList.cpp @@ -0,0 +1,229 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDeleteList.cpp +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "BackupClientDeleteList.h" +#include "BackupClientContext.h" +#include "autogen_BackupProtocolClient.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::BackupClientDeleteList() +// Purpose: Constructor +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList::BackupClientDeleteList() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::~BackupClientDeleteList() +// Purpose: Destructor +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList::~BackupClientDeleteList() +{ +} + +BackupClientDeleteList::FileToDelete::FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath) +: mDirectoryID(DirectoryID), + mFilename(rFilename), + mLocalPath(rLocalPath) +{ } + +BackupClientDeleteList::DirToDelete::DirToDelete(int64_t ObjectID, + const std::string& rLocalPath) +: mObjectID(ObjectID), + mLocalPath(rLocalPath) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::AddDirectoryDelete(int64_t, +// const BackupStoreFilename&) +// Purpose: Add a directory to the list of directories to be deleted. +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath) +{ + // Only add the delete to the list if it's not in the "no delete" set + if(mDirectoryNoDeleteList.find(ObjectID) == + mDirectoryNoDeleteList.end()) + { + // Not in the list, so should delete it + mDirectoryList.push_back(DirToDelete(ObjectID, rLocalPath)); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::AddFileDelete(int64_t, +// const BackupStoreFilename &) +// Purpose: +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, const std::string& rLocalPath) +{ + // Try to find it in the no delete list + std::vector >::iterator + delEntry(mFileNoDeleteList.begin()); + while(delEntry != mFileNoDeleteList.end()) + { + if((delEntry)->first == DirectoryID + && (delEntry)->second == rFilename) + { + // Found! + break; + } + ++delEntry; + } + + // Only add it to the delete list if it wasn't in the no delete list + if(delEntry == mFileNoDeleteList.end()) + { + mFileList.push_back(FileToDelete(DirectoryID, rFilename, + rLocalPath)); + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) +// Purpose: Perform all the pending deletes +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) +{ + // Anything to do? + if(mDirectoryList.empty() && mFileList.empty()) + { + // Nothing! + return; + } + + // Get a connection + BackupProtocolClient &connection(rContext.GetConnection()); + + // Do the deletes + for(std::vector::iterator i(mDirectoryList.begin()); + i != mDirectoryList.end(); ++i) + { + connection.QueryDeleteDirectory(i->mObjectID); + rContext.GetProgressNotifier().NotifyDirectoryDeleted( + i->mObjectID, i->mLocalPath); + } + + // Clear the directory list + mDirectoryList.clear(); + + // Delete the files + for(std::vector::iterator i(mFileList.begin()); + i != mFileList.end(); ++i) + { + connection.QueryDeleteFile(i->mDirectoryID, i->mFilename); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->mDirectoryID, i->mLocalPath); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::StopDirectoryDeletion(int64_t) +// Purpose: Stop a directory being deleted +// Created: 19/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) +{ + // First of all, is it in the delete vector? + std::vector::iterator delEntry(mDirectoryList.begin()); + for(; delEntry != mDirectoryList.end(); delEntry++) + { + if(delEntry->mObjectID == ObjectID) + { + // Found! + break; + } + } + if(delEntry != mDirectoryList.end()) + { + // erase this entry + mDirectoryList.erase(delEntry); + } + else + { + // Haven't been asked to delete it yet, put it in the + // no delete list + mDirectoryNoDeleteList.insert(ObjectID); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::StopFileDeletion(int64_t, const BackupStoreFilename &) +// Purpose: Stop a file from being deleted +// Created: 19/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename) +{ + // Find this in the delete list + std::vector::iterator delEntry(mFileList.begin()); + while(delEntry != mFileList.end()) + { + if(delEntry->mDirectoryID == DirectoryID + && delEntry->mFilename == rFilename) + { + // Found! + break; + } + ++delEntry; + } + + if(delEntry != mFileList.end()) + { + // erase this entry + mFileList.erase(delEntry); + } + else + { + // Haven't been asked to delete it yet, put it in the no delete list + mFileNoDeleteList.push_back(std::pair(DirectoryID, rFilename)); + } +} + diff --git a/bin/bbackupd/BackupClientDeleteList.h b/bin/bbackupd/BackupClientDeleteList.h new file mode 100644 index 00000000..b0fbf51a --- /dev/null +++ b/bin/bbackupd/BackupClientDeleteList.h @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDeleteList.h +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTDELETELIST__H +#define BACKUPCLIENTDELETELIST__H + +#include "BackupStoreFilename.h" + +class BackupClientContext; + +#include +#include +#include + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientDeleteList +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +class BackupClientDeleteList +{ +private: + class FileToDelete + { + public: + int64_t mDirectoryID; + BackupStoreFilename mFilename; + std::string mLocalPath; + FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath); + }; + + class DirToDelete + { + public: + int64_t mObjectID; + std::string mLocalPath; + DirToDelete(int64_t ObjectID, const std::string& rLocalPath); + }; + +public: + BackupClientDeleteList(); + ~BackupClientDeleteList(); + + void AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath); + void AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, + const std::string& rLocalPath); + + void StopDirectoryDeletion(int64_t ObjectID); + void StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename); + + void PerformDeletions(BackupClientContext &rContext); + +private: + std::vector mDirectoryList; + std::set mDirectoryNoDeleteList; // note: things only get in this list if they're not present in mDirectoryList when they are 'added' + std::vector mFileList; + std::vector > mFileNoDeleteList; +}; + +#endif // BACKUPCLIENTDELETELIST__H + diff --git a/bin/bbackupd/BackupClientDirectoryRecord.cpp b/bin/bbackupd/BackupClientDirectoryRecord.cpp new file mode 100644 index 00000000..84c17dab --- /dev/null +++ b/bin/bbackupd/BackupClientDirectoryRecord.cpp @@ -0,0 +1,1876 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDirectoryRecord.cpp +// Purpose: Implementation of record about directory for +// backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_DIRENT_H + #include +#endif + +#include +#include + +#include "BackupClientDirectoryRecord.h" +#include "autogen_BackupProtocolClient.h" +#include "BackupClientContext.h" +#include "IOStream.h" +#include "MemBlockStream.h" +#include "CommonException.h" +#include "CollectInBufferStream.h" +#include "BackupStoreFile.h" +#include "BackupClientInodeToIDMap.h" +#include "FileModificationTime.h" +#include "BackupDaemon.h" +#include "BackupStoreException.h" +#include "Archive.h" +#include "PathUtils.h" +#include "Logging.h" +#include "ReadLoggingStream.h" + +#include "MemLeakFindOn.h" + +typedef std::map DecryptedEntriesMap_t; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::BackupClientDirectoryRecord() +// Purpose: Constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName) + : mObjectID(ObjectID), + mSubDirName(rSubDirName), + mInitialSyncDone(false), + mSyncDone(false), + mpPendingEntries(0) +{ + ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::~BackupClientDirectoryRecord() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::~BackupClientDirectoryRecord() +{ + // Make deletion recursive + DeleteSubDirectories(); + + // Delete maps + if(mpPendingEntries != 0) + { + delete mpPendingEntries; + mpPendingEntries = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::DeleteSubDirectories(); +// Purpose: Delete all sub directory entries +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::DeleteSubDirectories() +{ + // Delete all pointers + for(std::map::iterator i = mSubDirectories.begin(); + i != mSubDirectories.end(); ++i) + { + delete i->second; + } + + // Empty list + mSubDirectories.clear(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncDirectory(i +// BackupClientDirectoryRecord::SyncParams &, +// int64_t, const std::string &, +// const std::string &, bool) +// Purpose: Recursively synchronise a local directory +// with the server. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::SyncDirectory( + BackupClientDirectoryRecord::SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, + bool ThisDirHasJustBeenCreated) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + // Signal received by daemon? + if(rParams.mrRunStatusProvider.StopRun()) + { + // Yes. Stop now. + THROW_EXCEPTION(BackupStoreException, SignalReceived) + } + + // Start by making some flag changes, marking this sync as not done, + // and on the immediate sub directories. + mSyncDone = false; + for(std::map::iterator + i = mSubDirectories.begin(); + i != mSubDirectories.end(); ++i) + { + i->second->mSyncDone = false; + } + + // Work out the time in the future after which the file should + // be uploaded regardless. This is a simple way to avoid having + // too many problems with file servers when they have clients + // with badly out of sync clocks. + rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() + + rParams.mMaxFileTimeInFuture; + + // Build the current state checksum to compare against while + // getting info from dirs. Note checksum is used locally only, + // so byte order isn't considered. + MD5Digest currentStateChecksum; + + EMU_STRUCT_STAT dest_st; + // Stat the directory, to get attribute info + // If it's a symbolic link, we want the link target here + // (as we're about to back up the contents of the directory) + { + if(EMU_STAT(rLocalPath.c_str(), &dest_st) != 0) + { + // The directory has probably been deleted, so + // just ignore this error. In a future scan, this + // deletion will be noticed, deleted from server, + // and this object deleted. + rNotifier.NotifyDirStatFailed(this, rLocalPath, + strerror(errno)); + return; + } + // Store inode number in map so directories are tracked + // in case they're renamed + { + BackupClientInodeToIDMap &idMap( + rParams.mrContext.GetNewIDMap()); + idMap.AddToMap(dest_st.st_ino, mObjectID, + ContainingDirectoryID); + } + // Add attributes to checksum + currentStateChecksum.Add(&dest_st.st_mode, + sizeof(dest_st.st_mode)); + currentStateChecksum.Add(&dest_st.st_uid, + sizeof(dest_st.st_uid)); + currentStateChecksum.Add(&dest_st.st_gid, + sizeof(dest_st.st_gid)); + // Inode to be paranoid about things moving around + currentStateChecksum.Add(&dest_st.st_ino, + sizeof(dest_st.st_ino)); +#ifdef HAVE_STRUCT_STAT_ST_FLAGS + currentStateChecksum.Add(&dest_st.st_flags, + sizeof(dest_st.st_flags)); +#endif + + StreamableMemBlock xattr; + BackupClientFileAttributes::FillExtendedAttr(xattr, + rLocalPath.c_str()); + currentStateChecksum.Add(xattr.GetBuffer(), xattr.GetSize()); + } + + // Read directory entries, building arrays of names + // First, need to read the contents of the directory. + std::vector dirs; + std::vector files; + bool downloadDirectoryRecordBecauseOfFutureFiles = false; + + EMU_STRUCT_STAT link_st; + if(EMU_LSTAT(rLocalPath.c_str(), &link_st) != 0) + { + // Report the error (logs and + // eventual email to administrator) + rNotifier.NotifyFileStatFailed(this, rLocalPath, + strerror(errno)); + + // FIXME move to NotifyFileStatFailed() + SetErrorWhenReadingFilesystemObject(rParams, + rLocalPath.c_str()); + + // This shouldn't happen, so we'd better not continue + THROW_EXCEPTION(CommonException, OSFileError) + } + + // BLOCK + { + // read the contents... + DIR *dirHandle = 0; + try + { + rNotifier.NotifyScanDirectory(this, rLocalPath); + + dirHandle = ::opendir(rLocalPath.c_str()); + if(dirHandle == 0) + { + // Report the error (logs and + // eventual email to administrator) + if (errno == EACCES) + { + rNotifier.NotifyDirListFailed(this, + rLocalPath, "Access denied"); + } + else + { + rNotifier.NotifyDirListFailed(this, + rLocalPath, strerror(errno)); + } + + // Report the error (logs and eventual email + // to administrator) + SetErrorWhenReadingFilesystemObject(rParams, + rLocalPath.c_str()); + // Ignore this directory for now. + return; + } + + // Basic structure for checksum info + struct { + box_time_t mModificationTime; + box_time_t mAttributeModificationTime; + int64_t mSize; + // And then the name follows + } checksum_info; + // Be paranoid about structure packing + ::memset(&checksum_info, 0, sizeof(checksum_info)); + + struct dirent *en = 0; + EMU_STRUCT_STAT file_st; + std::string filename; + while((en = ::readdir(dirHandle)) != 0) + { + rParams.mrContext.DoKeepAlive(); + + // Don't need to use + // LinuxWorkaround_FinishDirentStruct(en, + // rLocalPath.c_str()); + // on Linux, as a stat is performed to + // get all this info + + if(en->d_name[0] == '.' && + (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) + { + // ignore, it's . or .. + continue; + } + + // Stat file to get info + filename = MakeFullPath(rLocalPath, en->d_name); + + #ifdef WIN32 + // Don't stat the file just yet, to ensure + // that users can exclude unreadable files + // to suppress warnings that they are + // not accessible. + // + // 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 + if(EMU_LSTAT(filename.c_str(), &file_st) != 0) + { + if(!(rParams.mrContext.ExcludeDir( + filename))) + { + // Report the error (logs and + // eventual email to + // administrator) + rNotifier.NotifyFileStatFailed( + this, filename, + strerror(errno)); + + // FIXME move to + // NotifyFileStatFailed() + SetErrorWhenReadingFilesystemObject( + rParams, filename.c_str()); + } + + // Ignore this entry for now. + continue; + } + + if(file_st.st_dev != dest_st.st_dev) + { + if(!(rParams.mrContext.ExcludeDir( + filename))) + { + rNotifier.NotifyMountPointSkipped( + this, filename); + } + continue; + } + + int type = file_st.st_mode & S_IFMT; + #endif + + if(type == S_IFREG || type == S_IFLNK) + { + // File or symbolic link + + // Exclude it? + if(rParams.mrContext.ExcludeFile(filename)) + { + rNotifier.NotifyFileExcluded( + this, + filename); + + // Next item! + continue; + } + + // Store on list + files.push_back(std::string(en->d_name)); + } + else if(type == S_IFDIR) + { + // Directory + + // Exclude it? + if(rParams.mrContext.ExcludeDir(filename)) + { + rNotifier.NotifyDirExcluded( + this, + filename); + + // Next item! + continue; + } + + // Store on list + dirs.push_back(std::string(en->d_name)); + } + else + { + if (type == S_IFSOCK || type == S_IFIFO) + { + // removed notification for these types + // see Debian bug 479145, no objections + } + else if(rParams.mrContext.ExcludeFile(filename)) + { + rNotifier.NotifyFileExcluded( + this, + filename); + } + else + { + rNotifier.NotifyUnsupportedFileType( + this, filename); + SetErrorWhenReadingFilesystemObject( + rParams, filename.c_str()); + } + + continue; + } + + // Here if the object is something to back up (file, symlink or dir, not excluded) + // So make the information for adding to the checksum + + #ifdef WIN32 + // We didn't stat the file before, + // but now we need the information. + if(emu_stat(filename.c_str(), &file_st) != 0) + { + rNotifier.NotifyFileStatFailed(this, + filename, + strerror(errno)); + + // Report the error (logs and + // eventual email to administrator) + SetErrorWhenReadingFilesystemObject( + rParams, filename.c_str()); + + // Ignore this entry for now. + continue; + } + + if(file_st.st_dev != link_st.st_dev) + { + rNotifier.NotifyMountPointSkipped(this, + filename); + continue; + } + #endif + + checksum_info.mModificationTime = FileModificationTime(file_st); + checksum_info.mAttributeModificationTime = FileAttrModificationTime(file_st); + checksum_info.mSize = file_st.st_size; + currentStateChecksum.Add(&checksum_info, sizeof(checksum_info)); + currentStateChecksum.Add(en->d_name, strlen(en->d_name)); + + // If the file has been modified madly into the future, download the + // directory record anyway to ensure that it doesn't get uploaded + // every single time the disc is scanned. + if(checksum_info.mModificationTime > rParams.mUploadAfterThisTimeInTheFuture) + { + downloadDirectoryRecordBecauseOfFutureFiles = true; + // Log that this has happened + if(!rParams.mHaveLoggedWarningAboutFutureFileTimes) + { + rNotifier.NotifyFileModifiedInFuture( + this, filename); + rParams.mHaveLoggedWarningAboutFutureFileTimes = true; + } + } + } + + if(::closedir(dirHandle) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + dirHandle = 0; + } + catch(...) + { + if(dirHandle != 0) + { + ::closedir(dirHandle); + } + throw; + } + } + + // Finish off the checksum, and compare with the one currently stored + bool checksumDifferent = true; + currentStateChecksum.Finish(); + if(mInitialSyncDone && currentStateChecksum.DigestMatches(mStateChecksum)) + { + // The checksum is the same, and there was one to compare with + checksumDifferent = false; + } + + // Pointer to potentially downloaded store directory info + BackupStoreDirectory *pdirOnStore = 0; + + try + { + // Want to get the directory listing? + if(ThisDirHasJustBeenCreated) + { + // Avoid sending another command to the server when we know it's empty + pdirOnStore = new BackupStoreDirectory(mObjectID, ContainingDirectoryID); + } + else + { + // Consider asking the store for it + if(!mInitialSyncDone || checksumDifferent || downloadDirectoryRecordBecauseOfFutureFiles) + { + pdirOnStore = FetchDirectoryListing(rParams); + } + } + + // Make sure the attributes are up to date -- if there's space on the server + // and this directory has not just been created (because it's attributes will be correct in this case) + // and the checksum is different, implying they *MIGHT* be different. + if((!ThisDirHasJustBeenCreated) && checksumDifferent && (!rParams.mrContext.StorageLimitExceeded())) + { + UpdateAttributes(rParams, pdirOnStore, rLocalPath); + } + + // Create the list of pointers to directory entries + std::vector entriesLeftOver; + if(pdirOnStore) + { + entriesLeftOver.resize(pdirOnStore->GetNumberOfEntries(), 0); + BackupStoreDirectory::Iterator i(*pdirOnStore); + // Copy in pointers to all the entries + for(unsigned int l = 0; l < pdirOnStore->GetNumberOfEntries(); ++l) + { + entriesLeftOver[l] = i.Next(); + } + } + + // Do the directory reading + bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath, + rRemotePath, pdirOnStore, entriesLeftOver, files, dirs); + + // LAST THING! (think exception safety) + // Store the new checksum -- don't fetch things unnecessarily in the future + // But... only if 1) the storage limit isn't exceeded -- make sure things are done again if + // the directory is modified later + // and 2) All the objects within the directory were stored successfully. + if(!rParams.mrContext.StorageLimitExceeded() && updateCompleteSuccess) + { + currentStateChecksum.CopyDigestTo(mStateChecksum); + } + } + catch(...) + { + // Bad things have happened -- clean up + if(pdirOnStore != 0) + { + delete pdirOnStore; + pdirOnStore = 0; + } + + // Set things so that we get a full go at stuff later + ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); + + throw; + } + + // Clean up directory on store + if(pdirOnStore != 0) + { + delete pdirOnStore; + pdirOnStore = 0; + } + + // Flag things as having happened. + mInitialSyncDone = true; + mSyncDone = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::FetchDirectoryListing(BackupClientDirectoryRecord::SyncParams &) +// Purpose: Fetch the directory listing of this directory from the store. +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +BackupStoreDirectory *BackupClientDirectoryRecord::FetchDirectoryListing(BackupClientDirectoryRecord::SyncParams &rParams) +{ + BackupStoreDirectory *pdir = 0; + + try + { + // Get connection to store + BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + + // Query the directory + std::auto_ptr dirreply(connection.QueryListDirectory( + mObjectID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, // both files and directories + BackupProtocolClientListDirectory::Flags_Deleted | BackupProtocolClientListDirectory::Flags_OldVersion, // exclude old/deleted stuff + true /* want attributes */)); + + // Retrieve the directory from the stream following + pdir = new BackupStoreDirectory; + ASSERT(pdir != 0); + std::auto_ptr dirstream(connection.ReceiveStream()); + pdir->ReadFromStream(*dirstream, connection.GetTimeout()); + } + catch(...) + { + delete pdir; + pdir = 0; + throw; + } + + return pdir; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord::SyncParams &, const std::string &) +// Purpose: Sets the attributes of the directory on the store, if necessary +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::UpdateAttributes(BackupClientDirectoryRecord::SyncParams &rParams, BackupStoreDirectory *pDirOnStore, const std::string &rLocalPath) +{ + // Get attributes for the directory + BackupClientFileAttributes attr; + box_time_t attrModTime = 0; + attr.ReadAttributes(rLocalPath.c_str(), true /* directories have zero mod times */, + 0 /* no modification time */, &attrModTime); + + // Assume attributes need updating, unless proved otherwise + bool updateAttr = true; + + // Got a listing to compare with? + ASSERT(pDirOnStore == 0 || (pDirOnStore != 0 && pDirOnStore->HasAttributes())); + if(pDirOnStore != 0 && pDirOnStore->HasAttributes()) + { + const StreamableMemBlock &storeAttrEnc(pDirOnStore->GetAttributes()); + // Explict decryption + BackupClientFileAttributes storeAttr(storeAttrEnc); + + // Compare the attributes + if(attr.Compare(storeAttr, true, + true /* ignore both modification times */)) + { + // No update necessary + updateAttr = false; + } + } + + // Update them? + if(updateAttr) + { + // Get connection to store + BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + + // Exception thrown if this doesn't work + MemBlockStream attrStream(attr); + connection.QueryChangeDirAttributes(mObjectID, attrModTime, attrStream); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncParams &, const std::string &, BackupStoreDirectory *, std::vector &) +// Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space. +// Returns true if all items were updated successfully. (If not, the failures will have been logged). +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +bool BackupClientDirectoryRecord::UpdateItems( + BackupClientDirectoryRecord::SyncParams &rParams, + const std::string &rLocalPath, + const std::string &rRemotePath, + BackupStoreDirectory *pDirOnStore, + std::vector &rEntriesLeftOver, + std::vector &rFiles, + const std::vector &rDirs) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + bool allUpdatedSuccessfully = true; + + // Decrypt all the directory entries. + // It would be nice to be able to just compare the encrypted versions, however this doesn't work + // in practise because there can be multiple encodings of the same filename using different + // methods (although each method will result in the same string for the same filename.) This + // happens when the server fixes a broken store, and gives plain text generated filenames. + // So if we didn't do things like this, then you wouldn't be able to recover from bad things + // happening with the server. + DecryptedEntriesMap_t decryptedEntries; + if(pDirOnStore != 0) + { + BackupStoreDirectory::Iterator i(*pDirOnStore); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + decryptedEntries[BackupStoreFilenameClear(en->GetName()).GetClearFilename()] = en; + } + } + + // Do files + for(std::vector::const_iterator f = rFiles.begin(); + f != rFiles.end(); ++f) + { + // Send keep-alive message if needed + rContext.DoKeepAlive(); + + // Filename of this file + std::string filename(MakeFullPath(rLocalPath, *f)); + + // 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)); + + // Report the error (logs and + // eventual email to administrator) + SetErrorWhenReadingFilesystemObject(rParams, + filename.c_str()); + + // Ignore this entry for now. + continue; + } + + // Extract required data + modTime = FileModificationTime(st); + fileSize = st.st_size; + inodeNum = st.st_ino; + hasMultipleHardLinks = (st.st_nlink > 1); + attributesHash = BackupClientFileAttributes::GenerateAttributeHash(st, filename, *f); + } + + // See if it's in the listing (if we have one) + BackupStoreFilenameClear storeFilename(*f); + BackupStoreDirectory::Entry *en = 0; + int64_t latestObjectID = 0; + if(pDirOnStore != 0) + { + DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*f)); + if(i != decryptedEntries.end()) + { + en = i->second; + latestObjectID = en->GetObjectID(); + } + } + + // Check that the entry which might have been found is in fact a file + if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == 0)) + { + // Directory exists in the place of this file -- sort it out + RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore, + en, *f); + en = 0; + } + + // Check for renaming? + if(pDirOnStore != 0 && en == 0) + { + // We now know... + // 1) File has just been added + // 2) It's not in the store + + // Do we know about the inode number? + const BackupClientInodeToIDMap &idMap(rContext.GetCurrentIDMap()); + int64_t renameObjectID = 0, renameInDirectory = 0; + if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) + { + // Look up on the server to get the name, to build the local filename + std::string localPotentialOldName; + bool isDir = false; + bool isCurrentVersion = false; + box_time_t srvModTime = 0, srvAttributesHash = 0; + BackupStoreFilenameClear 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) + { + // Check that the object we found in the ID map doesn't exist on disc + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) + { + // Doesn't exist locally, but does exist on the server. + // Therefore we can safely rename it to this new file. + + // Get the connection to the server + BackupProtocolClient &connection(rContext.GetConnection()); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is space available + 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, + storeFilename); + + // Stop the attempt to delete the file in the original location + BackupClientDeleteList &rdelList(rContext.GetDeleteList()); + rdelList.StopFileDeletion(renameInDirectory, oldLeafname); + + // 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); + + // Store the object ID for the inode lookup map later + latestObjectID = renameObjectID; + } + } + } + } + } + } + + // Is it in the mPendingEntries list? + box_time_t pendingFirstSeenTime = 0; // ie not seen + if(mpPendingEntries != 0) + { + std::map::const_iterator i(mpPendingEntries->find(*f)); + if(i != mpPendingEntries->end()) + { + // found it -- set flag + pendingFirstSeenTime = i->second; + } + } + + // If pDirOnStore == 0, then this must have been after an initial sync: + ASSERT(pDirOnStore != 0 || mInitialSyncDone); + // So, if pDirOnStore == 0, then we know that everything before syncPeriodStart + // is either on the server, or in the toupload list. If the directory had changed, + // we'd have got a directory listing. + // + // At this point, if (pDirOnStore == 0 && en == 0), we can assume it's on the server with a + // mod time < syncPeriodStart, or didn't exist before that time. + // + // But if en != 0, then we need to compare modification times to avoid uploading it again. + + // Need to update? + // + // Condition for upload: + // modification time within sync period + // if it's been seen before but not uploaded, is the time from this first sight longer than the MaxUploadWait + // 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; + + // Only upload a file if the mod time locally is + // different to that on the server. + + if (en == 0 || en->GetModificationTime() != modTime) + { + // Check the file modified within the acceptable time period we're checking + // If the file isn't on the server, the acceptable time starts at zero. + // Check pDirOnStore and en, because if we didn't download a directory listing, + // pDirOnStore will be zero, but we know it's on the server. + if (modTime < rParams.mSyncPeriodEnd) + { + if (pDirOnStore != 0 && en == 0) + { + doUpload = true; + BOX_TRACE("Upload decision: " << + filename << ": will upload " + "(not on server)"); + } + else if (modTime >= rParams.mSyncPeriodStart) + { + doUpload = true; + BOX_TRACE("Upload decision: " << + filename << ": will upload " + "(modified since last sync)"); + } + } + + // However, just in case things are continually + // modified, we check the first seen time. + // The two compares of syncPeriodEnd and + // pendingFirstSeenTime are because the values + // are unsigned. + + if (!doUpload && + pendingFirstSeenTime != 0 && + rParams.mSyncPeriodEnd > pendingFirstSeenTime && + (rParams.mSyncPeriodEnd - pendingFirstSeenTime) + > rParams.mMaxUploadWait) + { + doUpload = true; + BOX_TRACE("Upload decision: " << + filename << ": will upload " + "(continually modified)"); + } + + // Then make sure that if files are added with a + // time less than the sync period start + // (which can easily happen on file server), it + // gets uploaded. The directory contents checksum + // will pick up the fact it has been added, so the + // store listing will be available when this happens. + + if (!doUpload && + modTime <= rParams.mSyncPeriodStart && + en != 0 && + en->GetModificationTime() != modTime) + { + doUpload = true; + BOX_TRACE("Upload decision: " << + filename << ": will upload " + "(mod time changed)"); + } + + // And just to catch really badly off clocks in + // the future for file server clients, + // just upload the file if it's madly in the future. + + if (!doUpload && modTime > + rParams.mUploadAfterThisTimeInTheFuture) + { + doUpload = true; + BOX_TRACE("Upload decision: " << + filename << ": will upload " + "(mod time in the future)"); + } + } + + if (en != 0 && en->GetModificationTime() == modTime) + { + BOX_TRACE("Upload decision: " << + filename << ": will not upload " + "(not modified since last upload)"); + } + else if (!doUpload) + { + if (modTime > rParams.mSyncPeriodEnd) + { + 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)"); + } + else + { + BOX_TRACE("Upload decision: " << + filename << ": will not upload " + "(mod time is " << modTime << + " which is outside sync window, " + << rParams.mSyncPeriodStart << " to " + << rParams.mSyncPeriodEnd << ")"); + } + } + + bool fileSynced = true; + + if (doUpload) + { + // Upload needed, don't mark sync success until + // we've actually done it + fileSynced = false; + + // Make sure we're connected -- must connect here so we know whether + // the storage limit has been exceeded, and hence whether or not + // to actually upload the file. + rContext.GetConnection(); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is space available + if(!rContext.StorageLimitExceeded()) + { + // Upload the file to the server, recording the + // object ID it returns + bool noPreviousVersionOnServer = + ((pDirOnStore != 0) && (en == 0)); + + // Surround this in a try/catch block, to + // catch errors, but still continue + bool uploadSuccess = false; + try + { + latestObjectID = UploadFile(rParams, + filename, storeFilename, + fileSize, modTime, + attributesHash, + noPreviousVersionOnServer); + + if (latestObjectID == 0) + { + // storage limit exceeded + rParams.mrContext.SetStorageLimitExceeded(); + uploadSuccess = false; + allUpdatedSuccessfully = false; + } + else + { + uploadSuccess = true; + } + } + catch(ConnectionException &e) + { + // Connection errors should just be + // passed on to the main handler, + // retries would probably just cause + // more problems. + rNotifier.NotifyFileUploadException( + this, filename, e); + throw; + } + catch(BoxException &e) + { + if (e.GetType() == BackupStoreException::ExceptionType && + e.GetSubType() == BackupStoreException::SignalReceived) + { + // abort requested, pass the + // exception on up. + throw; + } + + // an error occured -- make return + // code false, to show error in directory + allUpdatedSuccessfully = false; + // Log it. + SetErrorWhenReadingFilesystemObject(rParams, filename.c_str()); + rNotifier.NotifyFileUploadException( + this, filename, e); + } + + // Update structures if the file was uploaded + // successfully. + if(uploadSuccess) + { + fileSynced = true; + + // delete from pending entries + if(pendingFirstSeenTime != 0 && mpPendingEntries != 0) + { + mpPendingEntries->erase(*f); + } + } + } + else + { + rNotifier.NotifyFileSkippedServerFull(this, + filename); + } + } + else if(en != 0 && en->GetAttributesHash() != attributesHash) + { + // Attributes have probably changed, upload them again. + // If the attributes have changed enough, the directory + // hash will have changed too, and so the dir will have + // been downloaded, and the entry will be available. + + // Get connection + BackupProtocolClient &connection(rContext.GetConnection()); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is + // space available + if(!rContext.StorageLimitExceeded()) + { + try + { + rNotifier.NotifyFileUploadingAttributes( + this, filename); + + // Update store + BackupClientFileAttributes attr; + attr.ReadAttributes(filename.c_str(), false /* put mod times in the attributes, please */); + MemBlockStream attrStream(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"); + } + } + } + + if(modTime >= rParams.mSyncPeriodEnd) + { + // Allocate? + if(mpPendingEntries == 0) + { + mpPendingEntries = new std::map; + } + // Adding to mPendingEntries list + if(pendingFirstSeenTime == 0) + { + // Haven't seen this before -- add to list! + (*mpPendingEntries)[*f] = modTime; + } + } + + // Zero pointer in rEntriesLeftOver, if we have a pointer to zero + if(en != 0) + { + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] == en) + { + rEntriesLeftOver[l] = 0; + break; + } + } + } + + // Does this file need an entry in the ID map? + if(fileSize >= rParams.mFileTrackingSizeThreshold) + { + // Get the map + BackupClientInodeToIDMap &idMap(rContext.GetNewIDMap()); + + // Need to get an ID from somewhere... + if(latestObjectID != 0) + { + // Use this one + BOX_TRACE("Storing uploaded file ID " << + inodeNum << " (" << filename << ") " + "in ID map as object " << + latestObjectID << " with parent " << + mObjectID); + idMap.AddToMap(inodeNum, latestObjectID, mObjectID /* containing directory */); + } + else + { + // Don't know it -- haven't sent anything to the store, and didn't get a listing. + // Look it up in the current map, and if it's there, use that. + const BackupClientInodeToIDMap ¤tIDMap(rContext.GetCurrentIDMap()); + int64_t objid = 0, dirid = 0; + if(currentIDMap.Lookup(inodeNum, objid, dirid)) + { + // 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?)"); + } + + ASSERT(dirid == mObjectID); + + // NOTE: If the above assert fails, an inode number has been reused by the OS, + // or there is a problem somewhere. If this happened on a short test run, look + // 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); + idMap.AddToMap(inodeNum, objid, + mObjectID /* containing directory */); + } + } + } + + if (fileSynced) + { + rNotifier.NotifyFileSynchronised(this, filename, + fileSize); + } + } + + // Erase contents of files to save space when recursing + rFiles.clear(); + + // Delete the pending entries, if the map is entry + if(mpPendingEntries != 0 && mpPendingEntries->size() == 0) + { + BOX_TRACE("Deleting mpPendingEntries from dir ID " << + BOX_FORMAT_OBJECTID(mObjectID)); + delete mpPendingEntries; + mpPendingEntries = 0; + } + + // Do directories + for(std::vector::const_iterator d = rDirs.begin(); + d != rDirs.end(); ++d) + { + // Send keep-alive message if needed + rContext.DoKeepAlive(); + + // Get the local filename + std::string dirname(MakeFullPath(rLocalPath, *d)); + + // See if it's in the listing (if we have one) + BackupStoreFilenameClear storeFilename(*d); + BackupStoreDirectory::Entry *en = 0; + if(pDirOnStore != 0) + { + DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*d)); + if(i != decryptedEntries.end()) + { + en = i->second; + } + } + + // Check that the entry which might have been found is in fact a directory + if((en != 0) && ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == 0)) + { + // Entry exists, but is not a directory. Bad. + // Get rid of it. + BackupProtocolClient &connection(rContext.GetConnection()); + connection.QueryDeleteFile(mObjectID /* in directory */, storeFilename); + rNotifier.NotifyFileDeleted(en->GetObjectID(), + storeFilename.GetClearFilename()); + + // Nothing found + en = 0; + } + + // Flag for having created directory, so can optimise the + // recursive call not to read it again, because we know + // it's empty. + bool haveJustCreatedDirOnServer = false; + + // Next, see if it's in the list of sub directories + BackupClientDirectoryRecord *psubDirRecord = 0; + std::map::iterator + e(mSubDirectories.find(*d)); + + if(e != mSubDirectories.end()) + { + // In the list, just use this pointer + psubDirRecord = e->second; + } + else + { + // Note: if we have exceeded our storage limit, then + // we should not upload any more data, nor create any + // DirectoryRecord representing data that would have + // been uploaded. This step will be repeated when + // there is some space available. + bool doCreateDirectoryRecord = true; + + // Need to create the record. But do we need to create the directory on the server? + int64_t subDirObjectID = 0; + if(en != 0) + { + // No. Exists on the server, and we know about it from the listing. + subDirObjectID = en->GetObjectID(); + } + else if(rContext.StorageLimitExceeded()) + // know we've got a connection if we get this far, + // as dir will have been modified. + { + doCreateDirectoryRecord = false; + } + else + { + // Yes, creation required! + // It is known that the it doesn't exist: + // if pDirOnStore == 0, then the directory has had an initial sync, and hasn't been modified. + // so it has definately been created already. + // if en == 0 but pDirOnStore != 0, well... obviously it doesn't exist. + + // Get attributes + box_time_t attrModTime = 0; + InodeRefType inodeNum = 0; + BackupClientFileAttributes attr; + bool failedToReadAttributes = false; + + try + { + attr.ReadAttributes(dirname.c_str(), + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime, 0 /* not file size */, + &inodeNum); + } + catch (BoxException &e) + { + BOX_WARNING("Failed to read attributes " + "of directory, cannot check " + "for rename, assuming new: '" + << dirname << "'"); + failedToReadAttributes = true; + } + + // Check to see if the directory been renamed + // First, do we have a record in the ID map? + int64_t renameObjectID = 0, renameInDirectory = 0; + bool renameDir = false; + const BackupClientInodeToIDMap &idMap( + rContext.GetCurrentIDMap()); + + if(!failedToReadAttributes && idMap.Lookup(inodeNum, + renameObjectID, renameInDirectory)) + { + // Look up on the server to get the name, to build the local filename + std::string localPotentialOldName; + bool isDir = false; + bool isCurrentVersion = false; + if(rContext.FindFilename(renameObjectID, renameInDirectory, localPotentialOldName, isDir, isCurrentVersion)) + { + // Only interested if it's a directory + if(isDir && isCurrentVersion) + { + // Check that the object doesn't exist already + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) + { + // Doesn't exist locally, but does exist on the server. + // Therefore we can safely rename it. + renameDir = true; + } + } + } + } + + // Get connection + BackupProtocolClient &connection(rContext.GetConnection()); + + // Don't do a check for storage limit exceeded here, because if we get to this + // stage, a connection will have been opened, and the status known, so the check + // in the else if(...) above will be correct. + + // Build attribute stream for sending + MemBlockStream attrStream(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, + storeFilename); + + // Put the latest attributes on it + connection.QueryChangeDirAttributes(renameObjectID, attrModTime, attrStream); + + // Stop it being deleted later + BackupClientDeleteList &rdelList( + rContext.GetDeleteList()); + rdelList.StopDirectoryDeletion(renameObjectID); + + // This is the ID for the renamed directory + subDirObjectID = renameObjectID; + } + else + { + // Create a new directory + std::auto_ptr dirCreate(connection.QueryCreateDirectory( + mObjectID, attrModTime, storeFilename, attrStream)); + subDirObjectID = dirCreate->GetObjectID(); + + // Flag as having done this for optimisation later + haveJustCreatedDirOnServer = true; + } + } + + if (doCreateDirectoryRecord) + { + // New an object for this + psubDirRecord = new BackupClientDirectoryRecord(subDirObjectID, *d); + + // Store in list + try + { + mSubDirectories[*d] = psubDirRecord; + } + catch(...) + { + delete psubDirRecord; + psubDirRecord = 0; + throw; + } + } + } + + ASSERT(psubDirRecord != 0 || rContext.StorageLimitExceeded()); + + if(psubDirRecord) + { + // Sync this sub directory too + psubDirRecord->SyncDirectory(rParams, mObjectID, + dirname, rRemotePath + "/" + *d, + haveJustCreatedDirOnServer); + } + + // Zero pointer in rEntriesLeftOver, if we have a pointer to zero + if(en != 0) + { + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] == en) + { + rEntriesLeftOver[l] = 0; + break; + } + } + } + } + + // Delete everything which is on the store, but not on disc + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] != 0) + { + BackupStoreDirectory::Entry *en = rEntriesLeftOver[l]; + + // These entries can't be deleted immediately, as it would prevent + // renaming and moving of objects working properly. So we add them + // to a list, which is actually deleted at the very end of the session. + // 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()); + + BackupStoreFilenameClear clear(en->GetName()); + std::string localName = MakeFullPath(rLocalPath, + clear.GetClearFilename()); + + // Delete this entry -- file or directory? + if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0) + { + // Set a pending deletion for the file + rdel.AddFileDelete(mObjectID, en->GetName(), + localName); + } + else if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) + { + // Set as a pending deletion for the directory + rdel.AddDirectoryDelete(en->GetObjectID(), + localName); + + // If there's a directory record for it in + // the sub directory map, delete it now + BackupStoreFilenameClear dirname(en->GetName()); + std::map::iterator e(mSubDirectories.find(dirname.GetClearFilename())); + if(e != mSubDirectories.end()) + { + // 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); + } + } + } + } + + // Return success flag (will be false if some files failed) + return allUpdatedSuccessfully; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(SyncParams &, BackupStoreDirectory *, int64_t, const std::string &) +// Purpose: Called to resolve difficulties when a directory is found on the +// store where a file is to be uploaded. +// Created: 9/7/04 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile( + SyncParams &rParams, + BackupStoreDirectory* pDirOnStore, + BackupStoreDirectory::Entry* pEntry, + const std::string &rFilename) +{ + // First, delete the directory + BackupProtocolClient &connection(rParams.mrContext.GetConnection()); + connection.QueryDeleteDirectory(pEntry->GetObjectID()); + + BackupStoreFilenameClear clear(pEntry->GetName()); + rParams.mrContext.GetProgressNotifier().NotifyDirectoryDeleted( + pEntry->GetObjectID(), clear.GetClearFilename()); + + // Then, delete any directory record + std::map::iterator + e(mSubDirectories.find(rFilename)); + + if(e != mSubDirectories.end()) + { + // A record exists for this, remove it + BackupClientDirectoryRecord *psubDirRecord = e->second; + mSubDirectories.erase(e); + + // And delete the object + delete psubDirRecord; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UploadFile( +// BackupClientDirectoryRecord::SyncParams &, +// const std::string &, +// const BackupStoreFilename &, +// int64_t, box_time_t, box_time_t, bool) +// Purpose: Private. Upload a file to the server. May send +// a patch instead of the whole thing +// Created: 20/1/04 +// +// -------------------------------------------------------------------------- +int64_t BackupClientDirectoryRecord::UploadFile( + BackupClientDirectoryRecord::SyncParams &rParams, + const std::string &rFilename, + const BackupStoreFilename &rStoreFilename, + int64_t FileSize, + box_time_t ModificationTime, + box_time_t AttributesHash, + bool NoPreviousVersionOnServer) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + // Get the connection + BackupProtocolClient &connection(rContext.GetConnection()); + + // Info + int64_t objID = 0; + bool doNormalUpload = true; + + // Use a try block to catch store full errors + try + { + // Might an old version be on the server, and is the file + // size over the diffing threshold? + if(!NoPreviousVersionOnServer && + FileSize >= rParams.mDiffingUploadSizeThreshold) + { + // YES -- try to do diff, if possible + // First, query the server to see if there's an old version available + std::auto_ptr getBlockIndex(connection.QueryGetBlockIndexByName(mObjectID, rStoreFilename)); + int64_t diffFromID = getBlockIndex->GetObjectID(); + + if(diffFromID != 0) + { + // Found an old version + rNotifier.NotifyFileUploadingPatch(this, + rFilename); + + // Get the index + std::auto_ptr blockIndexStream(connection.ReceiveStream()); + + // + // Diff the file + // + + rContext.ManageDiffProcess(); + + bool isCompletelyDifferent = false; + std::auto_ptr 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 stored(connection.QueryStoreFile(mObjectID, ModificationTime, + AttributesHash, isCompletelyDifferent?(0):(diffFromID), rStoreFilename, *patchStream)); + + // Get object ID from the result + objID = stored->GetObjectID(); + + // Don't attempt to upload it again! + doNormalUpload = false; + } + } + + if(doNormalUpload) + { + // below threshold or nothing to diff from, so upload whole + rNotifier.NotifyFileUploading(this, rFilename); + + // Prepare to upload, getting a stream which will encode the file as we go along + std::auto_ptr upload( + BackupStoreFile::EncodeFile(rFilename.c_str(), + mObjectID, rStoreFilename, NULL, + &rParams, + &(rParams.mrRunStatusProvider))); + + // Send to store + std::auto_ptr stored( + connection.QueryStoreFile( + mObjectID, ModificationTime, + AttributesHash, + 0 /* no diff from file ID */, + rStoreFilename, *upload)); + + // Get object ID from the result + objID = stored->GetObjectID(); + } + } + catch(BoxException &e) + { + rContext.UnManageDiffProcess(); + + if(e.GetType() == ConnectionException::ExceptionType && + e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) + { + // Check and see what error the protocol has, + // this is more useful to users than the exception. + int type, subtype; + if(connection.GetLastError(type, subtype)) + { + if(type == BackupProtocolClientError::ErrorType + && subtype == BackupProtocolClientError::Err_StorageLimitExceeded) + { + // The hard limit was exceeded on the server, notify! + rParams.mrSysadminNotifier.NotifySysadmin( + SysadminNotifier::StoreFull); + // return an error code instead of + // throwing an exception that we + // can't debug. + return 0; + } + rNotifier.NotifyFileUploadServerError(this, + rFilename, type, subtype); + } + } + + // Send the error on it's way + throw; + } + + rNotifier.NotifyFileUploaded(this, rFilename, FileSize); + + // Return the new object ID of this file + return objID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(SyncParams &, const char *) +// Purpose: Sets the error state when there were problems reading an object +// from the filesystem. +// Created: 29/3/04 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject(BackupClientDirectoryRecord::SyncParams &rParams, const char *Filename) +{ + // Zero hash, so it gets synced properly next time round. + ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); + + // Log the error - already done by caller + /* + rParams.GetProgressNotifier().NotifyFileReadFailed(this, + Filename, strerror(errno)); + */ + + // Mark that an error occured in the parameters object + rParams.mReadErrorsOnFilesystemObjects = true; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncParams::SyncParams(BackupClientContext &) +// Purpose: Constructor +// Created: 8/3/04 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::SyncParams::SyncParams( + RunStatusProvider &rRunStatusProvider, + 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) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncParams::~SyncParams() +// Purpose: Destructor +// Created: 8/3/04 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::SyncParams::~SyncParams() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::Deserialize(Archive & rArchive) +// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::Deserialize(Archive & rArchive) +{ + // Make deletion recursive + DeleteSubDirectories(); + + // Delete maps + if(mpPendingEntries != 0) + { + delete mpPendingEntries; + mpPendingEntries = 0; + } + + // + // + // + rArchive.Read(mObjectID); + rArchive.Read(mSubDirName); + rArchive.Read(mInitialSyncDone); + rArchive.Read(mSyncDone); + + // + // + // + int64_t iCount = 0; + rArchive.Read(iCount); + + if (iCount != sizeof(mStateChecksum)/sizeof(mStateChecksum[0])) + { + // we have some kind of internal system representation change: throw for now + THROW_EXCEPTION(CommonException, Internal) + } + + for (int v = 0; v < iCount; v++) + { + // Load each checksum entry + rArchive.Read(mStateChecksum[v]); + } + + // + // + // + iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + // load each pending entry + mpPendingEntries = new std::map; + if (!mpPendingEntries) + { + throw std::bad_alloc(); + } + + for (int v = 0; v < iCount; v++) + { + std::string strItem; + box_time_t btItem; + + rArchive.Read(strItem); + rArchive.Read(btItem); + (*mpPendingEntries)[strItem] = btItem; + } + } + + // + // + // + iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + std::string strItem; + rArchive.Read(strItem); + + BackupClientDirectoryRecord* pSubDirRecord = + new BackupClientDirectoryRecord(0, ""); + // will be deserialized anyway, give it id 0 for now + + if (!pSubDirRecord) + { + throw std::bad_alloc(); + } + + /***** RECURSE *****/ + pSubDirRecord->Deserialize(rArchive); + mSubDirectories[strItem] = pSubDirRecord; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::Serialize(Archive & rArchive) +// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::Serialize(Archive & rArchive) const +{ + // + // + // + rArchive.Write(mObjectID); + rArchive.Write(mSubDirName); + rArchive.Write(mInitialSyncDone); + rArchive.Write(mSyncDone); + + // + // + // + int64_t iCount = 0; + + // when reading back the archive, we will + // need to know how many items there are. + iCount = sizeof(mStateChecksum) / sizeof(mStateChecksum[0]); + rArchive.Write(iCount); + + for (int v = 0; v < iCount; v++) + { + rArchive.Write(mStateChecksum[v]); + } + + // + // + // + if (!mpPendingEntries) + { + iCount = 0; + rArchive.Write(iCount); + } + else + { + iCount = mpPendingEntries->size(); + rArchive.Write(iCount); + + for (std::map::const_iterator + i = mpPendingEntries->begin(); + i != mpPendingEntries->end(); i++) + { + rArchive.Write(i->first); + rArchive.Write(i->second); + } + } + // + // + // + iCount = mSubDirectories.size(); + rArchive.Write(iCount); + + for (std::map::const_iterator + i = mSubDirectories.begin(); + i != mSubDirectories.end(); i++) + { + const BackupClientDirectoryRecord* pSubItem = i->second; + ASSERT(pSubItem); + + rArchive.Write(i->first); + pSubItem->Serialize(rArchive); + } +} diff --git a/bin/bbackupd/BackupClientDirectoryRecord.h b/bin/bbackupd/BackupClientDirectoryRecord.h new file mode 100644 index 00000000..fce3fc04 --- /dev/null +++ b/bin/bbackupd/BackupClientDirectoryRecord.h @@ -0,0 +1,171 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDirectoryRecord.h +// Purpose: Implementation of record about directory for backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTDIRECTORYRECORD__H +#define BACKUPCLIENTDIRECTORYRECORD__H + +#include +#include + +#include "BackupClientFileAttributes.h" +#include "BackupDaemonInterface.h" +#include "BackupStoreDirectory.h" +#include "BoxTime.h" +#include "MD5Digest.h" +#include "ReadLoggingStream.h" +#include "RunStatusProvider.h" + +class Archive; +class BackupClientContext; +class BackupDaemon; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientDirectoryRecord +// Purpose: Implementation of record about directory for backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupClientDirectoryRecord +{ +public: + BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName); + ~BackupClientDirectoryRecord(); + + void Deserialize(Archive & rArchive); + void Serialize(Archive & rArchive) const; +private: + BackupClientDirectoryRecord(const BackupClientDirectoryRecord &); +public: + + enum + { + UnknownDirectoryID = 0 + }; + + // -------------------------------------------------------------------------- + // + // Class + // Name: BackupClientDirectoryRecord::SyncParams + // Purpose: Holds parameters etc for directory syncing. Not passed as + // const, some parameters may be modified during sync. + // Created: 8/3/04 + // + // -------------------------------------------------------------------------- + class SyncParams : public ReadLoggingStream::Logger + { + public: + SyncParams( + RunStatusProvider &rRunStatusProvider, + SysadminNotifier &rSysadminNotifier, + ProgressNotifier &rProgressNotifier, + BackupClientContext &rContext); + ~SyncParams(); + private: + // No copying + SyncParams(const SyncParams&); + SyncParams &operator=(const SyncParams&); + + public: + // Data members are public, as accessors are not justified here + box_time_t mSyncPeriodStart; + box_time_t mSyncPeriodEnd; + box_time_t mMaxUploadWait; + box_time_t mMaxFileTimeInFuture; + int32_t mFileTrackingSizeThreshold; + int32_t mDiffingUploadSizeThreshold; + RunStatusProvider &mrRunStatusProvider; + SysadminNotifier &mrSysadminNotifier; + ProgressNotifier &mrProgressNotifier; + BackupClientContext &mrContext; + bool mReadErrorsOnFilesystemObjects; + + // Member variables modified by syncing process + box_time_t mUploadAfterThisTimeInTheFuture; + bool mHaveLoggedWarningAboutFutureFileTimes; + + bool StopRun() { return mrRunStatusProvider.StopRun(); } + void NotifySysadmin(SysadminNotifier::EventCode Event) + { + mrSysadminNotifier.NotifySysadmin(Event); + } + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } + + /* ReadLoggingStream::Logger implementation */ + virtual void Log(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length, elapsed, finish); + } + virtual void Log(int64_t readSize, int64_t offset, + int64_t length) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length); + } + virtual void Log(int64_t readSize, int64_t offset) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset); + } + }; + + void SyncDirectory(SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, + bool ThisDirHasJustBeenCreated = false); + +private: + void DeleteSubDirectories(); + BackupStoreDirectory *FetchDirectoryListing(SyncParams &rParams); + void UpdateAttributes(SyncParams &rParams, + BackupStoreDirectory *pDirOnStore, + const std::string &rLocalPath); + bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, + const std::string &rRemotePath, + BackupStoreDirectory *pDirOnStore, + std::vector &rEntriesLeftOver, + std::vector &rFiles, + const std::vector &rDirs); + int64_t UploadFile(SyncParams &rParams, + const std::string &rFilename, + const BackupStoreFilename &rStoreFilename, + int64_t FileSize, box_time_t ModificationTime, + box_time_t AttributesHash, bool NoPreviousVersionOnServer); + void SetErrorWhenReadingFilesystemObject(SyncParams &rParams, + const char *Filename); + void RemoveDirectoryInPlaceOfFile(SyncParams &rParams, + BackupStoreDirectory* pDirOnStore, + BackupStoreDirectory::Entry* pEntry, + const std::string &rFilename); + +private: + int64_t mObjectID; + std::string mSubDirName; + bool mInitialSyncDone; + bool mSyncDone; + + // Checksum of directory contents and attributes, used to detect changes + uint8_t mStateChecksum[MD5Digest::DigestLength]; + + std::map *mpPendingEntries; + std::map mSubDirectories; + // mpPendingEntries is a pointer rather than simple a member + // variable, because most of the time it'll be empty. This would + // waste a lot of memory because of STL allocation policies. +}; + +#endif // BACKUPCLIENTDIRECTORYRECORD__H + + diff --git a/bin/bbackupd/BackupClientInodeToIDMap.cpp b/bin/bbackupd/BackupClientInodeToIDMap.cpp new file mode 100644 index 00000000..b9f56c5a --- /dev/null +++ b/bin/bbackupd/BackupClientInodeToIDMap.cpp @@ -0,0 +1,327 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientInodeToIDMap.cpp +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_DB + // Include db headers and other OS files if they're needed for the disc implementation + #include + #include + #include + #include + #include +#endif + +#define BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION +#include "BackupClientInodeToIDMap.h" + +#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; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::BackupClientInodeToIDMap() +// Purpose: Constructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap::BackupClientInodeToIDMap() +#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + : mReadOnly(true), + mEmpty(false), + dbp(0) +#endif +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::~BackupClientInodeToIDMap() +// Purpose: Destructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap::~BackupClientInodeToIDMap() +{ +#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + if(dbp != 0) + { +#if BDB_VERSION_MAJOR >= 3 + dbp->close(0); +#else + dbp->close(dbp); +#endif + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Open(const char *, bool, bool) +// Purpose: Open the database map, creating a file on disc to store everything +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::Open(const char *Filename, bool ReadOnly, bool CreateNew) +{ +#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + // Correct arguments? + ASSERT(!(CreateNew && ReadOnly)); + + // Correct usage? + ASSERT(dbp == 0); + 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) + { + THROW_EXCEPTION(BackupStoreException, BerkelyDBFailure); + } + + // 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. +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::OpenEmpty() +{ +#ifndef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + ASSERT(dbp == 0); + mEmpty = true; + mReadOnly = true; +#endif +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Close() +// Purpose: Close the database file +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +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 +} + + +// -------------------------------------------------------------------------- +// +// Function +// 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) +{ +#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + mMap[InodeRef] = std::pair(ObjectID, InDirectory); +#else + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, InodeMapIsReadOnly); + } + + if(dbp == 0) + { + THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen); + } + + // 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 +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Lookup(InodeRefType, +// int64_t &, int64_t &) const +// Purpose: Looks up an inode in the map, returning true if it +// exists, and the object ids of it and the directory +// it's in the reference arguments. +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef, + int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const +{ +#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + std::map >::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) + { + 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; + + switch(dbp->get(dbp, &key, &data, 0)) +#endif + + { + case 1: // 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 new file mode 100644 index 00000000..1dfef702 --- /dev/null +++ b/bin/bbackupd/BackupClientInodeToIDMap.h @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientInodeToIDMap.h +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTINODETOIDMAP_H +#define BACKUPCLIENTINODETOIDMAP__H + +#include + +#include +#include + +// 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 +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientInodeToIDMap +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +class BackupClientInodeToIDMap +{ +public: + BackupClientInodeToIDMap(); + ~BackupClientInodeToIDMap(); +private: + BackupClientInodeToIDMap(const BackupClientInodeToIDMap &rToCopy); // not allowed +public: + + void Open(const char *Filename, bool ReadOnly, bool CreateNew); + void OpenEmpty(); + + void AddToMap(InodeRefType InodeRef, int64_t ObjectID, int64_t InDirectory); + bool Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, int64_t &rInDirectoryOut) const; + + void Close(); + +private: +#ifdef BACKIPCLIENTINODETOIDMAP_IN_MEMORY_IMPLEMENTATION + std::map > 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 +}; + +#endif // BACKUPCLIENTINODETOIDMAP__H + + diff --git a/bin/bbackupd/BackupDaemon.cpp b/bin/bbackupd/BackupDaemon.cpp new file mode 100644 index 00000000..b6f90cad --- /dev/null +++ b/bin/bbackupd/BackupDaemon.cpp @@ -0,0 +1,2883 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.cpp +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#ifdef HAVE_UNISTD_H + #include +#endif +#ifdef HAVE_SIGNAL_H + #include +#endif +#ifdef HAVE_SYS_PARAM_H + #include +#endif +#ifdef HAVE_SYS_WAIT_H + #include +#endif +#ifdef HAVE_SYS_MOUNT_H + #include +#endif +#ifdef HAVE_MNTENT_H + #include +#endif +#ifdef HAVE_SYS_MNTTAB_H + #include + #include +#endif +#ifdef HAVE_PROCESS_H + #include +#endif + +#include + +#include "Configuration.h" +#include "IOStream.h" +#include "MemBlockStream.h" +#include "CommonException.h" +#include "BoxPortsAndFiles.h" + +#include "SSLLib.h" + +#include "autogen_BackupProtocolClient.h" +#include "autogen_ClientException.h" +#include "autogen_ConversionException.h" +#include "Archive.h" +#include "BackupClientContext.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupClientFileAttributes.h" +#include "BackupClientInodeToIDMap.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupDaemon.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BannerText.h" +#include "Conversion.h" +#include "ExcludeList.h" +#include "FileStream.h" +#include "IOStreamGetLine.h" +#include "LocalProcessStream.h" +#include "Logging.h" +#include "Random.h" +#include "Timer.h" +#include "Utils.h" + +#ifdef WIN32 + #include "Win32ServiceFunctions.h" + #include "Win32BackupService.h" + + extern Win32BackupService* gpDaemonService; +#endif + +#include "MemLeakFindOn.h" + +static const time_t MAX_SLEEP_TIME = 1024; + +// Make the actual sync period have a little bit of extra time, up to a 64th of the main sync period. +// This prevents repetative cycles of load on the server +#define SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY 6 + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::BackupDaemon() +// Purpose: constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupDaemon::BackupDaemon() + : mState(BackupDaemon::State_Initialising), + mDeleteRedundantLocationsAfter(0), + mLastNotifiedEvent(SysadminNotifier::MAX), + mDeleteUnusedRootDirEntriesAfter(0), + mClientStoreMarker(BackupClientContext::ClientStoreMarker_NotKnown), + mStorageLimitExceeded(false), + mReadErrorsOnFilesystemObjects(false), + mLastSyncTime(0), + mNextSyncTime(0), + mCurrentSyncStartTime(0), + mUpdateStoreInterval(0), + mDeleteStoreObjectInfoFile(false), + mDoSyncForcedByPreviousSyncError(false), + mLogAllFileAccess(false), + mpProgressNotifier(this), + mpLocationResolver(this), + mpRunStatusProvider(this), + mpSysadminNotifier(this) + #ifdef WIN32 + , mInstallService(false), + mRemoveService(false), + mRunAsService(false), + mServiceName("bbackupd") + #endif +{ + // Only ever one instance of a daemon + SSLLib::Initialise(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::~BackupDaemon() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupDaemon::~BackupDaemon() +{ + DeleteAllLocations(); + DeleteAllIDMaps(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DaemonName() +// Purpose: Get name of daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +const char *BackupDaemon::DaemonName() const +{ + return "bbackupd"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DaemonBanner() +// Purpose: Daemon banner +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string BackupDaemon::DaemonBanner() const +{ + return BANNER_TEXT("Backup Client"); +} + +void BackupDaemon::Usage() +{ + this->Daemon::Usage(); + +#ifdef WIN32 + std::cout << + " -s Run as a Windows Service, for internal use only\n" + " -i Install Windows Service (you may want to specify a config file)\n" + " -r Remove Windows Service\n" + " -S Service name for -i and -r options\n"; +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::GetConfigVerify() +// Purpose: Get configuration specification +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *BackupDaemon::GetConfigVerify() const +{ + // Defined elsewhere + return &BackupDaemonConfigVerify; +} + +#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupInInitialProcess() +// Purpose: Platforms with non-checkable credentials on +// local sockets only. +// Prints a warning if the command socket is used. +// Created: 25/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetupInInitialProcess() +{ + // Print a warning on this platform if the CommandSocket is used. + if(GetConfiguration().KeyExists("CommandSocket")) + { + BOX_WARNING( + "==============================================================================\n" + "SECURITY WARNING: This platform cannot check the credentials of connections to\n" + "the command socket. This is a potential DoS security problem.\n" + "Remove the CommandSocket directive from the bbackupd.conf file if bbackupctl\n" + "is not used.\n" + "==============================================================================\n" + ); + } +} +#endif + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteAllLocations() +// Purpose: Deletes all records stored +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteAllLocations() +{ + // Run through, and delete everything + for(std::vector::iterator i = mLocations.begin(); + i != mLocations.end(); ++i) + { + delete *i; + } + + // Clear the contents of the map, so it is empty + mLocations.clear(); + + // And delete everything from the associated mount vector + mIDMapMounts.clear(); +} + +#ifdef WIN32 +std::string BackupDaemon::GetOptionString() +{ + std::string oldOpts = this->Daemon::GetOptionString(); + ASSERT(oldOpts.find("s") == std::string::npos); + ASSERT(oldOpts.find("S") == std::string::npos); + ASSERT(oldOpts.find("i") == std::string::npos); + ASSERT(oldOpts.find("r") == std::string::npos); + return oldOpts + "sS:ir"; +} + +int BackupDaemon::ProcessOption(signed int option) +{ + switch(option) + { + case 's': + { + mRunAsService = true; + return 0; + } + + case 'S': + { + mServiceName = optarg; + Logging::SetProgramName(mServiceName); + return 0; + } + + case 'i': + { + mInstallService = true; + return 0; + } + + case 'r': + { + mRemoveService = true; + return 0; + } + + default: + { + return this->Daemon::ProcessOption(option); + } + } +} + +int BackupDaemon::Main(const std::string &rConfigFileName) +{ + if (mInstallService) + { + return InstallService(rConfigFileName.c_str(), mServiceName); + } + + if (mRemoveService) + { + return RemoveService(mServiceName); + } + + int returnCode; + + if (mRunAsService) + { + // We will be called reentrantly by the Service Control + // Manager, and we had better not call OurService again! + mRunAsService = false; + + BOX_INFO("Box Backup service starting"); + returnCode = OurService(rConfigFileName.c_str()); + BOX_INFO("Box Backup service shut down"); + } + else + { + returnCode = this->Daemon::Main(rConfigFileName); + } + + return returnCode; +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::Run() +// Purpose: Run function for daemon +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::Run() +{ + // initialise global timer mechanism + Timers::Init(); + + #ifndef WIN32 + // Ignore SIGPIPE so that if a command connection is broken, + // the daemon doesn't terminate. + ::signal(SIGPIPE, SIG_IGN); + #endif + + // Create a command socket? + const Configuration &conf(GetConfiguration()); + if(conf.KeyExists("CommandSocket")) + { + // Yes, create a local UNIX socket + mapCommandSocketInfo.reset(new CommandSocketInfo); + const char *socketName = + conf.GetKeyValue("CommandSocket").c_str(); + #ifdef WIN32 + mapCommandSocketInfo->mListeningSocket.Listen( + socketName); + #else + ::unlink(socketName); + mapCommandSocketInfo->mListeningSocket.Listen( + Socket::TypeUNIX, socketName); + #endif + } + + // Handle things nicely on exceptions + try + { + Run2(); + } + catch(...) + { + if(mapCommandSocketInfo.get()) + { + try + { + mapCommandSocketInfo.reset(); + } + catch(std::exception &e) + { + BOX_WARNING("Internal error while " + "closing command socket after " + "another exception: " << e.what()); + } + catch(...) + { + BOX_WARNING("Error closing command socket " + "after exception, ignored."); + } + } + + Timers::Cleanup(); + + throw; + } + + // Clean up + mapCommandSocketInfo.reset(); + Timers::Cleanup(); +} + +void BackupDaemon::InitCrypto() +{ + // Read in the certificates creating a TLS context + const Configuration &conf(GetConfiguration()); + std::string certFile(conf.GetKeyValue("CertificateFile")); + std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); + std::string caFile(conf.GetKeyValue("TrustedCAsFile")); + mTlsContext.Initialise(false /* as client */, certFile.c_str(), + keyFile.c_str(), caFile.c_str()); + + // Set up the keys for various things + BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::Run2() +// Purpose: Run function for daemon (second stage) +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::Run2() +{ + InitCrypto(); + + const Configuration &conf(GetConfiguration()); + + // How often to connect to the store (approximate) + mUpdateStoreInterval = SecondsToBoxTime( + conf.GetKeyValueInt("UpdateStoreInterval")); + + // But are we connecting automatically? + bool automaticBackup = conf.GetKeyValueBool("AutomaticBackup"); + + // When the next sync should take place -- which is ASAP + mNextSyncTime = 0; + + // When the last sync started (only updated if the store was not full when the sync ended) + mLastSyncTime = 0; + + // -------------------------------------------------------------------------------------------- + + mDeleteStoreObjectInfoFile = DeserializeStoreObjectInfo( + mLastSyncTime, mNextSyncTime); + + // -------------------------------------------------------------------------------------------- + + + // Set state + SetState(State_Idle); + + mDoSyncForcedByPreviousSyncError = false; + + // Loop around doing backups + do + { + // Flags used below + bool storageLimitExceeded = false; + bool doSync = false; + bool mDoSyncForcedByCommand = false; + + // Is a delay necessary? + box_time_t currentTime; + + do + { + // Check whether we should be stopping, + // and don't run a sync if so. + if(StopRun()) break; + + currentTime = GetCurrentBoxTime(); + + // Pause a while, but no more than + // MAX_SLEEP_TIME seconds (use the conditional + // because times are unsigned) + box_time_t requiredDelay = + (mNextSyncTime < currentTime) + ? (0) + : (mNextSyncTime - currentTime); + + // If there isn't automatic backup happening, + // set a long delay. And limit delays at the + // same time. + if(!automaticBackup && !mDoSyncForcedByPreviousSyncError) + { + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + else if(requiredDelay > SecondsToBoxTime(MAX_SLEEP_TIME)) + { + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + + // Only delay if necessary + if(requiredDelay > 0) + { + // Sleep somehow. There are choices + // on how this should be done, + // depending on the state of the + // control connection + if(mapCommandSocketInfo.get() != 0) + { + // A command socket exists, + // so sleep by waiting on it + WaitOnCommandSocket(requiredDelay, + doSync, mDoSyncForcedByCommand); + } + else + { + // No command socket or + // connection, just do a + // normal sleep + time_t sleepSeconds = + BoxTimeToSeconds(requiredDelay); + ::sleep((sleepSeconds <= 0) + ? 1 : sleepSeconds); + } + } + + if ((automaticBackup || mDoSyncForcedByPreviousSyncError) + && currentTime >= mNextSyncTime) + { + doSync = true; + } + } + while(!doSync && !StopRun()); + + // Time of sync start, and if it's time for another sync + // (and we're doing automatic syncs), set the flag + mCurrentSyncStartTime = GetCurrentBoxTime(); + if((automaticBackup || mDoSyncForcedByPreviousSyncError) && + mCurrentSyncStartTime >= mNextSyncTime) + { + doSync = true; + } + + // Use a script to see if sync is allowed now? + if(!mDoSyncForcedByCommand && doSync && !StopRun()) + { + int d = UseScriptToSeeIfSyncAllowed(); + if(d > 0) + { + // Script has asked for a delay + mNextSyncTime = GetCurrentBoxTime() + + SecondsToBoxTime(d); + doSync = false; + } + } + + // Ready to sync? (but only if we're not supposed + // to be stopping) + if(doSync && !StopRun()) + { + RunSyncNowWithExceptionHandling(); + } + + // Set state + SetState(storageLimitExceeded?State_StorageLimitExceeded:State_Idle); + + } while(!StopRun()); + + // Make sure we have a clean start next time round (if restart) + DeleteAllLocations(); + DeleteAllIDMaps(); +} + +void BackupDaemon::RunSyncNowWithExceptionHandling() +{ + OnBackupStart(); + + // Do sync + bool errorOccurred = false; + int errorCode = 0, errorSubCode = 0; + const char* errorString = "unknown"; + + try + { + RunSyncNow(); + } + catch(BoxException &e) + { + errorOccurred = true; + errorString = e.what(); + errorCode = e.GetType(); + errorSubCode = e.GetSubType(); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error during backup run: " << e.what()); + errorOccurred = true; + errorString = e.what(); + } + catch(...) + { + // TODO: better handling of exceptions here... + // need to be very careful + errorOccurred = true; + } + + // do not retry immediately without a good reason + mDoSyncForcedByPreviousSyncError = false; + + if(errorOccurred) + { + // Is it a berkely db failure? + bool isBerkelyDbFailure = false; + + if (errorCode == BackupStoreException::ExceptionType + && errorSubCode == BackupStoreException::BerkelyDBFailure) + { + isBerkelyDbFailure = true; + } + + if(isBerkelyDbFailure) + { + // Delete corrupt files + DeleteCorruptBerkelyDbFiles(); + } + + // Clear state data + // Go back to beginning of time + mLastSyncTime = 0; + mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; // no store marker, so download everything + DeleteAllLocations(); + DeleteAllIDMaps(); + + // Handle restart? + if(StopRun()) + { + BOX_NOTICE("Exception (" << errorCode + << "/" << errorSubCode + << ") due to signal"); + OnBackupFinish(); + return; + } + + NotifySysadmin(SysadminNotifier::BackupError); + + // If the Berkely db files get corrupted, + // delete them and try again immediately. + if(isBerkelyDbFailure) + { + BOX_ERROR("Berkely db inode map files corrupted, " + "deleting and restarting scan. Renamed files " + "and directories will not be tracked until " + "after this scan."); + ::sleep(1); + } + else + { + // Not restart/terminate, pause and retry + // Notify administrator + SetState(State_Error); + BOX_ERROR("Exception caught (" << errorString << + " " << errorCode << "/" << errorSubCode << + "), reset state and waiting to retry..."); + ::sleep(10); + mNextSyncTime = mCurrentSyncStartTime + + SecondsToBoxTime(100) + + Random::RandomInt(mUpdateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + } + } + // Notify system administrator about the final state of the backup + else if(mReadErrorsOnFilesystemObjects) + { + NotifySysadmin(SysadminNotifier::ReadError); + } + else if(mStorageLimitExceeded) + { + NotifySysadmin(SysadminNotifier::StoreFull); + } + else + { + NotifySysadmin(SysadminNotifier::BackupOK); + } + + // If we were retrying after an error, and this backup succeeded, + // then now would be a good time to stop :-) + mDoSyncForcedByPreviousSyncError = errorOccurred; + + OnBackupFinish(); +} + +void BackupDaemon::RunSyncNow() +{ + // Delete the serialised store object file, + // so that we don't try to reload it after a + // partially completed backup + if(mDeleteStoreObjectInfoFile && + !DeleteStoreObjectInfo()) + { + BOX_ERROR("Failed to delete the StoreObjectInfoFile, " + "backup cannot continue safely."); + THROW_EXCEPTION(ClientException, + FailedToDeleteStoreObjectInfoFile); + } + + // In case the backup throws an exception, + // we should not try to delete the store info + // object file again. + mDeleteStoreObjectInfoFile = false; + + const Configuration &conf(GetConfiguration()); + + std::auto_ptr fileLogger; + + if (conf.KeyExists("LogFile")) + { + Log::Level level = Log::INFO; + if (conf.KeyExists("LogFileLevel")) + { + level = Logging::GetNamedLevel( + conf.GetKeyValue("LogFileLevel")); + } + fileLogger.reset(new FileLogger(conf.GetKeyValue("LogFile"), + level)); + } + + std::string extendedLogFile; + if (conf.KeyExists("ExtendedLogFile")) + { + extendedLogFile = conf.GetKeyValue("ExtendedLogFile"); + } + + if (conf.KeyExists("LogAllFileAccess")) + { + mLogAllFileAccess = conf.GetKeyValueBool("LogAllFileAccess"); + } + + // Then create a client context object (don't + // just connect, as this may be unnecessary) + BackupClientContext clientContext + ( + *mpLocationResolver, + mTlsContext, + conf.GetKeyValue("StoreHostname"), + conf.GetKeyValueInt("StorePort"), + conf.GetKeyValueUint32("AccountNumber"), + conf.GetKeyValueBool("ExtendedLogging"), + conf.KeyExists("ExtendedLogFile"), + extendedLogFile, *mpProgressNotifier + ); + + // The minimum age a file needs to be before it will be + // considered for uploading + box_time_t minimumFileAge = SecondsToBoxTime( + conf.GetKeyValueInt("MinimumFileAge")); + + // The maximum time we'll wait to upload a file, regardless + // of how often it's modified + box_time_t maxUploadWait = SecondsToBoxTime( + conf.GetKeyValueInt("MaxUploadWait")); + // Adjust by subtracting the minimum file age, so is relative + // to sync period end in comparisons + if (maxUploadWait > minimumFileAge) + { + maxUploadWait -= minimumFileAge; + } + else + { + maxUploadWait = 0; + } + + // Calculate the sync period of files to examine + box_time_t syncPeriodStart = mLastSyncTime; + box_time_t syncPeriodEnd = GetCurrentBoxTime() - minimumFileAge; + + if(syncPeriodStart >= syncPeriodEnd && + syncPeriodStart - syncPeriodEnd < minimumFileAge) + { + // This can happen if we receive a force-sync command less + // than minimumFileAge after the last sync. Deal with it by + // moving back syncPeriodStart, which should not do any + // damage. + syncPeriodStart = syncPeriodEnd - + SecondsToBoxTime(1); + } + + if(syncPeriodStart >= syncPeriodEnd) + { + BOX_ERROR("Invalid (negative) sync period: " + "perhaps your clock is going " + "backwards (" << syncPeriodStart << + " to " << syncPeriodEnd << ")"); + THROW_EXCEPTION(ClientException, + ClockWentBackwards); + } + + // Check logic + ASSERT(syncPeriodEnd > syncPeriodStart); + // Paranoid check on sync times + if(syncPeriodStart >= syncPeriodEnd) return; + + // Adjust syncPeriodEnd to emulate snapshot + // behaviour properly + box_time_t syncPeriodEndExtended = syncPeriodEnd; + + // Using zero min file age? + if(minimumFileAge == 0) + { + // Add a year on to the end of the end time, + // to make sure we sync files which are + // modified after the scan run started. + // Of course, they may be eligible to be + // synced again the next time round, + // but this should be OK, because the changes + // only upload should upload no data. + syncPeriodEndExtended += SecondsToBoxTime( + (time_t)(356*24*3600)); + } + + // Set up the sync parameters + BackupClientDirectoryRecord::SyncParams params(*mpRunStatusProvider, + *mpSysadminNotifier, *mpProgressNotifier, clientContext); + params.mSyncPeriodStart = syncPeriodStart; + params.mSyncPeriodEnd = syncPeriodEndExtended; + // use potentially extended end time + params.mMaxUploadWait = maxUploadWait; + params.mFileTrackingSizeThreshold = + conf.GetKeyValueInt("FileTrackingSizeThreshold"); + params.mDiffingUploadSizeThreshold = + conf.GetKeyValueInt("DiffingUploadSizeThreshold"); + params.mMaxFileTimeInFuture = + SecondsToBoxTime(conf.GetKeyValueInt("MaxFileTimeInFuture")); + mDeleteRedundantLocationsAfter = + conf.GetKeyValueInt("DeleteRedundantLocationsAfter"); + mStorageLimitExceeded = false; + mReadErrorsOnFilesystemObjects = false; + + // Setup various timings + int maximumDiffingTime = 600; + int keepAliveTime = 60; + + // max diffing time, keep-alive time + if(conf.KeyExists("MaximumDiffingTime")) + { + maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); + } + if(conf.KeyExists("KeepAliveTime")) + { + keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); + } + + clientContext.SetMaximumDiffingTime(maximumDiffingTime); + clientContext.SetKeepAliveTime(keepAliveTime); + + // Set store marker + clientContext.SetClientStoreMarker(mClientStoreMarker); + + // 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( + "BackupLocations")); + + // Make sure all the directory records + // are set up + SetupLocations(clientContext, locations); + } + + mpProgressNotifier->NotifyIDMapsSetup(clientContext); + + // Get some ID maps going + SetupIDMapsForSync(); + + // Delete any unused directories? + DeleteUnusedRootDirEntries(clientContext); + + // Go through the records, syncing them + for(std::vector::const_iterator + i(mLocations.begin()); + i != mLocations.end(); ++i) + { + // Set current and new ID map pointers + // in the context + clientContext.SetIDMaps(mCurrentIDMaps[(*i)->mIDMapIndex], + mNewIDMaps[(*i)->mIDMapIndex]); + + // Set exclude lists (context doesn't + // take ownership) + clientContext.SetExcludeLists( + (*i)->mpExcludeFiles, + (*i)->mpExcludeDirs); + + // Sync the directory + (*i)->mpDirectoryRecord->SyncDirectory( + params, + BackupProtocolClientListDirectory::RootDirectory, + (*i)->mPath, std::string("/") + (*i)->mName); + + // Unset exclude lists (just in case) + clientContext.SetExcludeLists(0, 0); + } + + // Perform any deletions required -- these are + // delayed until the end to allow renaming to + // happen neatly. + clientContext.PerformDeletions(); + + // Close any open connection + clientContext.CloseAnyOpenConnection(); + + // Get the new store marker + mClientStoreMarker = clientContext.GetClientStoreMarker(); + mStorageLimitExceeded = clientContext.StorageLimitExceeded(); + mReadErrorsOnFilesystemObjects = + params.mReadErrorsOnFilesystemObjects; + + if(!mStorageLimitExceeded) + { + // The start time of the next run is the end time of this + // run. This is only done if the storage limit wasn't + // exceeded (as things won't have been done properly if + // it was) + mLastSyncTime = syncPeriodEnd; + } + + // Commit the ID Maps + CommitIDMapsAfterSync(); + + // Calculate when the next sync run should be + mNextSyncTime = mCurrentSyncStartTime + + mUpdateStoreInterval + + Random::RandomInt(mUpdateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + + // -------------------------------------------------------------------------------------------- + + // We had a successful backup, save the store + // info. If we save successfully, we must + // delete the file next time we start a backup + + mDeleteStoreObjectInfoFile = + SerializeStoreObjectInfo(mLastSyncTime, + mNextSyncTime); + + // -------------------------------------------------------------------------------------------- +} + +void BackupDaemon::OnBackupStart() +{ + // Touch a file to record times in filesystem + TouchFileInWorkingDir("last_sync_start"); + + // Reset statistics on uploads + BackupStoreFile::ResetStats(); + + // Tell anything connected to the command socket + SendSyncStartOrFinish(true /* start */); + + // Notify administrator + NotifySysadmin(SysadminNotifier::BackupStart); + + // Set state and log start + SetState(State_Connected); + BOX_NOTICE("Beginning scan of local files"); +} + +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(); + + // 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"); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::UseScriptToSeeIfSyncAllowed() +// Purpose: Private. Use a script to see if the sync should be +// allowed now (if configured). Returns -1 if it's +// allowed, time in seconds to wait otherwise. +// Created: 21/6/04 +// +// -------------------------------------------------------------------------- +int BackupDaemon::UseScriptToSeeIfSyncAllowed() +{ + const Configuration &conf(GetConfiguration()); + + // Got a script to run? + if(!conf.KeyExists("SyncAllowScript")) + { + // No. Do sync. + return -1; + } + + // If there's no result, try again in five minutes + int waitInSeconds = (60*5); + + std::string script(conf.GetKeyValue("SyncAllowScript") + + " \"" + GetConfigFileName() + "\""); + + // Run it? + pid_t pid = 0; + try + { + std::auto_ptr pscript(LocalProcessStream(script, + pid)); + + // Read in the result + IOStreamGetLine getLine(*pscript); + 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(line); + } + catch(ConversionException &e) + { + BOX_ERROR("Invalid output from " + "SyncAllowScript: '" << + line << "' (" << script << ")"); + throw; + } + + BOX_NOTICE("Delaying sync by " << waitInSeconds + << " seconds due to SyncAllowScript " + << "(" << script << ")"); + } + } + + } + catch(std::exception &e) + { + BOX_ERROR("Internal error running SyncAllowScript: " + << e.what() << " (" << script << ")"); + } + catch(...) + { + // Ignore any exceptions + // Log that something bad happened + BOX_ERROR("Unknown error running SyncAllowScript (" << + script << ")"); + } + + // Wait and then cleanup child process, if any + if(pid != 0) + { + int status = 0; + ::waitpid(pid, &status, 0); + } + + return waitInSeconds; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::WaitOnCommandSocket(box_time_t, bool &, bool &) +// Purpose: Waits on a the command socket for a time of UP TO the required time +// but may be much less, and handles a command if necessary. +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut) +{ + ASSERT(mapCommandSocketInfo.get()); + if(!mapCommandSocketInfo.get()) + { + // failure case isn't too bad + ::sleep(1); + return; + } + + BOX_TRACE("Wait on command socket, delay = " << RequiredDelay); + + try + { + // Timeout value for connections and things + int timeout = ((int)BoxTimeToMilliSeconds(RequiredDelay)) + 1; + // Handle bad boundary cases + if(timeout <= 0) timeout = 1; + if(timeout == INFTIM) timeout = 100000; + + // Wait for socket connection, or handle a command? + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + // No connection, listen for a new one + mapCommandSocketInfo->mpConnectedSocket.reset(mapCommandSocketInfo->mListeningSocket.Accept(timeout).release()); + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + // If a connection didn't arrive, there was a timeout, which means we've + // waited long enough and it's time to go. + return; + } + else + { +#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + bool uidOK = true; + BOX_WARNING("On this platform, no security check can be made on the credentials of peers connecting to the command socket. (bbackupctl)"); +#else + // Security check -- does the process connecting to this socket have + // the same UID as this process? + bool uidOK = false; + // BLOCK + { + uid_t remoteEUID = 0xffff; + gid_t remoteEGID = 0xffff; + if(mapCommandSocketInfo->mpConnectedSocket->GetPeerCredentials(remoteEUID, remoteEGID)) + { + // Credentials are available -- check UID + if(remoteEUID == ::getuid()) + { + // Acceptable + uidOK = true; + } + } + } +#endif // PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + + // Is this an acceptable connection? + if(!uidOK) + { + // Dump the connection + BOX_ERROR("Incoming command connection from peer had different user ID than this process, or security check could not be completed."); + mapCommandSocketInfo->mpConnectedSocket.reset(); + return; + } + else + { + // Log + BOX_INFO("Connection from command socket"); + + // Send a header line summarising the configuration and current state + const Configuration &conf(GetConfiguration()); + char summary[256]; + int summarySize = sprintf(summary, "bbackupd: %d %d %d %d\nstate %d\n", + conf.GetKeyValueBool("AutomaticBackup"), + conf.GetKeyValueInt("UpdateStoreInterval"), + conf.GetKeyValueInt("MinimumFileAge"), + conf.GetKeyValueInt("MaxUploadWait"), + mState); + mapCommandSocketInfo->mpConnectedSocket->Write(summary, summarySize); + + // Set the timeout to something very small, so we don't wait too long on waiting + // for any incoming data + timeout = 10; // milliseconds + } + } + } + + // So there must be a connection now. + ASSERT(mapCommandSocketInfo->mpConnectedSocket.get() != 0); + + // Is there a getline object ready? + if(mapCommandSocketInfo->mpGetLine == 0) + { + // Create a new one + mapCommandSocketInfo->mpGetLine = new IOStreamGetLine(*(mapCommandSocketInfo->mpConnectedSocket.get())); + } + + // Ping the remote side, to provide errors which will mean the socket gets closed + mapCommandSocketInfo->mpConnectedSocket->Write("ping\n", 5); + + // Wait for a command or something on the socket + std::string command; + while(mapCommandSocketInfo->mpGetLine != 0 && !mapCommandSocketInfo->mpGetLine->IsEOF() + && mapCommandSocketInfo->mpGetLine->GetLine(command, false /* no preprocessing */, timeout)) + { + BOX_TRACE("Receiving command '" << command + << "' over command socket"); + + bool sendOK = false; + bool sendResponse = true; + + // Command to process! + if(command == "quit" || command == "") + { + // Close the socket. + CloseCommandConnection(); + sendResponse = false; + } + else if(command == "sync") + { + // Sync now! + DoSyncFlagOut = true; + SyncIsForcedOut = false; + sendOK = true; + } + else if(command == "force-sync") + { + // Sync now (forced -- overrides any SyncAllowScript) + DoSyncFlagOut = true; + SyncIsForcedOut = true; + sendOK = true; + } + else if(command == "reload") + { + // Reload the configuration + SetReloadConfigWanted(); + sendOK = true; + } + else if(command == "terminate") + { + // Terminate the daemon cleanly + SetTerminateWanted(); + sendOK = true; + } + + // Send a response back? + if(sendResponse) + { + mapCommandSocketInfo->mpConnectedSocket->Write(sendOK?"ok\n":"error\n", sendOK?3:6); + } + + // Set timeout to something very small, so this just checks for data which is waiting + timeout = 1; + } + + // Close on EOF? + if(mapCommandSocketInfo->mpGetLine != 0 && mapCommandSocketInfo->mpGetLine->IsEOF()) + { + CloseCommandConnection(); + } + } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write to command socket: " << ce.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write to command socket: " << + e.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } + catch(...) + { + BOX_ERROR("Failed to write to command socket: unknown error"); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CloseCommandConnection() +// Purpose: Close the command connection, ignoring any errors +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::CloseCommandConnection() +{ + try + { + BOX_TRACE("Closing command connection"); + + if(mapCommandSocketInfo->mpGetLine) + { + delete mapCommandSocketInfo->mpGetLine; + mapCommandSocketInfo->mpGetLine = 0; + } + mapCommandSocketInfo->mpConnectedSocket.reset(); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error while closing command " + "socket: " << e.what()); + } + catch(...) + { + // Ignore any errors + } +} + + +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.cpp +// Purpose: Send a start or finish sync message to the command socket, if it's connected. +// +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SendSyncStartOrFinish(bool SendStart) +{ + // The bbackupctl program can't rely on a state change, because it + // may never change if the server doesn't need to be contacted. + + if(mapCommandSocketInfo.get() && + mapCommandSocketInfo->mpConnectedSocket.get() != 0) + { + std::string message = SendStart ? "start-sync" : "finish-sync"; + try + { + message += "\n"; + mapCommandSocketInfo->mpConnectedSocket->Write( + message.c_str(), message.size()); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error while sending to " + "command socket client: " << e.what()); + CloseCommandConnection(); + } + catch(...) + { + CloseCommandConnection(); + } + } +} + + + + +#if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_NMTONNAME) + // string comparison ordering for when mount points are handled + // by code, rather than the OS. + typedef struct + { + bool operator()(const std::string &s1, const std::string &s2) + { + if(s1.size() == s2.size()) + { + // Equal size, sort according to natural sort order + return s1 < s2; + } + else + { + // Make sure longer strings go first + return s1.size() > s2.size(); + } + } + } mntLenCompare; +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupLocations(BackupClientContext &, const Configuration &) +// Purpose: Makes sure that the list of directories records is correctly set up +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +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()); + + // Ask server for a list of everything in the root directory, + // which is a directory itself + std::auto_ptr dirreply( + connection.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + // only directories + BackupProtocolClientListDirectory::Flags_Dir, + // exclude old/deleted stuff + BackupProtocolClientListDirectory::Flags_Deleted | + BackupProtocolClientListDirectory::Flags_OldVersion, + false /* no attributes */)); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(connection.ReceiveStream()); + dir.ReadFromStream(*dirstream, connection.GetTimeout()); + + // Map of mount names to ID map index + std::map mounts; + int numIDMaps = 0; + +#ifdef HAVE_MOUNTS +#if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_MNTONNAME) + // Linux and others can't tell you where a directory is mounted. So we + // have to read the mount entries from /etc/mtab! Bizarre that the OS + // itself can't tell you, but there you go. + std::set mountPoints; + // BLOCK + FILE *mountPointsFile = 0; + +#ifdef HAVE_STRUCT_MNTENT_MNT_DIR + // Open mounts file + mountPointsFile = ::setmntent("/proc/mounts", "r"); + if(mountPointsFile == 0) + { + mountPointsFile = ::setmntent("/etc/mtab", "r"); + } + if(mountPointsFile == 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + + try + { + // Read all the entries, and put them in the set + struct mntent *entry = 0; + while((entry = ::getmntent(mountPointsFile)) != 0) + { + BOX_TRACE("Found mount point at " << entry->mnt_dir); + mountPoints.insert(std::string(entry->mnt_dir)); + } + + // Close mounts file + ::endmntent(mountPointsFile); + } + catch(...) + { + ::endmntent(mountPointsFile); + throw; + } +#else // ! HAVE_STRUCT_MNTENT_MNT_DIR + // Open mounts file + mountPointsFile = ::fopen("/etc/mnttab", "r"); + if(mountPointsFile == 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + + try + { + // Read all the entries, and put them in the set + struct mnttab entry; + while(getmntent(mountPointsFile, &entry) == 0) + { + BOX_TRACE("Found mount point at " << entry.mnt_mountp); + mountPoints.insert(std::string(entry.mnt_mountp)); + } + + // Close mounts file + ::fclose(mountPointsFile); + } + catch(...) + { + ::fclose(mountPointsFile); + throw; + } +#endif // HAVE_STRUCT_MNTENT_MNT_DIR + // Check sorting and that things are as we expect + ASSERT(mountPoints.size() > 0); +#ifndef BOX_RELEASE_BUILD + { + std::set::reverse_iterator i(mountPoints.rbegin()); + ASSERT(*i == "/"); + } +#endif // n BOX_RELEASE_BUILD +#endif // n HAVE_STRUCT_STATFS_F_MNTONNAME || n HAVE_STRUCT_STATVFS_F_MNTONNAME +#endif // HAVE_MOUNTS + + // Then... go through each of the entries in the configuration, + // making sure there's a directory created for it. + std::vector locNames = + rLocationsConf.GetSubConfigurationNames(); + + for(std::vector::iterator + pLocName = locNames.begin(); + pLocName != locNames.end(); + pLocName++) + { + const Configuration& rConfig( + rLocationsConf.GetSubConfiguration(*pLocName)); + BOX_TRACE("new location: " << *pLocName); + + // Create a record for it + std::auto_ptr apLoc(new Location); + + 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); + + // 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 + BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname); + int64_t oid = 0; + if(en != 0) + { + oid = en->GetObjectID(); + + // Delete the entry from the directory, so we get a list of + // unused root directories at the end of this. + dir.DeleteEntry(oid); + } + + // Do a fsstat on the pathname to find out which mount it's on + { + +#if defined HAVE_STRUCT_STATFS_F_MNTONNAME || defined HAVE_STRUCT_STATVFS_F_MNTONNAME || defined WIN32 + + // 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) +#else // HAVE_STRUCT_STATVFS_F_MNTONNAME + struct statfs s; + if(::statfs(apLoc->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; + } + + // Where the filesystem is mounted + std::string mountName(s.f_mntonname); + +#else // !HAVE_STRUCT_STATFS_F_MNTONNAME && !WIN32 + + // Warn in logs if the directory isn't absolute + if(apLoc->mPath[0] != '/') + { + BOX_WARNING("Location path '" + << apLoc->mPath + << "' is not absolute"); + } + // Go through the mount points found, and find a suitable one + std::string mountName("/"); + { + std::set::const_iterator i(mountPoints.begin()); + BOX_TRACE(mountPoints.size() + << " potential mount points"); + for(; i != mountPoints.end(); ++i) + { + // Compare first n characters with the filename + // 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) + { + // Match + mountName = *i; + break; + } + } + BOX_TRACE("mount point chosen for " + << apLoc->mPath << " is " + << mountName); + } + +#endif + + // Got it? + std::map::iterator f(mounts.find(mountName)); + if(f != mounts.end()) + { + // Yes -- store the index + apLoc->mIDMapIndex = f->second; + } + else + { + // No -- new index + apLoc->mIDMapIndex = numIDMaps; + mounts[mountName] = numIDMaps; + + // Store the mount name + mIDMapMounts.push_back(mountName); + + // Increment number of maps + ++numIDMaps; + } + } + + // Does this exist on the server? + if(en == 0) + { + // Doesn't exist, so it has to be created on the server. Let's go! + // First, get the directory's attributes and modification time + box_time_t attrModTime = 0; + BackupClientFileAttributes attr; + try + { + attr.ReadAttributes(apLoc->mPath.c_str(), + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime /* get the attribute modification time */); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to get attributes " + "for path '" << apLoc->mPath + << "', skipping location '" << + apLoc->mName << "'"); + continue; + } + + // Execute create directory command + try + { + MemBlockStream attrStream(attr); + std::auto_ptr + dirCreate(connection.QueryCreateDirectory( + BackupProtocolClientListDirectory::RootDirectory, + attrModTime, dirname, attrStream)); + + // Object ID for later creation + oid = dirCreate->GetObjectID(); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to create remote " + "directory '/" << apLoc->mName << + "', skipping location '" << + apLoc->mName << "'"); + continue; + } + + } + + // 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); + + // Push it back on the vector of locations + mLocations.push_back(apLoc.release()); + } + catch (std::exception &e) + { + BOX_ERROR("Failed to configure location '" + << apLoc->mName << "' path '" + << apLoc->mPath << "': " << e.what() << + ": please check for previous errors"); + throw; + } + catch(...) + { + BOX_ERROR("Failed to configure location '" + << apLoc->mName << "' path '" + << apLoc->mPath << "': please check for " + "previous errors"); + throw; + } + } + + // Any entries in the root directory which need deleting? + if(dir.GetNumberOfEntries() > 0 && + mDeleteRedundantLocationsAfter == 0) + { + BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " + "in root directory found, but will not delete because " + "DeleteRedundantLocationsAfter = 0"); + } + else if(dir.GetNumberOfEntries() > 0) + { + box_time_t now = GetCurrentBoxTime(); + + // This should reset the timer if the list of unused + // locations changes, but it will not if the number of + // unused locations does not change, but the locations + // do change, e.g. one mysteriously appears and another + // mysteriously appears. (FIXME) + if (dir.GetNumberOfEntries() != mUnusedRootDirEntries.size() || + mDeleteUnusedRootDirEntriesAfter == 0) + { + mDeleteUnusedRootDirEntriesAfter = now + + SecondsToBoxTime(mDeleteRedundantLocationsAfter); + } + + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + + BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " + "in root directory found, will delete from store " + "after " << secs << " seconds."); + + // Store directories in list of things to delete + mUnusedRootDirEntries.clear(); + BackupStoreDirectory::Iterator iter(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = iter.Next()) != 0) + { + // Add name to list + BackupStoreFilenameClear clear(en->GetName()); + const std::string &name(clear.GetClearFilename()); + mUnusedRootDirEntries.push_back( + std::pair + (en->GetObjectID(), name)); + // Log this + BOX_INFO("Unused location in root: " << name); + } + ASSERT(mUnusedRootDirEntries.size() > 0); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupIDMapsForSync() +// Purpose: Sets up ID maps for the sync process -- make sure they're all there +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +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 +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::FillIDMapVector(std::vector &) +// Purpose: Fills the vector with the right number of empty ID maps +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::FillIDMapVector(std::vector &rVector, bool NewMaps) +{ + ASSERT(rVector.size() == 0); + rVector.reserve(mIDMapMounts.size()); + + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + // Create the object + BackupClientInodeToIDMap *pmap = new BackupClientInodeToIDMap(); + try + { + // Get the base filename of this map + std::string filename; + MakeMapBaseName(l, filename); + + // If it's a new one, add a suffix + if(NewMaps) + { + filename += ".n"; + } + + // 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())) + { + pmap->OpenEmpty(); + } + else + { + // Open the map + pmap->Open(filename.c_str(), !NewMaps /* read only */, NewMaps /* create new */); + } + + // Store on vector + rVector.push_back(pmap); + } + catch(...) + { + delete pmap; + throw; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteCorruptBerkelyDbFiles() +// Purpose: Delete the Berkely db files from disc after they have been corrupted. +// Created: 14/9/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteCorruptBerkelyDbFiles() +{ + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + // Get the base filename of this map + std::string filename; + MakeMapBaseName(l, filename); + + // Delete the file + BOX_TRACE("Deleting " << filename); + ::unlink(filename.c_str()); + + // Add a suffix for the new map + filename += ".n"; + + // Delete that too + BOX_TRACE("Deleting " << filename); + ::unlink(filename.c_str()); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeMapBaseName(unsigned int, std::string &) +// Purpose: Makes the base name for a inode map +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const +{ + // Get the directory for the maps + const Configuration &config(GetConfiguration()); + std::string dir(config.GetKeyValue("DataDirectory")); + + // Make a leafname + std::string leaf(mIDMapMounts[MountNumber]); + for(unsigned int z = 0; z < leaf.size(); ++z) + { + if(leaf[z] == DIRECTORY_SEPARATOR_ASCHAR) + { + leaf[z] = '_'; + } + } + + // Build the final filename + rNameOut = dir + DIRECTORY_SEPARATOR "mnt" + leaf; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CommitIDMapsAfterSync() +// Purpose: Commits the new ID maps, so the 'new' maps are now the 'current' maps. +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +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); + + // Then move the old maps into the new places + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + std::string target; + MakeMapBaseName(l, target); + std::string newmap(target + ".n"); + + // Try to rename +#ifdef WIN32 + // win32 rename doesn't overwrite existing files + ::remove(target.c_str()); +#endif + if(::rename(newmap.c_str(), target.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to rename ID map: " << + newmap << " to " << target); + THROW_EXCEPTION(CommonException, OSFileError) + } + } + +#endif +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteIDMapVector(std::vector &) +// Purpose: Deletes the contents of a vector of ID maps +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteIDMapVector(std::vector &rVector) +{ + while(!rVector.empty()) + { + // Pop off list + BackupClientInodeToIDMap *toDel = rVector.back(); + rVector.pop_back(); + + // Close and delete + toDel->Close(); + delete toDel; + } + ASSERT(rVector.size() == 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::FindLocationPathName(const std::string &, std::string &) const +// Purpose: Tries to find the path of the root of a backup location. Returns true (and path in rPathOut) +// if it can be found, false otherwise. +// Created: 12/11/03 +// +// -------------------------------------------------------------------------- +bool BackupDaemon::FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const +{ + // Search for the location + for(std::vector::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i) + { + if((*i)->mName == rLocationName) + { + rPathOut = (*i)->mPath; + return true; + } + } + + // Didn't find it + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetState(int) +// Purpose: Record current action of daemon, and update process title to reflect this +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetState(int State) +{ + // Two little checks + if(State == mState) return; + if(State < 0) return; + + // Update + mState = State; + + // Set process title + const static char *stateText[] = {"idle", "connected", "error -- waiting for retry", "over limit on server -- not backing up"}; + SetProcessTitle(stateText[State]); + + // If there's a command socket connected, then inform it -- disconnecting from the + // command socket if there's an error + + char newState[64]; + sprintf(newState, "state %d", State); + std::string message = newState; + + message += "\n"; + + if(!mapCommandSocketInfo.get()) + { + return; + } + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + return; + } + + // Something connected to the command socket, tell it about the new state + try + { + mapCommandSocketInfo->mpConnectedSocket->Write(message.c_str(), + message.length()); + } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write state to command socket: " << + ce.what()); + CloseCommandConnection(); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write state to command socket: " << + e.what()); + CloseCommandConnection(); + } + catch(...) + { + BOX_ERROR("Failed to write state to command socket: " + "unknown error"); + CloseCommandConnection(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::TouchFileInWorkingDir(const char *) +// Purpose: Make sure a zero length file of the name exists in the working directory. +// Use for marking times of events in the filesystem. +// Created: 21/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::TouchFileInWorkingDir(const char *Filename) +{ + // Filename + const Configuration &config(GetConfiguration()); + std::string fn(config.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); + 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); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::NotifySysadmin(int) +// Purpose: Run the script to tell the sysadmin about events +// which need attention. +// Created: 25/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::NotifySysadmin(SysadminNotifier::EventCode Event) +{ + static const char *sEventNames[] = + { + "store-full", + "read-error", + "backup-error", + "backup-start", + "backup-finish", + "backup-ok", + 0 + }; + + // BOX_TRACE("sizeof(sEventNames) == " << sizeof(sEventNames)); + // BOX_TRACE("sizeof(*sEventNames) == " << sizeof(*sEventNames)); + // BOX_TRACE("NotifyEvent__MAX == " << NotifyEvent__MAX); + ASSERT((sizeof(sEventNames)/sizeof(*sEventNames)) == SysadminNotifier::MAX + 1); + + if(Event < 0 || Event >= SysadminNotifier::MAX) + { + BOX_ERROR("BackupDaemon::NotifySysadmin() called for " + "invalid event code " << Event); + THROW_EXCEPTION(BackupStoreException, + BadNotifySysadminEventCode); + } + + BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << + sEventNames[Event]); + + if(!GetConfiguration().KeyExists("NotifyAlways") || + !GetConfiguration().GetKeyValueBool("NotifyAlways")) + { + // Don't send lots of repeated messages + // Note: backup-start and backup-finish will always be + // logged, because mLastNotifiedEvent is never set to + // these values and therefore they are never "duplicates". + if(mLastNotifiedEvent == Event) + { + if(Event == SysadminNotifier::BackupOK) + { + BOX_INFO("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + else + { + BOX_WARNING("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + return; + } + } + + // Is there a notification script? + const Configuration &conf(GetConfiguration()); + if(!conf.KeyExists("NotifyScript")) + { + // Log, and then return + if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) + { + BOX_INFO("Not notifying administrator about event " + << sEventNames[Event] << ", set NotifyScript " + "to do this in future"); + } + return; + } + + // Script to run + std::string script(conf.GetKeyValue("NotifyScript") + " " + + sEventNames[Event] + " \"" + GetConfigFileName() + "\""); + + // Log what we're about to do + BOX_INFO("About to notify administrator about event " + << sEventNames[Event] << ", running script '" << script << "'"); + + // Then do it + int returnCode = ::system(script.c_str()); + if(returnCode != 0) + { + BOX_WARNING("Notify script returned error code: " << + returnCode << " (" << script << ")"); + } + else if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) + { + mLastNotifiedEvent = Event; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &) +// Purpose: Deletes any unused entries in the root directory, if they're scheduled to be deleted. +// Created: 13/5/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext) +{ + if(mUnusedRootDirEntries.empty()) + { + BOX_INFO("Not deleting unused entries - none in list"); + return; + } + + if(mDeleteUnusedRootDirEntriesAfter == 0) + { + BOX_INFO("Not deleting unused entries - " + "zero delete time (bad)"); + return; + } + + // Check time + box_time_t now = GetCurrentBoxTime(); + if(now < mDeleteUnusedRootDirEntriesAfter) + { + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + BOX_INFO("Not deleting unused entries - too early (" + << secs << " seconds remaining)"); + return; + } + + // Entries to delete, and it's the right time to do so... + BOX_NOTICE("Deleting unused locations from store root..."); + BackupProtocolClient &connection(rContext.GetConnection()); + for(std::vector >::iterator + i(mUnusedRootDirEntries.begin()); + i != mUnusedRootDirEntries.end(); ++i) + { + connection.QueryDeleteDirectory(i->first); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->first, i->second); + } + + // Reset state + mDeleteUnusedRootDirEntriesAfter = 0; + mUnusedRootDirEntries.clear(); +} + +// -------------------------------------------------------------------------- + +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 + +} loc_StreamFormat; + +// -------------------------------------------------------------------------- +// +// 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 +// +// -------------------------------------------------------------------------- +BackupDaemon::CommandSocketInfo::CommandSocketInfo() + : mpGetLine(0) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CommandSocketInfo::~CommandSocketInfo() +// Purpose: Destructor +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +BackupDaemon::CommandSocketInfo::~CommandSocketInfo() +{ + if(mpGetLine) + { + delete mpGetLine; + mpGetLine = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SerializeStoreObjectInfo( +// box_time_t theLastSyncTime, +// box_time_t theNextSyncTime) +// Purpose: Serializes remote directory and file information +// into a stream of bytes, using an Archive +// abstraction. +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- + +static const int STOREOBJECTINFO_MAGIC_ID_VALUE = 0x7777525F; +static const std::string STOREOBJECTINFO_MAGIC_ID_STRING = "BBACKUPD-STATE"; +static const int STOREOBJECTINFO_VERSION = 2; + +bool BackupDaemon::SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const +{ + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + return false; + } + + std::string StoreObjectInfoFile = + GetConfiguration().GetKeyValue("StoreObjectInfoFile"); + + if(StoreObjectInfoFile.size() <= 0) + { + return false; + } + + bool created = false; + + try + { + FileStream aFile(StoreObjectInfoFile.c_str(), + O_WRONLY | O_CREAT | O_TRUNC); + created = true; + + Archive anArchive(aFile, 0); + + anArchive.Write(STOREOBJECTINFO_MAGIC_ID_VALUE); + anArchive.Write(STOREOBJECTINFO_MAGIC_ID_STRING); + anArchive.Write(STOREOBJECTINFO_VERSION); + anArchive.Write(GetLoadedConfigModifiedTime()); + anArchive.Write(mClientStoreMarker); + anArchive.Write(theLastSyncTime); + anArchive.Write(theNextSyncTime); + + // + // + // + int64_t iCount = mLocations.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + { + ASSERT(mLocations[v]); + mLocations[v]->Serialize(anArchive); + } + + // + // + // + iCount = mIDMapMounts.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + anArchive.Write(mIDMapMounts[v]); + + // + // + // + iCount = mUnusedRootDirEntries.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + { + anArchive.Write(mUnusedRootDirEntries[v].first); + anArchive.Write(mUnusedRootDirEntries[v].second); + } + + if (iCount > 0) + { + anArchive.Write(mDeleteUnusedRootDirEntriesAfter); + } + + // + // + // + aFile.Close(); + BOX_INFO("Saved store object info file version " << + STOREOBJECTINFO_VERSION << " (" << + StoreObjectInfoFile << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": " << e.what()); + } + catch(...) + { + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": unknown error"); + } + + return created; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeserializeStoreObjectInfo( +// box_time_t & theLastSyncTime, +// box_time_t & theNextSyncTime) +// Purpose: Deserializes remote directory and file information +// from a stream of bytes, using an Archive +// abstraction. +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +bool BackupDaemon::DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime) +{ + // + // + // + DeleteAllLocations(); + + // + // + // + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + return false; + } + + std::string StoreObjectInfoFile = + GetConfiguration().GetKeyValue("StoreObjectInfoFile"); + + if(StoreObjectInfoFile.size() <= 0) + { + return false; + } + + try + { + FileStream aFile(StoreObjectInfoFile.c_str(), O_RDONLY); + Archive anArchive(aFile, 0); + + // + // see if the content looks like a valid serialised archive + // + int iMagicValue = 0; + anArchive.Read(iMagicValue); + + if(iMagicValue != STOREOBJECTINFO_MAGIC_ID_VALUE) + { + BOX_WARNING("Store object info file " + "is not a valid or compatible serialised " + "archive. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + return false; + } + + // + // get a bit optimistic and read in a string identifier + // + std::string strMagicValue; + anArchive.Read(strMagicValue); + + if(strMagicValue != STOREOBJECTINFO_MAGIC_ID_STRING) + { + BOX_WARNING("Store object info file " + "is not a valid or compatible serialised " + "archive. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + return false; + } + + // + // check if we are loading some future format + // version by mistake + // + int iVersion = 0; + anArchive.Read(iVersion); + + if(iVersion != STOREOBJECTINFO_VERSION) + { + BOX_WARNING("Store object info file " + "version " << iVersion << " unsupported. " + "Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + return false; + } + + // + // check if this state file is even valid + // for the loaded bbackupd.conf file + // + box_time_t lastKnownConfigModTime; + anArchive.Read(lastKnownConfigModTime); + + if(lastKnownConfigModTime != GetLoadedConfigModifiedTime()) + { + BOX_WARNING("Store object info file " + "out of date. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + return false; + } + + // + // this is it, go at it + // + anArchive.Read(mClientStoreMarker); + anArchive.Read(theLastSyncTime); + anArchive.Read(theNextSyncTime); + + // + // + // + int64_t iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + Location* pLocation = new Location; + if(!pLocation) + { + throw std::bad_alloc(); + } + + pLocation->Deserialize(anArchive); + mLocations.push_back(pLocation); + } + + // + // + // + iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + std::string strItem; + anArchive.Read(strItem); + + mIDMapMounts.push_back(strItem); + } + + // + // + // + iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + int64_t anId; + anArchive.Read(anId); + + std::string aName; + anArchive.Read(aName); + + mUnusedRootDirEntries.push_back(std::pair(anId, aName)); + } + + if (iCount > 0) + anArchive.Read(mDeleteUnusedRootDirEntriesAfter); + + // + // + // + aFile.Close(); + BOX_INFO("Loaded store object info file version " << iVersion + << " (" << StoreObjectInfoFile << ")"); + + return true; + } + catch(std::exception &e) + { + BOX_ERROR("Internal error reading store object info file: " + << StoreObjectInfoFile << ": " << e.what()); + } + catch(...) + { + BOX_ERROR("Internal error reading store object info file: " + << StoreObjectInfoFile << ": unknown error"); + } + + DeleteAllLocations(); + + mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; + theLastSyncTime = 0; + theNextSyncTime = 0; + + BOX_WARNING("Store object info file is missing, not accessible, " + "or inconsistent. Will re-cache from store. " + "(" << StoreObjectInfoFile << ")"); + + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteStoreObjectInfo() +// Purpose: Deletes the serialised state file, to prevent us +// from using it again if a backup is interrupted. +// +// Created: 2006/02/12 +// +// -------------------------------------------------------------------------- + +bool BackupDaemon::DeleteStoreObjectInfo() const +{ + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + return false; + } + + std::string storeObjectInfoFile(GetConfiguration().GetKeyValue("StoreObjectInfoFile")); + + // Check to see if the file exists + if(!FileExists(storeObjectInfoFile.c_str())) + { + // File doesn't exist -- so can't be deleted. But something + // isn't quite right, so log a message + BOX_WARNING("StoreObjectInfoFile did not exist when it " + "was supposed to: " << storeObjectInfoFile); + + // Return true to stop things going around in a loop + return true; + } + + // Actually delete it + if(::unlink(storeObjectInfoFile.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete the old " + "StoreObjectInfoFile: " << storeObjectInfoFile); + return false; + } + + return true; +} diff --git a/bin/bbackupd/BackupDaemon.h b/bin/bbackupd/BackupDaemon.h new file mode 100644 index 00000000..b41c6508 --- /dev/null +++ b/bin/bbackupd/BackupDaemon.h @@ -0,0 +1,525 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.h +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMON__H +#define BACKUPDAEMON__H + +#include +#include +#include + +#include "BackupClientContext.h" +#include "BackupClientDirectoryRecord.h" +#include "BoxTime.h" +#include "Daemon.h" +#include "Logging.h" +#include "Socket.h" +#include "SocketListen.h" +#include "SocketStream.h" +#include "TLSContext.h" + +#include "autogen_BackupProtocolClient.h" + +#ifdef WIN32 + #include "WinNamedPipeListener.h" + #include "WinNamedPipeStream.h" +#endif + +class BackupClientDirectoryRecord; +class BackupClientContext; +class Configuration; +class BackupClientInodeToIDMap; +class ExcludeList; +class IOStreamGetLine; +class Archive; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupDaemon +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupDaemon : public Daemon, ProgressNotifier, LocationResolver, +RunStatusProvider, SysadminNotifier +{ +public: + BackupDaemon(); + ~BackupDaemon(); + +private: + // methods below do partial (specialized) serialization of + // client state only + bool SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const; + bool DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime); + bool DeleteStoreObjectInfo() const; + BackupDaemon(const BackupDaemon &); + +public: + #ifdef WIN32 + // add command-line options to handle Windows services + std::string GetOptionString(); + int ProcessOption(signed int option); + int Main(const std::string &rConfigFileName); + + // This shouldn't be here, but apparently gcc on + // Windows has no idea about inherited methods... + virtual int Main(const char *DefaultConfigFile, int argc, + const char *argv[]) + { + return Daemon::Main(DefaultConfigFile, argc, argv); + } + #endif + + void Run(); + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + virtual void Usage(); + const ConfigurationVerify *GetConfigVerify() const; + + bool FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const; + + enum + { + // Add stuff to this, make sure the textual equivalents in SetState() are changed too. + State_Initialising = -1, + State_Idle = 0, + State_Connected = 1, + State_Error = 2, + State_StorageLimitExceeded = 3 + }; + + int GetState() {return mState;} + + // Allow other classes to call this too + void NotifySysadmin(SysadminNotifier::EventCode Event); + +private: + void Run2(); + +public: + void InitCrypto(); + void RunSyncNowWithExceptionHandling(); + void RunSyncNow(); + void OnBackupStart(); + void OnBackupFinish(); + // TouchFileInWorkingDir is only here for use by Boxi. + // This does NOT constitute an API! + void TouchFileInWorkingDir(const char *Filename); + +private: + void DeleteAllLocations(); + void SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf); + + void DeleteIDMapVector(std::vector &rVector); + void DeleteAllIDMaps() + { + DeleteIDMapVector(mCurrentIDMaps); + DeleteIDMapVector(mNewIDMaps); + } + void FillIDMapVector(std::vector &rVector, bool NewMaps); + + void SetupIDMapsForSync(); + void CommitIDMapsAfterSync(); + void DeleteCorruptBerkelyDbFiles(); + + void MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const; + + void SetState(int State); + + void WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut); + void CloseCommandConnection(); + void SendSyncStartOrFinish(bool SendStart); + + void DeleteUnusedRootDirEntries(BackupClientContext &rContext); + +#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + // For warning user about potential security hole + virtual void SetupInInitialProcess(); +#endif + + 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 mpDirectoryRecord; + int mIDMapIndex; + ExcludeList *mpExcludeFiles; + ExcludeList *mpExcludeDirs; + }; + + typedef const std::vector Locations; + Locations GetLocations() { return mLocations; } + +private: + int mState; // what the daemon is currently doing + + std::vector mLocations; + + std::vector mIDMapMounts; + std::vector mCurrentIDMaps; + std::vector mNewIDMaps; + + int mDeleteRedundantLocationsAfter; + + // For the command socket + class CommandSocketInfo + { + public: + CommandSocketInfo(); + ~CommandSocketInfo(); + private: + CommandSocketInfo(const CommandSocketInfo &); // no copying + CommandSocketInfo &operator=(const CommandSocketInfo &); + public: +#ifdef WIN32 + WinNamedPipeListener<1 /* listen backlog */> mListeningSocket; + std::auto_ptr mpConnectedSocket; +#else + SocketListen mListeningSocket; + std::auto_ptr mpConnectedSocket; +#endif + IOStreamGetLine *mpGetLine; + }; + + // Using a socket? + std::auto_ptr mapCommandSocketInfo; + + // Stop notifications being repeated. + SysadminNotifier::EventCode mLastNotifiedEvent; + + // Unused entries in the root directory wait a while before being deleted + box_time_t mDeleteUnusedRootDirEntriesAfter; // time to delete them + std::vector > mUnusedRootDirEntries; + + int64_t mClientStoreMarker; + bool mStorageLimitExceeded; + bool mReadErrorsOnFilesystemObjects; + box_time_t mLastSyncTime, mNextSyncTime; + box_time_t mCurrentSyncStartTime, mUpdateStoreInterval; + TLSContext mTlsContext; + bool mDeleteStoreObjectInfoFile; + bool mDoSyncForcedByPreviousSyncError; + +public: + bool StopRun() { return this->Daemon::StopRun(); } + bool StorageLimitExceeded() { return mStorageLimitExceeded; } + +private: + bool mLogAllFileAccess; + +public: + ProgressNotifier* GetProgressNotifier() { return mpProgressNotifier; } + LocationResolver* GetLocationResolver() { return mpLocationResolver; } + RunStatusProvider* GetRunStatusProvider() { return mpRunStatusProvider; } + SysadminNotifier* GetSysadminNotifier() { return mpSysadminNotifier; } + void SetProgressNotifier (ProgressNotifier* p) { mpProgressNotifier = p; } + void SetLocationResolver (LocationResolver* p) { mpLocationResolver = p; } + void SetRunStatusProvider(RunStatusProvider* p) { mpRunStatusProvider = p; } + void SetSysadminNotifier (SysadminNotifier* p) { mpSysadminNotifier = p; } + +private: + ProgressNotifier* mpProgressNotifier; + LocationResolver* mpLocationResolver; + RunStatusProvider* mpRunStatusProvider; + SysadminNotifier* mpSysadminNotifier; + + /* ProgressNotifier implementation */ +public: + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) { } + + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Scanning directory: " << rLocalPath); + } + } + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to list directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + #ifdef WIN32 + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is an NTFS junction/reparse point; create " + "a new location if you want to back it up"); + #else + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is a mount point; create a new location " + "if you want to back it up"); + #endif + } + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded file: " << rLocalPath); + } + } + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded directory: " << rLocalPath); + } + } + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Ignoring file of unknown type: " << rLocalPath); + } + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Error reading file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Some files have modification times excessively " + "in the future. Check clock synchronisation. " + "Example file (only one shown): " << rLocalPath); + } + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Skipped file: server is full: " << rLocalPath); + } + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) + { + if (rException.GetType() == CommonException::ExceptionType && + rException.GetSubType() == CommonException::AccessDenied) + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": Access denied"); + } + else + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": caught exception: " << rException.what() + << " (" << rException.GetType() + << "/" << rException.GetSubType() << ")"); + } + } + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) + { + std::ostringstream msgs; + if (type != BackupProtocolClientError::ErrorType) + { + msgs << "unknown error type " << type; + } + else + { + switch(subtype) + { + case BackupProtocolClientError::Err_WrongVersion: + msgs << "WrongVersion"; + break; + case BackupProtocolClientError::Err_NotInRightProtocolPhase: + msgs << "NotInRightProtocolPhase"; + break; + case BackupProtocolClientError::Err_BadLogin: + msgs << "BadLogin"; + break; + case BackupProtocolClientError::Err_CannotLockStoreForWriting: + msgs << "CannotLockStoreForWriting"; + break; + case BackupProtocolClientError::Err_SessionReadOnly: + msgs << "SessionReadOnly"; + break; + case BackupProtocolClientError::Err_FileDoesNotVerify: + msgs << "FileDoesNotVerify"; + break; + case BackupProtocolClientError::Err_DoesNotExist: + msgs << "DoesNotExist"; + break; + case BackupProtocolClientError::Err_DirectoryAlreadyExists: + msgs << "DirectoryAlreadyExists"; + break; + case BackupProtocolClientError::Err_CannotDeleteRoot: + msgs << "CannotDeleteRoot"; + break; + case BackupProtocolClientError::Err_TargetNameExists: + msgs << "TargetNameExists"; + break; + case BackupProtocolClientError::Err_StorageLimitExceeded: + msgs << "StorageLimitExceeded"; + break; + case BackupProtocolClientError::Err_DiffFromFileDoesNotExist: + msgs << "DiffFromFileDoesNotExist"; + break; + case BackupProtocolClientError::Err_DoesNotExistInDirectory: + msgs << "DoesNotExistInDirectory"; + break; + case BackupProtocolClientError::Err_PatchConsistencyError: + msgs << "PatchConsistencyError"; + break; + default: + msgs << "unknown error subtype " << subtype; + } + } + + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": server error: " << msgs.str()); + } + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading complete file: " << rLocalPath); + } + } + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading patch to file: " << rLocalPath); + } + } + virtual void NotifyFileUploadingAttributes( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading new file attributes: " << + rLocalPath); + } + } + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploaded file: " << rLocalPath); + } + } + virtual void NotifyFileSynchronised( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) + { + if (mLogAllFileAccess) + { + BOX_INFO("Synchronised file: " << rLocalPath); + } + } + virtual void NotifyDirectoryDeleted( + int64_t ObjectID, + const std::string& rRemotePath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted directory: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted file: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain, eta " << + BoxTimeToSeconds(finish - elapsed) << "s"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", unknown bytes remaining"); + } + +#ifdef WIN32 + private: + bool mInstallService, mRemoveService, mRunAsService; + std::string mServiceName; +#endif +}; + +#endif // BACKUPDAEMON__H diff --git a/bin/bbackupd/BackupDaemonInterface.h b/bin/bbackupd/BackupDaemonInterface.h new file mode 100644 index 00000000..2a2d8d4b --- /dev/null +++ b/bin/bbackupd/BackupDaemonInterface.h @@ -0,0 +1,167 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonInterface.h +// Purpose: Interfaces for managing a BackupDaemon +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMONINTERFACE__H +#define BACKUPDAEMONINTERFACE__H + +#include +// #include + +// #include "BackupClientFileAttributes.h" +// #include "BackupStoreDirectory.h" +#include "BoxTime.h" +// #include "MD5Digest.h" +// #include "ReadLoggingStream.h" +// #include "RunStatusProvider.h" + +class Archive; +class BackupClientContext; +class BackupDaemon; + +// -------------------------------------------------------------------------- +// +// Class +// Name: SysadminNotifier +// Purpose: Provides a NotifySysadmin() method to send mail to the sysadmin +// Created: 2005/11/15 +// +// -------------------------------------------------------------------------- +class SysadminNotifier +{ + public: + virtual ~SysadminNotifier() { } + + typedef enum + { + StoreFull = 0, + ReadError, + BackupError, + BackupStart, + BackupFinish, + BackupOK, + MAX + // When adding notifications, remember to add + // strings to NotifySysadmin() + } + EventCode; + + virtual void NotifySysadmin(EventCode Event) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProgressNotifier +// Purpose: Provides methods for the backup library to inform the user +// interface about its progress with the backup +// Created: 2005/11/20 +// +// -------------------------------------------------------------------------- + +class BackupClientContext; +class BackupClientDirectoryRecord; + +class ProgressNotifier +{ + public: + virtual ~ProgressNotifier() { } + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) = 0; + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) = 0; + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) = 0; + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadingAttributes( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) = 0; + virtual void NotifyFileSynchronised( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) = 0; + virtual void NotifyDirectoryDeleted( + int64_t ObjectID, + const std::string& rRemotePath) = 0; + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: LocationResolver +// Purpose: Interface for classes that can resolve locations to paths, +// like BackupDaemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class LocationResolver +{ +public: + virtual ~LocationResolver() { } + virtual bool FindLocationPathName(const std::string &rLocationName, + std::string &rPathOut) const = 0; +}; + +#endif // BACKUPDAEMONINTERFACE__H diff --git a/bin/bbackupd/ClientException.txt b/bin/bbackupd/ClientException.txt new file mode 100644 index 00000000..04f88620 --- /dev/null +++ b/bin/bbackupd/ClientException.txt @@ -0,0 +1,11 @@ + +# NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. + + +EXCEPTION Client 13 + +Internal 0 +AssertFailed 1 +ClockWentBackwards 2 Invalid (negative) sync period: perhaps your clock is going backwards? +FailedToDeleteStoreObjectInfoFile 3 Failed to delete the StoreObjectInfoFile, backup cannot continue safely. +CorruptStoreObjectInfoFile 4 The store object info file contained an invalid value and is probably corrupt. Try deleting it. diff --git a/bin/bbackupd/Makefile.extra b/bin/bbackupd/Makefile.extra new file mode 100644 index 00000000..25ceb1e7 --- /dev/null +++ b/bin/bbackupd/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_ClientException.h autogen_ClientException.cpp: $(MAKEEXCEPTION) ClientException.txt + $(_PERL) $(MAKEEXCEPTION) ClientException.txt + diff --git a/bin/bbackupd/Win32BackupService.cpp b/bin/bbackupd/Win32BackupService.cpp new file mode 100644 index 00000000..6d027abf --- /dev/null +++ b/bin/bbackupd/Win32BackupService.cpp @@ -0,0 +1,48 @@ +// Win32 service functions for Box Backup, by Nick Knight + +#ifdef WIN32 + +#include "Box.h" +#include "BackupDaemon.h" +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreException.h" + +#include "MemLeakFindOn.h" + +#include "Win32BackupService.h" + +Win32BackupService* gpDaemonService = NULL; +extern HANDLE gStopServiceEvent; +extern DWORD gServiceReturnCode; + +unsigned int WINAPI RunService(LPVOID lpParameter) +{ + DWORD retVal = gpDaemonService->WinService((const char*) lpParameter); + gServiceReturnCode = retVal; + SetEvent(gStopServiceEvent); + return retVal; +} + +void TerminateService(void) +{ + gpDaemonService->SetTerminateWanted(); +} + +DWORD Win32BackupService::WinService(const char* pConfigFileName) +{ + DWORD ret; + + if (pConfigFileName != NULL) + { + ret = this->Main(pConfigFileName); + } + else + { + ret = this->Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE); + } + + return ret; +} + +#endif // WIN32 diff --git a/bin/bbackupd/Win32BackupService.h b/bin/bbackupd/Win32BackupService.h new file mode 100644 index 00000000..e7f077f2 --- /dev/null +++ b/bin/bbackupd/Win32BackupService.h @@ -0,0 +1,21 @@ +// Box Backup service daemon implementation by Nick Knight + +#ifndef WIN32BACKUPSERVICE_H +#define WIN32BACKUPSERVICE_H + +#ifdef WIN32 + +class Configuration; +class ConfigurationVerify; +class BackupDaemon; + +class Win32BackupService : public BackupDaemon +{ +public: + DWORD WinService(const char* pConfigFileName); +}; + +#endif // WIN32 + +#endif // WIN32BACKUPSERVICE_H + diff --git a/bin/bbackupd/Win32ServiceFunctions.cpp b/bin/bbackupd/Win32ServiceFunctions.cpp new file mode 100644 index 00000000..2df914a7 --- /dev/null +++ b/bin/bbackupd/Win32ServiceFunctions.cpp @@ -0,0 +1,384 @@ +//*************************************************************** +// From the book "Win32 System Services: The Heart of Windows 98 +// and Windows 2000" +// by Marshall Brain +// Published by Prentice Hall +// Copyright 1995 Prentice Hall. +// +// This code implements the Windows API Service interface +// for the Box Backup for Windows native port. +// Adapted for Box Backup by Nick Knight. +//*************************************************************** + +#ifdef WIN32 + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif +#ifdef HAVE_PROCESS_H + #include +#endif + +extern void TerminateService(void); +extern unsigned int WINAPI RunService(LPVOID lpParameter); + +// Global variables + +TCHAR* gServiceName = TEXT("Box Backup Service"); +SERVICE_STATUS gServiceStatus; +SERVICE_STATUS_HANDLE gServiceStatusHandle = 0; +HANDLE gStopServiceEvent = 0; +DWORD gServiceReturnCode = 0; + +#define SERVICE_NAME "boxbackup" + +void ShowMessage(char *s) +{ + MessageBox(0, s, "Box Backup Message", + MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); +} + +void ErrorHandler(char *s, DWORD err) +{ + char buf[256]; + memset(buf, 0, sizeof(buf)); + _snprintf(buf, sizeof(buf)-1, "%s: %s", s, + GetErrorMessage(err).c_str()); + BOX_ERROR(buf); + MessageBox(0, buf, "Error", + MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); + ExitProcess(err); +} + +void WINAPI ServiceControlHandler( DWORD controlCode ) +{ + switch ( controlCode ) + { + case SERVICE_CONTROL_INTERROGATE: + break; + + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + Beep(1000,100); + TerminateService(); + gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + SetEvent(gStopServiceEvent); + return; + + case SERVICE_CONTROL_PAUSE: + break; + + case SERVICE_CONTROL_CONTINUE: + break; + + default: + if ( controlCode >= 128 && controlCode <= 255 ) + // user defined control code + break; + else + // unrecognised control code + break; + } + + SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); +} + +// ServiceMain is called when the SCM wants to +// start the service. When it returns, the service +// has stopped. It therefore waits on an event +// just before the end of the function, and +// that event gets set when it is time to stop. +// It also returns on any error because the +// service cannot start if there is an eror. + +static char* spConfigFileName; + +VOID ServiceMain(DWORD argc, LPTSTR *argv) +{ + // initialise service status + gServiceStatus.dwServiceType = SERVICE_WIN32; + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + gServiceStatus.dwControlsAccepted = 0; + gServiceStatus.dwWin32ExitCode = NO_ERROR; + gServiceStatus.dwServiceSpecificExitCode = NO_ERROR; + gServiceStatus.dwCheckPoint = 0; + gServiceStatus.dwWaitHint = 0; + + gServiceStatusHandle = RegisterServiceCtrlHandler(gServiceName, + ServiceControlHandler); + + if (gServiceStatusHandle) + { + // service is starting + gServiceStatus.dwCurrentState = SERVICE_START_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // do initialisation here + gStopServiceEvent = CreateEvent(0, TRUE, FALSE, 0); + if (!gStopServiceEvent) + { + gServiceStatus.dwControlsAccepted &= + ~(SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_SHUTDOWN); + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + return; + } + + HANDLE ourThread = (HANDLE)_beginthreadex( + NULL, + 0, + RunService, + spConfigFileName, + CREATE_SUSPENDED, + NULL); + + SetThreadPriority(ourThread, THREAD_PRIORITY_LOWEST); + ResumeThread(ourThread); + + // we are now running so tell the SCM + gServiceStatus.dwControlsAccepted |= + (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + gServiceStatus.dwCurrentState = SERVICE_RUNNING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // do cleanup here + WaitForSingleObject(gStopServiceEvent, INFINITE); + CloseHandle(gStopServiceEvent); + gStopServiceEvent = 0; + + // service was stopped + gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // service is now stopped + gServiceStatus.dwControlsAccepted &= + ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + + if (gServiceReturnCode != 0) + { + gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + gServiceStatus.dwServiceSpecificExitCode = gServiceReturnCode; + } + + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + } +} + +int OurService(const char* pConfigFileName) +{ + spConfigFileName = strdup(pConfigFileName); + + SERVICE_TABLE_ENTRY serviceTable[] = + { + { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain }, + { NULL, NULL } + }; + BOOL success; + + // Register with the SCM + success = StartServiceCtrlDispatcher(serviceTable); + + free(spConfigFileName); + spConfigFileName = NULL; + + if (!success) + { + ErrorHandler("Failed to start service. Did you start " + "Box Backup from the Service Control Manager? " + "(StartServiceCtrlDispatcher)", GetLastError()); + return 1; + } + + return 0; +} + +int InstallService(const char* pConfigFileName, const std::string& rServiceName) +{ + if (pConfigFileName != NULL) + { + EMU_STRUCT_STAT st; + + if (emu_stat(pConfigFileName, &st) != 0) + { + BOX_LOG_SYS_ERROR("Failed to open configuration file " + "'" << pConfigFileName << "'"); + return 1; + } + + if (!(st.st_mode & S_IFREG)) + { + + BOX_ERROR("Failed to open configuration file '" << + pConfigFileName << "': not a file"); + return 1; + } + } + + SC_HANDLE scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); + + if (!scm) + { + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); + return 1; + } + + char cmd[MAX_PATH]; + GetModuleFileName(NULL, cmd, sizeof(cmd)-1); + cmd[sizeof(cmd)-1] = 0; + + std::string cmdWithArgs(cmd); + cmdWithArgs += " -s -S \"" + rServiceName + "\""; + + if (pConfigFileName != NULL) + { + cmdWithArgs += " \""; + cmdWithArgs += pConfigFileName; + cmdWithArgs += "\""; + } + + std::string serviceDesc = "Box Backup (" + rServiceName + ")"; + + SC_HANDLE newService = CreateService( + scm, + rServiceName.c_str(), + serviceDesc.c_str(), + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + cmdWithArgs.c_str(), + 0,0,0,0,0); + + DWORD err = GetLastError(); + CloseServiceHandle(scm); + + if (!newService) + { + switch (err) + { + case ERROR_SERVICE_EXISTS: + { + BOX_ERROR("Failed to create Box Backup " + "service: it already exists"); + } + break; + + case ERROR_SERVICE_MARKED_FOR_DELETE: + { + BOX_ERROR("Failed to create Box Backup " + "service: it is waiting to be deleted"); + } + break; + + case ERROR_DUPLICATE_SERVICE_NAME: + { + BOX_ERROR("Failed to create Box Backup " + "service: a service with this name " + "already exists"); + } + break; + + default: + { + BOX_ERROR("Failed to create Box Backup " + "service: error " << + GetErrorMessage(GetLastError())); + } + } + + return 1; + } + + BOX_INFO("Created Box Backup service"); + + SERVICE_DESCRIPTION desc; + desc.lpDescription = "Backs up your data files over the Internet"; + + if (!ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION, + &desc)) + { + BOX_WARNING("Failed to set description for Box Backup " + "service: " << GetErrorMessage(GetLastError())); + } + + CloseServiceHandle(newService); + + return 0; +} + +int RemoveService(const std::string& rServiceName) +{ + SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); + + if (!scm) + { + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); + return 1; + } + + SC_HANDLE service = OpenService(scm, rServiceName.c_str(), + SERVICE_ALL_ACCESS|DELETE); + DWORD err = GetLastError(); + CloseServiceHandle(scm); + + if (!service) + { + if (err == ERROR_SERVICE_DOES_NOT_EXIST || + err == ERROR_IO_PENDING) + // hello microsoft? anyone home? + { + BOX_ERROR("Failed to open Box Backup service: " + "not installed or not found"); + } + else + { + BOX_ERROR("Failed to open Box Backup service: " << + GetErrorMessage(err)); + } + return 1; + } + + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) + { + err = GetLastError(); + if (err != ERROR_SERVICE_NOT_ACTIVE) + { + BOX_WARNING("Failed to stop Box Backup service: " << + GetErrorMessage(err)); + } + } + + BOOL deleted = DeleteService(service); + err = GetLastError(); + CloseServiceHandle(service); + + if (deleted) + { + BOX_INFO("Box Backup service deleted"); + return 0; + } + else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) + { + BOX_ERROR("Failed to remove Box Backup service: " + "it is already being deleted"); + } + else + { + BOX_ERROR("Failed to remove Box Backup service: " << + GetErrorMessage(err)); + } + + return 1; +} + +#endif // WIN32 diff --git a/bin/bbackupd/Win32ServiceFunctions.h b/bin/bbackupd/Win32ServiceFunctions.h new file mode 100644 index 00000000..e04c368f --- /dev/null +++ b/bin/bbackupd/Win32ServiceFunctions.h @@ -0,0 +1,19 @@ +//*************************************************************** +// From the book "Win32 System Services: The Heart of Windows 98 +// and Windows 2000" +// by Marshall Brain +// Published by Prentice Hall +// Copyright 1995 Prentice Hall. +// +// This code implements the Windows API Service interface +// for the Box Backup for Windows native port. +//*************************************************************** + +#ifndef WIN32SERVICEFUNCTIONS_H +#define WIN32SERVICEFUNCTIONS_H + +int RemoveService (const std::string& rServiceName); +int InstallService (const char* pConfigFilePath, const std::string& rServiceName); +int OurService (const char* pConfigFileName); + +#endif diff --git a/bin/bbackupd/bbackupd-config.in b/bin/bbackupd/bbackupd-config.in new file mode 100755 index 00000000..98dc8b6e --- /dev/null +++ b/bin/bbackupd/bbackupd-config.in @@ -0,0 +1,601 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +sub error_print_usage +{ + print <<__E; + +Setup bbackupd config utility. + +Bad command line parameters. +Usage: + bbackupd-config config-dir backup-mode account-num server-hostname + working-dir [backup directories] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + backup-mode is lazy or snapshot: + lazy mode runs continously, uploading files over a specified age + snapshot mode uploads a snapshot of the filesystem when instructed + explicitly, using bbackupctl sync + account-num (hexdecimal) and server-hostname + are supplied by the server administrator + working-dir is usually @localstatedir_expanded@/bbackupd + backup directories is list of directories to back up + +__E + print "=========\nERROR:\n",$_[0],"\n\n" if $_[0] ne ''; + exit(1); +} + +# check and get command line parameters +if($#ARGV < 4) +{ + error_print_usage(); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# default locations +my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbackupd.conf'; + +# command line parameters +my ($config_dir,$backup_mode,$account_num,$server,$working_dir,@tobackup) = @ARGV; + +# check backup mode is valid +if($backup_mode ne 'lazy' && $backup_mode ne 'snapshot') +{ + error_print_usage("ERROR: backup mode must be 'lazy' or 'snapshot'"); +} + +# check server exists +{ + my @r = gethostbyname($server); + if($#r < 0) + { + error_print_usage("Backup server specified as '$server', but it could not found.\n(A test DNS lookup failed -- check arguments)"); + } +} + +if($working_dir !~ m~\A/~) +{ + error_print_usage("Working directory $working_dir is not specified as an absolute path"); +} + +# ssl stuff +my $private_key = "$config_dir/bbackupd/$account_num-key.pem"; +my $certificate_request = "$config_dir/bbackupd/$account_num-csr.pem"; +my $certificate = "$config_dir/bbackupd/$account_num-cert.pem"; +my $ca_root_cert = "$config_dir/bbackupd/serverCA.pem"; + +# encryption keys +my $enc_key_file = "$config_dir/bbackupd/$account_num-FileEncKeys.raw"; + +# other files +my $config_file = "$config_dir/bbackupd.conf"; +my $notify_script = "$config_dir/bbackupd/NotifySysadmin.sh"; + +# check that the directories are allowable +for(@tobackup) +{ + if($_ eq '/') + { + die "It is not recommended that you backup the root directory of your disc"; + } + if($_ !~ m/\A\//) + { + die "Directory $_ is not specified as an absolute path"; + } + if(!-d $_) + { + die "$_ is not a directory"; + } +} + +# summarise configuration + +print <<__E; + +Setup bbackupd config utility. + +Configuration: + Writing configuration file: $config_file + Account: $account_num + Server hostname: $server + Directories to back up: +__E +print ' ',$_,"\n" for(@tobackup); +print <<__E; + +Note: If other file systems are mounted inside these directories, then +they will NOT be backed up. You will have to create separate locations for +any mounted filesystems inside your backup locations. + +__E + +# create directories +if(!-d $config_dir) +{ + printf "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +if(!-d "$config_dir/bbackupd") +{ + printf "Creating $config_dir/bbackupd\n"; + mkdir "$config_dir/bbackupd",0700 or die "Can't create $config_dir/bbackupd"; +} + +if(!-d "$working_dir") +{ + printf "Creating $working_dir\n"; + if(!mkdir($working_dir,0700)) + { + die "Couldn't create $working_dir -- create this manually and try again\n"; + } +} + +# generate the private key for the server +if(!-f $private_key) +{ + print "Generating private key...\n"; + if(system("openssl genrsa -out $private_key 2048") != 0) + { + die "Couldn't generate private key." + } +} + +# generate a certificate request +if(!-f $certificate_request) +{ + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); + print CSR <<__E; +. +. +. +. +. +BACKUP-$account_num +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $certificate_request +} + +# generate the key material for the file +if(!-f $enc_key_file) +{ + print "Generating keys for file backup\n"; + if(system("openssl rand -out $enc_key_file 1024") != 0) + { + die "Couldn't generate file backup keys." + } +} + +# write the notify when store full script +print "Writing notify script $notify_script\n"; +open NOTIFY,">$notify_script" or die "Can't open for writing"; + +my $hostname = `hostname`; chomp $hostname; +my $current_username = `whoami`; chomp $current_username; +my $sendmail = `whereis sendmail`; chomp $sendmail; +$sendmail =~ s/\n.\Z//s; +# for Linux style whereis +$sendmail = $1 if $sendmail =~ /^sendmail:\s+([\S]+)/; +# last ditch guess +$sendmail = 'sendmail' if $sendmail !~ m/\S/; + +print NOTIFY <<__EOS; +#!/bin/sh + +# 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. + +SUBJECT="BACKUP PROBLEM on host $hostname" +SENDTO="$current_username" + +if [ "\$1" = "" ]; then + echo "Usage: \$0 " >&2 + exit 2 +elif [ "\$1" = store-full ]; then + $sendmail \$SENDTO <$config_file" or die "Can't open config file for writing"; +print CONFIG <<__E; + +StoreHostname = $server +AccountNumber = 0x$account_num +KeysFile = $enc_key_file + +CertificateFile = $certificate +PrivateKeyFile = $private_key +TrustedCAsFile = $ca_root_cert + +DataDirectory = $working_dir + + +# 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 = $notify_script + +__E + +if($backup_mode eq 'lazy') +{ + # lazy mode configuration + print CONFIG <<__E; + +# 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 + +__E +} +else +{ + # snapshot configuration + print CONFIG <<__E; + +# This configuration file is written for snapshot mode. +# You will need to run bbackupctl to instruct the daemon to upload files. + +AutomaticBackup = no +UpdateStoreInterval = 0 +MinimumFileAge = 0 +MaxUploadWait = 0 + +__E +} + +print CONFIG <<__E; + +# 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 = $working_dir/bbackupd.sock + +# 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 = $working_dir/bbackupd.state + +Server +{ + PidFile = $working_dir/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. + +BackupLocations +{ +__E + +# write the dirs to backup +for my $d (@tobackup) +{ + $d =~ m/\A.(.+)\Z/; + my $n = $1; + $n =~ tr`/`-`; + + my $excludekeys = ''; + if(substr($enc_key_file, 0, length($d)+1) eq $d.'/') + { + $excludekeys = "\t\tExcludeFile = $enc_key_file\n"; + print <<__E; + +NOTE: Keys file has been explicitly excluded from the backup. + +__E + } + + print CONFIG <<__E + $n + { + Path = $d +$excludekeys } +__E +} + +print CONFIG "}\n\n"; +close CONFIG; + +# explain to the user what they need to do next +my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; +my $ctl_daemon_args = ($config_file eq $default_config_location)?'':" -c $config_file"; + +print <<__E; + +=================================================================== + +bbackupd basic configuration complete. + +What you need to do now... + +1) Make a backup of $enc_key_file + This should be a secure offsite backup. + Without it, you cannot restore backups. Everything else can + be replaced. But this cannot. + KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. + +2) Send $certificate_request + to the administrator of the backup server, and ask for it to + be signed. + +3) The administrator will send you two files. Install them as + $certificate + $ca_root_cert + after checking their authenticity. + +4) You may wish to read the configuration file + $config_file + and adjust as appropriate. + + There are some notes in it on excluding files you do not + wish to be backed up. + +5) Review the script + $notify_script + and check that it will email the right person when the store + becomes full. This is important -- when the store is full, no + more files will be backed up. You want to know about this. + +6) Start the backup daemon with the command + @sbindir_expanded@/bbackupd$daemon_args + in /etc/rc.local, or your local equivalent. + Note that bbackupd must run as root. +__E +if($backup_mode eq 'snapshot') +{ + print <<__E; + +7) Set up a cron job to run whenever you want a snapshot of the + file system to be taken. Run the command + @bindir_expanded@/bbackupctl -q$ctl_daemon_args sync +__E +} +print <<__E; + +=================================================================== + +Remember to make a secure, offsite backup of your backup keys, +as described in step 1 above. If you do not, you have no backups. + +__E + diff --git a/bin/bbackupd/bbackupd.cpp b/bin/bbackupd/bbackupd.cpp new file mode 100644 index 00000000..d334a2df --- /dev/null +++ b/bin/bbackupd/bbackupd.cpp @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupd.cpp +// Purpose: main file for backup daemon +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupDaemon.h" +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#ifdef WIN32 + #include "Win32ServiceFunctions.h" + #include "Win32BackupService.h" + + extern Win32BackupService* gpDaemonService; +#endif + +int main(int argc, const char *argv[]) +{ + int ExitCode = 0; + + MAINHELPER_START + + Logging::SetProgramName("bbackupd"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + +#ifdef WIN32 + + EnableBackupRights(); + + gpDaemonService = new Win32BackupService(); + ExitCode = gpDaemonService->Daemon::Main( + BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, + argc, argv); + delete gpDaemonService; + +#else // !WIN32 + + BackupDaemon daemon; + ExitCode = daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); + +#endif // WIN32 + + MAINHELPER_END + + return ExitCode; +} diff --git a/bin/bbackupd/win32/NotifySysAdmin.vbs b/bin/bbackupd/win32/NotifySysAdmin.vbs new file mode 100644 index 00000000..712d92da --- /dev/null +++ b/bin/bbackupd/win32/NotifySysAdmin.vbs @@ -0,0 +1,113 @@ +Dim hostname +Dim account +Dim from +Dim sendto +Dim subjtmpl +Dim subject +Dim body +Dim smtpserver + +Set WshNet = CreateObject("WScript.Network") +hostname = WshNet.ComputerName + +account = "0x1" +from = "boxbackup@" & hostname +sendto = "admin@example.com" +smtpserver = "smtp.example.com" +subjtmpl = "BACKUP PROBLEM on host " & hostname + +Set args = WScript.Arguments + +If args(0) = "store-full" Then + subject = subjtmpl & " (store full)" + body = "The store account for "&hostname&" is full." & vbCrLf & _ + vbCrLf & _ + "=============================" & vbCrLf & _ + "FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "=============================" & vbCrLf & _ + vbCrLf & _ + "Please adjust the limits on account "&account&" on server "&hostname&"." _ + & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "read-error" Then + subject = subjtmpl & " (read errors)" + body = "Errors occurred reading some files or directories " & _ + "for backup on " & hostname & "." & vbCrLf & _ + vbCrLf & _ + "===================================" & vbCrLf & _ + "THESE FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "===================================" & vbCrLf & vbCrLf & _ + "Check the logs on "&hostname&" for the files and " & _ + "directories which caused" & vbCrLf & _ + "these errors, and take appropriate action." & vbCrLf & _ + vbCrLf & _ + "Other files are being backed up." & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "backup-error" Then + subject = subjtmpl & " (read errors)" + body = "An error occurred during the backup on "&hostname&"." _ + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & _ + vbCrLf & _ + "Check the logs on "&hostname&" for more " & _ + "information about the error, " & vbCrLf & _ + "and take appropriate action." & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "backup-start" Or args(0) = "backup-finish" _ + Or args(0) = "backup-ok" Then + ' do nothing for these messages by default +Else + subject = subjtmpl & " (unknown)" + body = "The backup daemon on "&hostname&" reported an unknown error." _ + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & vbCrLf & _ + "Please check the logs on "&hostname&"." & vbCrLf + SendMail from,sendto,subject,body +End If + +Function CheckSMTPSvc() + Set objWMISvc = GetObject("winmgmts:" _ + & "{impersonationLevel=impersonate}!\\.\root\cimv2") + Set colSMTPSvc = objWMISvc.ExecQuery("Select * From Win32_Service " _ + & "Where Name='SMTPSVC'") + If colSMTPSvc.Count > 0 Then + CheckSMTPSvc = True + Else + CheckSMTPSvc = False + End If +End Function + +Sub SendMail(from,sendto,subject,body) + Set objEmail = CreateObject("CDO.Message") + Set WshShell = CreateObject("WScript.Shell") + Dim cdoschema + cdoschema = "http://schemas.microsoft.com/cdo/configuration/" + + With objEmail + .From = from + .To = sendto + .Subject = subject + .TextBody = body + If CheckSMTPSvc = False Then + .Configuration.Fields.Item(cdoschema & "sendusing") = 2 + .Configuration.Fields.Item(cdoschema & "smtpserver") = smtpserver + .Configuration.Fields.Item(cdoschema & "smtpserverport") = 25 + .Configuration.Fields.Update + End If + End With + On Error Resume Next + rc = objEmail.Send + If rc Then + WshShell.Exec "eventcreate /L Application /ID 201 /T WARNING " _ + & "/SO ""Box Backup"" /D """ & args(0) _ + & " notification sent to " & sendto & ".""" + Else + WshShell.Exec "eventcreate /L Application /ID 202 /T ERROR " _ + & "/SO ""Box Backup"" /D ""Failed to send " & args(0) _ + & " notification to " & sendto & ".""" + End If +End Sub diff --git a/bin/bbackupd/win32/bbackupd.conf b/bin/bbackupd/win32/bbackupd.conf new file mode 100644 index 00000000..b0793b29 --- /dev/null +++ b/bin/bbackupd/win32/bbackupd.conf @@ -0,0 +1,238 @@ + +StoreHostname = yourhost +AccountNumber = 0x1 +KeysFile = C:\Program Files\Box Backup\1-FileEncKeys.raw + +CertificateFile = C:\Program Files\Box Backup\1-cert.pem +PrivateKeyFile = C:\Program Files\Box Backup\1-key.pem +TrustedCAsFile = C:\Program Files\Box Backup\serverCA.pem + +DataDirectory = C:\Program Files\Box Backup\bbackupd + +# If you do not install it in the default location - also do not forget to +# change the pid file location (below) + +# 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. +# +# NOTE: You need to edit the following variables in the script before +# enabling it: +# +# account = "accountnumber" +# sendto = "your@email.address" +# smtpserver = "your.smtp.server" +# +# You do not need to set smtpserver if the client has the SMTP Service +# installed, the script will connect directly to the SMTP service. + +NotifyScript = cscript "C:\Program Files\Box Backup\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 = pipe + +# 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 = C:\Program Files\Box Backup\bbackupd\bbackupd.state + +Server +{ + PidFile = C:\Program Files\Box Backup\bbackupd\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. +# +# If a directive ends in Regex, then it is a regular expression rather than a +# explicit full pathname. See: +# +# http://www.boxbackup.org/trac/wiki/Win32Regex +# +# for more information about regular expressions on Windows. +# +# 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/ +# +# Here are some more examples of possible regular expressions for Windows: +# +# ExcludeDir = C:\Documents and Settings\Owner +# ExcludeFilesRegex = \.(mp3|MP3)$ +# AlwaysIncludeFile = C:\Documents and Settings\Owner\My Documents\My Music\veryimportant.mp3 +# ExcludeFilesRegex = \.pst$ +# AlwaysIncludeFilesRegex = \.*backup.*\.pst$ +# ExcludeFilesRegex = \.avi$ +# ExcludeDirsRegex = \\Temporary Internet Files$ +# ExcludeFilesRegex = \\pagefile\.sys$ +# ExcludeDirsRegex = \\pagefile\.sys$ +# ExcludeFilesRegex = \\boot\.ini$ +# ExcludeFilesRegex = \\NTDETECT\.COM$ +# ExcludeFilesRegex = \\UsrClass\.dat\.LOG$ +# ExcludeDirsRegex = \\System Volume Information$ +# ExcludeFilesRegex = \\ntldr$ +# ExcludeDirsRegex = \\Local Settings\\.*\\Cache$ +# ExcludeFilesRegex = \\thumbs\.db$ +# ExcludeFilesRegex = \\~.* +# ExcludeFilesRegex = \\Perflib.* +# ExcludeDirsRegex = \\Application Data$ +# ExcludeFilesRegex = \.bk[~!0-9]$ +# ExcludeFilesRegex = \.iso$ +# ExcludeFilesRegex = \.mpe?[2345g]$ +# ExcludeFilesRegex = \.qbw$ +# AlwaysIncludeFilesRegex = \.qbb$ +# ExcludeFilesRegex = \.tif[f]$ +# ExcludeFilesRegex = \.wmv$ +# ExcludeFilesRegex = \.avi$ +# ExcludeFilesRegex = \.(avi|iso|mp(e)?[g345]|bk[~!1-9]|[mt]bk)$ + +BackupLocations +{ + MyDocuments + { + Path = C:\Documents and Settings\ + } +} + diff --git a/bin/bbackupd/win32/installer.iss b/bin/bbackupd/win32/installer.iss new file mode 100644 index 00000000..20e3addb --- /dev/null +++ b/bin/bbackupd/win32/installer.iss @@ -0,0 +1,51 @@ +; 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/bbackupobjdump/bbackupobjdump.cpp b/bin/bbackupobjdump/bbackupobjdump.cpp new file mode 100644 index 00000000..5b6c44f7 --- /dev/null +++ b/bin/bbackupobjdump/bbackupobjdump.cpp @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupobjdump.cpp +// Purpose: Dump contents of backup objects +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "MainHelper.h" +#include "FileStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "BackupStoreObjectMagic.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: int main(int, const char *[]) +// Purpose: Main fn for bbackupobjdump +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + if(argc != 2) + { + ::printf("Input file not specified.\nUsage: bbackupobjdump \n"); + return 1; + } + + // Open file + FileStream file(argv[1]); + + // Read magic number + uint32_t signature; + if(file.Read(&signature, sizeof(signature)) != sizeof(signature)) + { + // Too short, can't read signature from it + return false; + } + // Seek back to beginning + file.Seek(0, IOStream::SeekType_Absolute); + + // Then... check depending on the type + switch(ntohl(signature)) + { + case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: +#endif + BackupStoreFile::DumpFile(stdout, false, file); + break; + + case OBJECTMAGIC_DIR_MAGIC_VALUE: + { + BackupStoreDirectory dir; + dir.ReadFromStream(file, IOStream::TimeOutInfinite); + dir.Dump(stdout, false); + if(dir.CheckAndFix()) + { + ::printf("Directory didn't pass checking\n"); + } + } + break; + + default: + ::printf("File does not appear to be a valid box backup object.\n"); + break; + } + + MAINHELPER_END +} + diff --git a/bin/bbackupquery/BackupQueries.cpp b/bin/bbackupquery/BackupQueries.cpp new file mode 100644 index 00000000..60724800 --- /dev/null +++ b/bin/bbackupquery/BackupQueries.cpp @@ -0,0 +1,2340 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupQueries.cpp +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_DIRENT_H + #include +#endif + +#include +#include +#include +#include +#include + +#include "BackupClientFileAttributes.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupClientRestore.h" +#include "BackupQueries.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BoxTimeToText.h" +#include "CommonException.h" +#include "Configuration.h" +#include "ExcludeList.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "IOStream.h" +#include "Logging.h" +#include "PathUtils.h" +#include "SelfFlushingStream.h" +#include "TemporaryDirectory.h" +#include "Utils.h" +#include "autogen_BackupProtocolClient.h" + +#include "MemLeakFindOn.h" + +// min() and max() macros from stdlib.h break numeric_limits<>::min(), etc. +#undef min +#undef max + +#define COMPARE_RETURN_SAME 1 +#define COMPARE_RETURN_DIFFERENT 2 +#define COMPARE_RETURN_ERROR 3 +#define COMMAND_RETURN_ERROR 4 + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::BackupQueries() +// Purpose: Constructor +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +BackupQueries::BackupQueries(BackupProtocolClient &rConnection, + const Configuration &rConfiguration, bool readWrite) + : mReadWrite(readWrite), + mrConnection(rConnection), + mrConfiguration(rConfiguration), + mQuitNow(false), + mRunningAsRoot(false), + mWarnedAboutOwnerAttributes(false), + mReturnCode(0) // default return code +{ + #ifdef WIN32 + mRunningAsRoot = TRUE; + #else + mRunningAsRoot = (::geteuid() == 0); + #endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::~BackupQueries() +// Purpose: Destructor +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +BackupQueries::~BackupQueries() +{ +} + +typedef struct +{ + const char* name; + const char* opts; +} QueryCommandSpecification; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::DoCommand(const char *, bool) +// Purpose: Perform a command +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::DoCommand(const char *Command, bool isFromCommandLine) +{ + // is the command a shell command? + if(Command[0] == 's' && Command[1] == 'h' && Command[2] == ' ' && Command[3] != '\0') + { + // Yes, run shell command + int result = ::system(Command + 3); + if(result != 0) + { + BOX_WARNING("System command returned error code " << + result); + SetReturnCode(ReturnCode::Command_Error); + } + return; + } + + // split command into components + std::vector cmdElements; + std::string options; + { + 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); + } + + #ifdef WIN32 + if (isFromCommandLine) + { + for (std::vector::iterator + i = cmdElements.begin(); + i != cmdElements.end(); i++) + { + std::string converted; + if (!ConvertEncoding(*i, CP_ACP, converted, + GetConsoleCP())) + { + BOX_ERROR("Failed to convert encoding"); + return; + } + *i = converted; + } + } + #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) + { + // 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; + } + } + + // Arguments + std::vector args(cmdElements.begin() + 1, cmdElements.end()); + + // Set up options + bool opts[256]; + for(int o = 0; o < 256; ++o) opts[o] = false; + // BLOCK + { + // options + const char *c = options.c_str(); + while(*c != 0) + { + // Valid option? + if(::strchr(commands[cmd].opts, *c) == NULL) + { + BOX_ERROR("Invalid option '" << *c << "' for " + "command " << commands[cmd].name); + return; + } + opts[(int)*c] = true; + ++c; + } + } + + if(cmd != Command_Quit && cmd != Command_Exit) + { + // If not a quit command, set the return code to zero + SetReturnCode(ReturnCode::Command_OK); + } + + // Handle command + switch(cmd) + { + case Command_Quit: + case Command_Exit: + mQuitNow = true; + break; + + case Command_List: + CommandList(args, opts); + break; + + case Command_pwd: + { + // Simple implementation, so do it here + BOX_NOTICE(GetCurrentDirectoryName() << " (" << + BOX_FORMAT_OBJECTID(GetCurrentDirectoryID()) << + ")"); + } + break; + + case Command_cd: + CommandChangeDir(args, opts); + break; + + case Command_lcd: + CommandChangeLocalDir(args); + break; + + case Command_sh: + BOX_ERROR("The command to run must be specified as an argument."); + break; + + case Command_GetObject: + CommandGetObject(args, opts); + break; + + case Command_Get: + CommandGet(args, opts); + break; + + case Command_Compare: + CommandCompare(args, opts); + break; + + case Command_Restore: + CommandRestore(args, opts); + break; + + case Command_Usage: + CommandUsage(opts); + break; + + case Command_Help: + CommandHelp(args); + break; + + case Command_Undelete: + CommandUndelete(args, opts); + break; + + case Command_Delete: + CommandDelete(args, opts); + break; + + default: + BOX_ERROR("Unknown command: " << Command); + break; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandList(const std::vector &, const bool *) +// Purpose: List directories (optionally recursive) +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandList(const std::vector &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' + #define LIST_OPTION_TIMES_UTC 'T' + #define LIST_OPTION_TIMES_ATTRIBS 'a' + #define LIST_OPTION_SIZEINBLOCKS 's' + #define LIST_OPTION_DISPLAY_HASH 'h' + + // default to using the current directory + int64_t rootDir = GetCurrentDirectoryID(); + + // name of base directory + std::string listRoot; // blank + + // Got a directory in the arguments? + if(args.size() > 0) + { +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) + return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Attempt to find the directory + rootDir = FindDirectoryObjectID(storeDirEncoded, + opts[LIST_OPTION_ALLOWOLD], + opts[LIST_OPTION_ALLOWDELETED]); + + if(rootDir == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found " + "on store."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + } + + // List it + List(rootDir, listRoot, opts, true /* first level to list */); +} + +static std::string GetTimeString(BackupStoreDirectory::Entry& en, + bool useLocalTime, bool showAttrModificationTimes) +{ + std::ostringstream out; + box_time_t originalTime, newAttributesTime; + + // there is no attribute modification time in the directory + // entry, unfortunately, so we can't display it. + originalTime = en.GetModificationTime(); + out << BoxTimeToISO8601String(originalTime, useLocalTime); + + if(en.HasAttributes()) + { + const StreamableMemBlock &storeAttr(en.GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + + box_time_t NewModificationTime, NewAttrModificationTime; + attr.GetModificationTimes(&NewModificationTime, + &NewAttrModificationTime); + + if (showAttrModificationTimes) + { + newAttributesTime = NewAttrModificationTime; + } + else + { + newAttributesTime = NewModificationTime; + } + + if (newAttributesTime == originalTime) + { + out << "*"; + } + else + { + out << "~" << BoxTimeToISO8601String(newAttributesTime, + useLocalTime); + } + } + else + { + out << " "; + } + + return out.str(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::List(int64_t, const std::string &, const bool *, bool) +// Purpose: Do the actual listing of directories and files +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::List(int64_t DirID, const std::string &rListRoot, const bool *opts, bool FirstLevel) +{ + // 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; + + // Do communication + try + { + mrConnection.QueryListDirectory( + DirID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + // both files and directories + excludeFlags, + true /* want attributes */); + } + catch (std::exception &e) + { + BOX_ERROR("Failed to list directory: " << e.what()); + SetReturnCode(ReturnCode::Command_Error); + return; + } + catch (...) + { + BOX_ERROR("Failed to list directory: unknown error"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Then... display everything + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + // Display this entry + BackupStoreFilenameClear clear(en->GetName()); + + // Object ID? + 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 + } + + // Flags? + if(!opts[LIST_OPTION_NOFLAGS]) + { + static const char *flags = BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES; + char displayflags[16]; + // make sure f is big enough + ASSERT(sizeof(displayflags) >= sizeof(BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES) + 3); + // Insert flags + char *f = displayflags; + const char *t = flags; + int16_t en_flags = en->GetFlags(); + while(*t != 0) + { + *f = ((en_flags&1) == 0)?'-':*t; + en_flags >>= 1; + f++; + t++; + } + // attributes flags + *(f++) = (en->HasAttributes())?'a':'-'; + + // terminate + *(f++) = ' '; + *(f++) = '\0'; + printf(displayflags); + + if(en_flags != 0) + { + printf("[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()); + } + + if(opts[LIST_OPTION_TIMES_LOCAL]) + { + // Show local times... + printf("%s ", GetTimeString(*en, true, + opts[LIST_OPTION_TIMES_ATTRIBS]).c_str()); + } + + if(opts[LIST_OPTION_DISPLAY_HASH]) + { +#ifdef _MSC_VER + printf("%016I64x ", (int64_t)en->GetAttributesHash()); +#else + printf("%016llx ", (long long)en->GetAttributesHash()); +#endif + } + + if(opts[LIST_OPTION_SIZEINBLOCKS]) + { +#ifdef _MSC_VER + printf("%05I64d ", (int64_t)en->GetSizeInBlocks()); +#else + printf("%05lld ", (long long)en->GetSizeInBlocks()); +#endif + } + + // add name + if(!FirstLevel) + { +#ifdef WIN32 + std::string listRootDecoded; + if(!ConvertUtf8ToConsole(rListRoot.c_str(), + listRootDecoded)) return; + printf("%s/", listRootDecoded.c_str()); +#else + printf("%s/", rListRoot.c_str()); +#endif + } + +#ifdef WIN32 + { + std::string fileName; + if(!ConvertUtf8ToConsole( + clear.GetClearFilename().c_str(), fileName)) + return; + printf("%s", fileName.c_str()); + } +#else + printf("%s", clear.GetClearFilename().c_str()); +#endif + + if(!en->GetName().IsEncrypted()) + { + printf("[FILENAME NOT ENCRYPTED]"); + } + + printf("\n"); + + // Directory? + if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) + { + // Recurse? + if(opts[LIST_OPTION_RECURSIVE]) + { + std::string subroot(rListRoot); + if(!FirstLevel) subroot += '/'; + subroot += clear.GetClearFilename(); + List(en->GetObjectID(), subroot, opts, false /* not the first level to list */); + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::FindDirectoryObjectID(const +// std::string &) +// Purpose: Find the object ID of a directory on the store, +// or return 0 for not found. If pStack != 0, the +// object is set to the stack of directories. +// Will start from the current directory stack. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, + bool AllowOldVersion, bool AllowDeletedDirs, + std::vector > *pStack) +{ + // Split up string into elements + std::vector dirElements; + SplitString(rDirName, '/', dirElements); + + // Start from current stack, or root, whichever is required + std::vector > stack; + int64_t dirID = BackupProtocolClientListDirectory::RootDirectory; + if(rDirName.size() > 0 && rDirName[0] == '/') + { + // Root, do nothing + } + else + { + // Copy existing stack + stack = mDirStack; + if(stack.size() > 0) + { + dirID = stack[stack.size() - 1].second; + } + } + + // Generate exclude flags + int16_t excludeFlags = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; + if(!AllowOldVersion) excludeFlags |= BackupProtocolClientListDirectory::Flags_OldVersion; + if(!AllowDeletedDirs) excludeFlags |= BackupProtocolClientListDirectory::Flags_Deleted; + + // Read directories + for(unsigned int e = 0; e < dirElements.size(); ++e) + { + if(dirElements[e].size() > 0) + { + if(dirElements[e] == ".") + { + // Ignore. + } + else if(dirElements[e] == "..") + { + // Up one! + if(stack.size() > 0) + { + // Remove top element + stack.pop_back(); + + // New dir ID + dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolClientListDirectory::RootDirectory; + } + else + { + // At root anyway + dirID = BackupProtocolClientListDirectory::RootDirectory; + } + } + else + { + // Not blank element. Read current directory. + std::auto_ptr dirreply(mrConnection.QueryListDirectory( + dirID, + BackupProtocolClientListDirectory::Flags_Dir, // just directories + excludeFlags, + true /* want attributes */)); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Then... find the directory within it + BackupStoreDirectory::Iterator i(dir); + BackupStoreFilenameClear dirname(dirElements[e]); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname); + if(en == 0) + { + // Not found + return 0; + } + + // Object ID for next round of searching + dirID = en->GetObjectID(); + + // Push onto stack + stack.push_back(std::pair(dirElements[e], dirID)); + } + } + } + + // If required, copy the new stack to the caller + if(pStack) + { + *pStack = stack; + } + + return dirID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::GetCurrentDirectoryID() +// Purpose: Returns the ID of the current directory +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::GetCurrentDirectoryID() +{ + // Special case for root + if(mDirStack.size() == 0) + { + return BackupProtocolClientListDirectory::RootDirectory; + } + + // Otherwise, get from the last entry on the stack + return mDirStack[mDirStack.size() - 1].second; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::GetCurrentDirectoryName() +// Purpose: Gets the name of the current directory +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +std::string BackupQueries::GetCurrentDirectoryName() +{ + // Special case for root + if(mDirStack.size() == 0) + { + return std::string("/"); + } + + // Build path + std::string r; + for(unsigned int l = 0; l < mDirStack.size(); ++l) + { + r += "/"; +#ifdef WIN32 + std::string dirName; + if(!ConvertUtf8ToConsole(mDirStack[l].first.c_str(), dirName)) + return "error"; + r += dirName; +#else + r += mDirStack[l].first; +#endif + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandChangeDir(const std::vector &) +// Purpose: Change directory command +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandChangeDir(const std::vector &args, const bool *opts) +{ + if(args.size() != 1 || args[0].size() == 0) + { + BOX_ERROR("Incorrect usage. cd [-o] [-d] "); + SetReturnCode(ReturnCode::Command_Error); + return; + } + +#ifdef WIN32 + std::string dirName; + if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return; +#else + const std::string& dirName(args[0]); +#endif + + std::vector > newStack; + int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'], + &newStack); + + if(id == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Store new stack + mDirStack = newStack; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandChangeLocalDir(const std::vector &) +// Purpose: Change local directory command +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandChangeLocalDir(const std::vector &args) +{ + if(args.size() != 1 || args[0].size() == 0) + { + BOX_ERROR("Incorrect usage. lcd "); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Try changing directory +#ifdef WIN32 + std::string dirName; + if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) + { + BOX_ERROR("Failed to convert path from console encoding."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + int result = ::chdir(dirName.c_str()); +#else + int result = ::chdir(args[0].c_str()); +#endif + if(result != 0) + { + if(errno == ENOENT || errno == ENOTDIR) + { + BOX_ERROR("Directory '" << args[0] << "' does not exist."); + } + else + { + BOX_LOG_SYS_ERROR("Failed to change to directory " + "'" << args[0] << "'"); + } + + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Report current dir + char wd[PATH_MAX]; + if(::getcwd(wd, PATH_MAX) == 0) + { + BOX_LOG_SYS_ERROR("Error getting current directory"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + +#ifdef WIN32 + if(!ConvertUtf8ToConsole(wd, dirName)) + { + BOX_ERROR("Failed to convert new path from console encoding."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + BOX_INFO("Local current directory is now '" << dirName << "'."); +#else + BOX_INFO("Local current directory is now '" << wd << "'."); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandGetObject(const std::vector &, const bool *) +// Purpose: Gets an object without any translation. +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandGetObject(const std::vector &args, const bool *opts) +{ + // Check args + if(args.size() != 2) + { + BOX_ERROR("Incorrect usage. getobject " + ""); + return; + } + + int64_t id = ::strtoll(args[0].c_str(), 0, 16); + if(id == std::numeric_limits::min() || id == std::numeric_limits::max() || id == 0) + { + BOX_ERROR("Not a valid object ID (specified in hex)."); + return; + } + + // Does file exist? + EMU_STRUCT_STAT st; + if(EMU_STAT(args[1].c_str(), &st) == 0 || errno != ENOENT) + { + BOX_ERROR("The local file '" << args[1] << " already exists."); + return; + } + + // Open file + FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL); + + // Request that object + try + { + // Request object + std::auto_ptr getobj(mrConnection.QueryGetObject(id)); + if(getobj->GetObjectID() != BackupProtocolClientGetObject::NoObject) + { + // Stream that object out to the file + std::auto_ptr objectStream(mrConnection.ReceiveStream()); + objectStream->CopyStreamTo(out); + + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) << + " fetched successfully."); + } + else + { + BOX_ERROR("Object ID " << BOX_FORMAT_OBJECTID(id) << + " does not exist on store."); + ::unlink(args[1].c_str()); + } + } + catch(...) + { + ::unlink(args[1].c_str()); + BOX_ERROR("Error occured fetching object."); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::FindFileID(const std::string& +// rNameOrIdString, const bool *options, +// int64_t *pDirIdOut, std::string* pFileNameOut) +// Purpose: Locate a file on the store (either by name or by +// object ID, depending on opts['i'], where name can +// include a path) and return the file ID, placing the +// directory ID in *pDirIdOut and the filename part +// of the path in *pFileNameOut (if not NULL). +// Created: 2008-09-12 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString, + const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut, + int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut) +{ + // Find object ID somehow + int64_t fileId; + int64_t dirId = GetCurrentDirectoryID(); + std::string fileName = rNameOrIdString; + + if(!opts['i']) + { + // does this remote filename include a path? + std::string::size_type index = fileName.rfind('/'); + if(index != std::string::npos) + { + std::string dirName(fileName.substr(0, index)); + fileName = fileName.substr(index + 1); + + dirId = FindDirectoryObjectID(dirName); + if(dirId == 0) + { + BOX_ERROR("Directory '" << dirName << + "' not found."); + return 0; + } + } + } + + BackupStoreFilenameClear fn(fileName); + + // Need to look it up in the current directory + mrConnection.QueryListDirectory( + dirId, flagsInclude, flagsExclude, + true /* do want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + BackupStoreDirectory::Entry *en; + + if(opts['i']) + { + // Specified as ID. + fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16); + if(fileId == std::numeric_limits::min() || + fileId == std::numeric_limits::max() || + fileId == 0) + { + BOX_ERROR("Not a valid object ID (specified in hex)."); + return 0; + } + + // Check that the item is actually in the directory + en = dir.FindEntryByID(fileId); + if(en == 0) + { + BOX_ERROR("File ID " << + BOX_FORMAT_OBJECTID(fileId) << + " not found in current directory on store.\n" + "(You can only access files by ID from the " + "current directory.)"); + return 0; + } + } + else + { + // Specified by name, find the object in the directory to get the ID + BackupStoreDirectory::Iterator i(dir); + en = i.FindMatchingClearName(fn); + if(en == 0) + { + BOX_ERROR("Filename '" << rNameOrIdString << "' " + "not found in current directory on store.\n" + "(Subdirectories in path not searched.)"); + return 0; + } + + fileId = en->GetObjectID(); + } + + *pDirIdOut = dirId; + + if(pFlagsOut) + { + *pFlagsOut = en->GetFlags(); + } + + if(pFileNameOut) + { + BackupStoreFilenameClear entryName(en->GetName()); + *pFileNameOut = entryName.GetClearFilename(); + } + + return fileId; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandGet(const std::vector &, const bool *) +// Purpose: Command to get a file from the store +// Created: 2003/10/12 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandGet(std::vector args, const bool *opts) +{ + // At least one argument? + // Check args + if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2) + { + BOX_ERROR("Incorrect usage.\n" + "get [] or\n" + "get -i "); + return; + } + + // Find object ID somehow + int64_t fileId, dirId; + std::string localName; + +#ifdef WIN32 + for (std::vector::iterator + i = args.begin(); i != args.end(); i++) + { + std::string out; + if(!ConvertConsoleToUtf8(i->c_str(), out)) + { + BOX_ERROR("Failed to convert encoding."); + return; + } + *i = out; + } +#endif + + int16_t flagsExclude; + + if(opts['i']) + { + // can retrieve anything by ID + flagsExclude = BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING; + } + else + { + // only current versions by name + flagsExclude = + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted; + } + + + fileId = FindFileID(args[0], opts, &dirId, &localName, + BackupProtocolClientListDirectory::Flags_File, // just files + flagsExclude, NULL /* don't care about flags found */); + + if (fileId == 0) + { + // error already reported + return; + } + + if(opts['i']) + { + // Specified as ID. Must have a local name in the arguments + // (check at beginning of function ensures this) + localName = args[1]; + } + else + { + // Specified by name. Local name already set by FindFileID, + // but may be overridden by user supplying a second argument. + if(args.size() == 2) + { + localName = args[1]; + } + } + + // Does local file already exist? (don't want to overwrite) + EMU_STRUCT_STAT st; + if(EMU_STAT(localName.c_str(), &st) == 0 || errno != ENOENT) + { + BOX_ERROR("The local file " << localName << " already exists, " + "will not overwrite it."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Request it from the store + try + { + // Request object + mrConnection.QueryGetFile(dirId, fileId); + + // Stream containing encoded file + std::auto_ptr objectStream(mrConnection.ReceiveStream()); + + // Decode it + BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout()); + + // Done. + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) << + " fetched successfully."); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); + } + catch(...) + { + BOX_ERROR("Failed to fetch file: unknown error"); + ::unlink(localName.c_str()); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CompareParams::CompareParams() +// Purpose: Constructor +// Created: 29/1/04 +// +// -------------------------------------------------------------------------- +BackupQueries::CompareParams::CompareParams(bool QuickCompare, + bool IgnoreExcludes, bool IgnoreAttributes, + box_time_t LatestFileUploadTime) +: BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes, + LatestFileUploadTime), + mDifferences(0), + mDifferencesExplainedByModTime(0), + mUncheckedFiles(0), + mExcludedDirs(0), + mExcludedFiles(0) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandCompare(const std::vector &, const bool *) +// Purpose: Command to compare data on the store with local data +// Created: 2003/10/12 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandCompare(const std::vector &args, const bool *opts) +{ + box_time_t LatestFileUploadTime = GetCurrentBoxTime(); + + // Try and work out the time before which all files should be on the server + { + std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); + syncTimeFilename += "last_sync_start"; + // Stat it to get file time + EMU_STRUCT_STAT st; + if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0) + { + // Files modified after this time shouldn't be on the server, so report errors slightly differently + LatestFileUploadTime = FileModificationTime(st) - + SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge")); + } + else + { + BOX_WARNING("Failed to determine the time of the last " + "synchronisation -- checks not performed."); + } + } + + // Parameters, including count of differences + BackupQueries::CompareParams params(opts['q'], // quick compare? + opts['E'], // ignore excludes + opts['A'], // ignore attributes + LatestFileUploadTime); + + params.mQuietCompare = opts['Q']; + + // Quick compare? + if(params.QuickCompare()) + { + BOX_WARNING("Quick compare used -- file attributes are not " + "checked."); + } + + if(!opts['l'] && opts['a'] && args.size() == 0) + { + // Compare all locations + const Configuration &rLocations( + mrConfiguration.GetSubConfiguration("BackupLocations")); + std::vector locNames = + rLocations.GetSubConfigurationNames(); + for(std::vector::iterator + pLocName = locNames.begin(); + pLocName != locNames.end(); + pLocName++) + { + CompareLocation(*pLocName, params); + } + } + else if(opts['l'] && !opts['a'] && args.size() == 1) + { + // Compare one location + CompareLocation(args[0], params); + } + else if(!opts['l'] && !opts['a'] && args.size() == 2) + { + // Compare directory to directory + + // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list + if(!params.IgnoreExcludes()) + { + BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes."); + return; + } + else + { + // Do compare + Compare(args[0], args[1], params); + } + } + else + { + BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l \n or compare "); + return; + } + + if (!params.mQuietCompare) + { + BOX_INFO("[ " << + params.mDifferencesExplainedByModTime << " (of " << + params.mDifferences << ") differences probably " + "due to file modifications after the last upload ]"); + } + + BOX_INFO("Differences: " << params.mDifferences << " (" << + params.mExcludedDirs << " dirs excluded, " << + params.mExcludedFiles << " files excluded, " << + params.mUncheckedFiles << " files not checked)"); + + // Set return code? + if(opts['c']) + { + if (params.mUncheckedFiles != 0) + { + SetReturnCode(ReturnCode::Compare_Error); + } + else if (params.mDifferences != 0) + { + SetReturnCode(ReturnCode::Compare_Different); + } + else + { + SetReturnCode(ReturnCode::Compare_Same); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a location +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams) +{ + // Find the location's sub configuration + const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); + if(!locations.SubConfigurationExists(rLocation.c_str())) + { + BOX_ERROR("Location " << rLocation << " does not exist."); + return; + } + const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str())); + + #ifdef WIN32 + { + std::string path = loc.GetKeyValue("Path"); + if (path.size() > 0 && path[path.size()-1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + BOX_WARNING("Location '" << rLocation << "' path ends " + "with '" DIRECTORY_SEPARATOR "', " + "compare may fail!"); + } + } + #endif + + // Generate the exclude lists + if(!rParams.IgnoreExcludes()) + { + rParams.LoadExcludeLists(loc); + } + + // Then get it compared + Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::Compare(const std::string &, +// const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a store directory against a local directory +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::Compare(const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) +{ +#ifdef WIN32 + std::string localDirEncoded; + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return; + if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return; +#else + const std::string& localDirEncoded(rLocalDir); + const std::string& storeDirEncoded(rStoreDir); +#endif + + // Get the directory ID of the directory -- only use current data + int64_t dirID = FindDirectoryObjectID(storeDirEncoded); + + // Found? + if(dirID == 0) + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyRemoteFileMissing(localDirEncoded, + storeDirEncoded, modifiedAfterLastSync); + return; + } + + // Go! + Compare(dirID, storeDirEncoded, localDirEncoded, rParams); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::Compare(int64_t, const std::string &, +// const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a store directory against a local directory +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) +{ + rParams.NotifyDirComparing(rLocalDir, rStoreDir); + + // Get info on the local directory + EMU_STRUCT_STAT st; + if(EMU_LSTAT(rLocalDir.c_str(), &st) != 0) + { + // What kind of error? + if(errno == ENOTDIR || errno == ENOENT) + { + rParams.NotifyLocalDirMissing(rLocalDir, rStoreDir); + } + else + { + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); + } + return; + } + + // Get the directory listing from the store + mrConnection.QueryListDirectory( + DirID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + // get everything + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted, + // except for old versions and deleted files + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Test out the attributes + if(!dir.HasAttributes()) + { + rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir); + } + else + { + // Fetch the attributes + const StreamableMemBlock &storeAttr(dir.GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + + // Get attributes of local directory + BackupClientFileAttributes localAttr; + localAttr.ReadAttributes(rLocalDir.c_str(), + true /* directories have zero mod times */); + + if(attr.Compare(localAttr, true, true /* ignore modification times */)) + { + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + false, false /* actually we didn't check :) */); + } + else + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + true, modifiedAfterLastSync); + } + } + + // Open the local directory + DIR *dirhandle = ::opendir(rLocalDir.c_str()); + if(dirhandle == 0) + { + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); + return; + } + + try + { + // Read the files and directories into sets + std::set localFiles; + std::set localDirs; + struct dirent *localDirEn = 0; + while((localDirEn = readdir(dirhandle)) != 0) + { + // Not . and ..! + if(localDirEn->d_name[0] == '.' && + (localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0'))) + { + // ignore, it's . or .. + +#ifdef HAVE_VALID_DIRENT_D_TYPE + if (localDirEn->d_type != DT_DIR) + { + BOX_ERROR("d_type does not really " + "work on your platform. " + "Reconfigure Box!"); + return; + } +#endif + + continue; + } + + std::string localDirPath(MakeFullPath(rLocalDir, + localDirEn->d_name)); + std::string storeDirPath(rStoreDir + "/" + + localDirEn->d_name); + +#ifndef HAVE_VALID_DIRENT_D_TYPE + EMU_STRUCT_STAT st; + if(EMU_LSTAT(localDirPath.c_str(), &st) != 0) + { + // Check whether dir is excluded before trying + // to stat it, to fix problems with .gvfs + // directories that are not readable by root + // causing compare to crash: + // http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000013.html + if(rParams.IsExcludedDir(localDirPath)) + { + rParams.NotifyExcludedDir(localDirPath, + storeDirPath); + continue; + } + else + { + THROW_EXCEPTION_MESSAGE(CommonException, + OSFileError, localDirPath); + } + } + + // Entry -- file or dir? + if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + { + // File or symbolic link + localFiles.insert(std::string(localDirEn->d_name)); + } + else if(S_ISDIR(st.st_mode)) + { + // Directory + localDirs.insert(std::string(localDirEn->d_name)); + } +#else + // Entry -- file or dir? + if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK) + { + // File or symbolic link + localFiles.insert(std::string(localDirEn->d_name)); + } + else if(localDirEn->d_type == DT_DIR) + { + // Directory + localDirs.insert(std::string(localDirEn->d_name)); + } +#endif + } + // Close directory + if(::closedir(dirhandle) != 0) + { + BOX_LOG_SYS_ERROR("Failed to close local directory " + "'" << rLocalDir << "'"); + } + dirhandle = 0; + + // Do the same for the store directories + std::set > storeFiles; + std::set > storeDirs; + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *storeDirEn = 0; + while((storeDirEn = i.Next()) != 0) + { + // Decrypt filename + BackupStoreFilenameClear name(storeDirEn->GetName()); + + // What is it? + if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File) + { + // File + storeFiles.insert(std::pair(name.GetClearFilename(), storeDirEn)); + } + else + { + // Dir + storeDirs.insert(std::pair(name.GetClearFilename(), storeDirEn)); + } + } + +#ifdef _MSC_VER + typedef std::set::iterator string_set_iter_t; +#else + typedef std::set::const_iterator string_set_iter_t; +#endif + + // Now compare files. + for(std::set >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i) + { + const std::string& fileName(i->first); + + std::string localPath(MakeFullPath(rLocalDir, fileName)); + std::string storePath(rStoreDir + "/" + fileName); + + rParams.NotifyFileComparing(localPath, storePath); + + // Does the file exist locally? + string_set_iter_t local(localFiles.find(fileName)); + if(local == localFiles.end()) + { + // Not found -- report + rParams.NotifyLocalFileMissing(localPath, + storePath); + } + 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 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 objectStream(mrConnection.ReceiveStream()); + + // Decode it + std::auto_ptr 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); + } + + // Remove from set so that we know it's been compared + localFiles.erase(local); + } + } + + // Report any files which exist locally, but not on the store + for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); + + // Should this be ignored (ie is excluded)? + if(!rParams.IsExcludedFile(localPath)) + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0) + { + if(FileModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); + } + else + { + rParams.NotifyExcludedFile(localPath, + storePath); + } + } + + // Finished with the files, clear the sets to reduce memory usage slightly + localFiles.clear(); + storeFiles.clear(); + + // Now do the directories, recursively to check subdirectories + for(std::set >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, i->first)); + std::string storePath(rStoreDir + "/" + i->first); + + // Does the directory exist locally? + string_set_iter_t local(localDirs.find(i->first)); + if(local == localDirs.end() && + rParams.IsExcludedDir(localPath)) + { + rParams.NotifyExcludedFileNotDeleted(localPath, + storePath); + } + else if(local == localDirs.end()) + { + // Not found -- report + rParams.NotifyLocalFileMissing(localPath, + storePath); + } + else if(rParams.IsExcludedDir(localPath)) + { + // don't recurse into excluded directories + } + else + { + // Compare directory + Compare(i->second->GetObjectID(), + storePath, localPath, rParams); + + // Remove from set so that we know it's been compared + localDirs.erase(local); + } + } + + // Report any directories which exist locally, but not on the store + for(std::set::const_iterator + i = localDirs.begin(); + i != localDirs.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); + + // Should this be ignored (ie is excluded)? + if(!rParams.IsExcludedDir(localPath)) + { + bool modifiedAfterLastSync = false; + + // Check the dir modification time + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0 && + FileModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); + } + else + { + rParams.NotifyExcludedDir(localPath, storePath); + } + } + } + catch(...) + { + if(dirhandle != 0) + { + ::closedir(dirhandle); + } + throw; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandRestore(const std::vector &, const bool *) +// Purpose: Restore a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandRestore(const std::vector &args, const bool *opts) +{ + // Check arguments + if(args.size() != 2) + { + BOX_ERROR("Incorrect usage. restore [-drif] "); + return; + } + + // Restoring deleted things? + bool restoreDeleted = opts['d']; + + std::string storeDirEncoded; + + // Get directory ID + int64_t dirID = 0; + if(opts['i']) + { + // Specified as ID. + dirID = ::strtoll(args[0].c_str(), 0, 16); + if(dirID == std::numeric_limits::min() || dirID == std::numeric_limits::max() || dirID == 0) + { + BOX_ERROR("Not a valid object ID (specified in hex)"); + return; + } + std::ostringstream oss; + oss << BOX_FORMAT_OBJECTID(args[0]); + storeDirEncoded = oss.str(); + } + else + { +#ifdef WIN32 + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) + return; +#else + storeDirEncoded = args[0]; +#endif + + // Look up directory ID + dirID = FindDirectoryObjectID(storeDirEncoded, + false /* no old versions */, + restoreDeleted /* find deleted dirs */); + } + + // Allowable? + if(dirID == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found on server"); + return; + } + if(dirID == BackupProtocolClientListDirectory::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 + + // Go and restore... + int result; + + try + { + // At TRACE level, we print a line for each file and + // directory, so we don't need dots. + + bool printDots = ! Logging::IsEnabled(Log::TRACE); + + result = BackupClientRestore(mrConnection, dirID, + storeDirEncoded.c_str(), localName.c_str(), + printDots /* print progress dots */, restoreDeleted, + false /* don't undelete after restore! */, + opts['r'] /* resume? */, + opts['f'] /* force continue after errors */); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore: " << e.what()); + SetReturnCode(ReturnCode::Command_Error); + return; + } + catch(...) + { + BOX_ERROR("Failed to restore: unknown exception"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + switch(result) + { + case Restore_Complete: + BOX_INFO("Restore complete."); + break; + + case Restore_CompleteWithErrors: + BOX_WARNING("Restore complete, but some files could not be " + "restored."); + break; + + case Restore_ResumePossible: + BOX_ERROR("Resume possible -- repeat command with -r flag " + "to resume."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_TargetExists: + BOX_ERROR("The target directory exists. You cannot restore " + "over an existing directory."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_TargetPathNotFound: + BOX_ERROR("The target directory path does not exist.\n" + "To restore to a directory whose parent " + "does not exist, create the parent first."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_UnknownError: + BOX_ERROR("Unknown error during restore."); + SetReturnCode(ReturnCode::Command_Error); + break; + + default: + BOX_ERROR("Unknown restore result " << result << "."); + SetReturnCode(ReturnCode::Command_Error); + break; + } +} + + + +// These are autogenerated by a script. +extern char *help_commands[]; +extern char *help_text[]; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandHelp(const std::vector &args) +// Purpose: Display help on commands +// Created: 15/2/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandHelp(const std::vector &args) +{ + if(args.size() == 0) + { + // Display a list of all commands + printf("Available commands are:\n"); + for(int c = 0; help_commands[c] != 0; ++c) + { + printf(" %s\n", help_commands[c]); + } + printf("Type \"help \" for more information on a command.\n\n"); + } + else + { + // Display help on a particular command + int c; + for(c = 0; help_commands[c] != 0; ++c) + { + if(::strcmp(help_commands[c], args[0].c_str()) == 0) + { + // Found the command, print help + printf("\n%s\n", help_text[c]); + break; + } + } + if(help_commands[c] == 0) + { + printf("No help found for command '%s'\n", args[0].c_str()); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUsage() +// Purpose: Display storage space used on server +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUsage(const bool *opts) +{ + bool MachineReadable = opts['m']; + + // Request full details from the server + std::auto_ptr usage(mrConnection.QueryGetAccountUsage()); + + // Display each entry in turn + int64_t hardLimit = usage->GetBlocksHardLimit(); + int32_t blockSize = usage->GetBlockSize(); + CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, + blockSize, MachineReadable); + CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize, + MachineReadable); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUsageDisplayEntry(const char *, +// int64_t, int64_t, int32_t, bool) +// Purpose: Display an entry in the usage table +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, +int64_t HardLimit, int32_t BlockSize, bool MachineReadable) +{ + std::cout << FormatUsageLineStart(Name, MachineReadable) << + FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize, + MachineReadable) << std::endl; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUndelete(const std::vector &, const bool *) +// Purpose: Undelete a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUndelete(const std::vector &args, const bool *opts) +{ + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + + // Check arguments + if(args.size() != 1) + { + BOX_ERROR("Incorrect usage. undelete or undelete -i "); + return; + } + +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + /* include old and deleted files */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + &flagsOut); + + if (fileId == 0) + { + // error already reported + return; + } + + // Undelete it on the store + try + { + // Undelete object + if(flagsOut & BackupProtocolClientListDirectory::Flags_File) + { + mrConnection.QueryUndeleteFile(parentId, fileId); + } + else + { + mrConnection.QueryUndeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to undelete object: unknown error"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandDelete(const +// std::vector &, const bool *) +// Purpose: Deletes a file +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandDelete(const std::vector &args, + const bool *opts) +{ + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + + // Check arguments + if(args.size() != 1) + { + BOX_ERROR("Incorrect usage. delete "); + return; + } + +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + /* exclude old and deleted files */ + BackupProtocolClientListDirectory::Flags_OldVersion | + BackupProtocolClientListDirectory::Flags_Deleted, + &flagsOut); + + if (fileId == 0) + { + // error already reported + return; + } + + BackupStoreFilenameClear fn(fileName); + + // Delete it on the store + try + { + // Delete object + if(flagsOut & BackupProtocolClientListDirectory::Flags_File) + { + mrConnection.QueryDeleteFile(parentId, fn); + } + else + { + mrConnection.QueryDeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to delete object: unknown error"); + } +} diff --git a/bin/bbackupquery/BackupQueries.h b/bin/bbackupquery/BackupQueries.h new file mode 100644 index 00000000..392aa428 --- /dev/null +++ b/bin/bbackupquery/BackupQueries.h @@ -0,0 +1,346 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupQueries.h +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPQUERIES__H +#define BACKUPQUERIES__H + +#include +#include + +#include "BoxTime.h" +#include "BoxBackupCompareParams.h" + +class BackupProtocolClient; +class Configuration; +class ExcludeList; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupQueries +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +class BackupQueries +{ +public: + BackupQueries(BackupProtocolClient &rConnection, + const Configuration &rConfiguration, + bool readWrite); + ~BackupQueries(); +private: + BackupQueries(const BackupQueries &); +public: + + void DoCommand(const char *Command, bool isFromCommandLine); + + // Ready to stop? + bool Stop() {return mQuitNow;} + + // Return code? + int GetReturnCode() {return mReturnCode;} + +private: + // Commands + void CommandList(const std::vector &args, const bool *opts); + void CommandChangeDir(const std::vector &args, const bool *opts); + void CommandChangeLocalDir(const std::vector &args); + void CommandGetObject(const std::vector &args, const bool *opts); + void CommandGet(std::vector args, const bool *opts); + void CommandCompare(const std::vector &args, const bool *opts); + void CommandRestore(const std::vector &args, const bool *opts); + void CommandUndelete(const std::vector &args, const bool *opts); + void CommandDelete(const std::vector &args, + const bool *opts); + void CommandUsage(const bool *opts); + void CommandUsageDisplayEntry(const char *Name, int64_t Size, + int64_t HardLimit, int32_t BlockSize, bool MachineReadable); + void CommandHelp(const std::vector &args); + + // Implementations + void List(int64_t DirID, const std::string &rListRoot, const bool *opts, + bool FirstLevel); + +public: + class CompareParams : public BoxBackupCompareParams + { + public: + CompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime); + + bool mQuietCompare; + int mDifferences; + int mDifferencesExplainedByModTime; + int mUncheckedFiles; + int mExcludedDirs; + int mExcludedFiles; + + std::string ConvertForConsole(const std::string& rUtf8String) + { + #ifdef WIN32 + std::string output; + + if(!ConvertUtf8ToConsole(rUtf8String.c_str(), output)) + { + BOX_WARNING("Character set conversion failed " + "on string: " << rUtf8String); + return rUtf8String; + } + + return output; + #else + return rUtf8String; + #endif + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "does not exist, but remote directory does."); + mDifferences ++; + } + + virtual void NotifyLocalDirAccessFailed( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_LOG_SYS_WARNING("Failed to access local directory " + "'" << ConvertForConsole(rLocalPath) << "'"); + mUncheckedFiles ++; + } + + virtual void NotifyStoreDirMissingAttributes( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Store directory '" << + ConvertForConsole(rRemotePath) << "' " + "doesn't have attributes."); + } + + virtual void NotifyRemoteFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "exists, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "does not."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the file above was modified after " + "the last sync time -- might be " + "reason for difference)"); + } + } + + virtual void NotifyLocalFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Remote file '" << + ConvertForConsole(rRemotePath) << "' " + "exists, but local file '" << + ConvertForConsole(rLocalPath) << "' does not."); + mDifferences ++; + } + + virtual void NotifyExcludedFileNotDeleted( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "is excluded, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "still exists."); + mDifferences ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + BoxException& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what() << " (" << + rException.GetType() << "/" << + rException.GetSubType() << ")"); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + std::exception& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what()); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath)); + mUncheckedFiles ++; + } + + virtual void NotifyExcludedFile(const std::string& rLocalPath, + const std::string& rRemotePath) + { + mExcludedFiles ++; + } + + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) + { + mExcludedDirs ++; + } + + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + } + + virtual void NotifyDirCompared( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool HasDifferentAttributes, + bool modifiedAfterLastSync) + { + if(HasDifferentAttributes) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store directory '" << + ConvertForConsole(rRemotePath) << "'."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the directory above was " + "modified after the last sync " + "time -- might be reason for " + "difference)"); + } + } + } + + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + } + + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool ModifiedAfterLastSync, bool NewAttributesApplied) + { + int NewDifferences = 0; + + if(HasDifferentAttributes) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentContents) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different contents to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentAttributes || HasDifferentContents) + { + if(ModifiedAfterLastSync) + { + mDifferencesExplainedByModTime += + NewDifferences; + BOX_INFO("(the file above was modified " + "after the last sync time -- " + "might be reason for difference)"); + } + else if(NewAttributesApplied) + { + BOX_INFO("(the file above has had new " + "attributes applied)\n"); + } + } + + mDifferences += NewDifferences; + } + }; + void CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams); + void Compare(const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams); + void Compare(int64_t DirID, const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams); + +public: + + class ReturnCode + { + public: + enum { + Command_OK = 0, + Compare_Same = 1, + Compare_Different, + Compare_Error, + Command_Error, + } Type; + }; + +private: + + // Utility functions + int64_t FindDirectoryObjectID(const std::string &rDirName, + bool AllowOldVersion = false, bool AllowDeletedDirs = false, + std::vector > *pStack = 0); + 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;} + +private: + bool mReadWrite; + BackupProtocolClient &mrConnection; + const Configuration &mrConfiguration; + bool mQuitNow; + std::vector > mDirStack; + bool mRunningAsRoot; + bool mWarnedAboutOwnerAttributes; + int mReturnCode; +}; + +#endif // BACKUPQUERIES__H + diff --git a/bin/bbackupquery/BoxBackupCompareParams.h b/bin/bbackupquery/BoxBackupCompareParams.h new file mode 100644 index 00000000..c58759a2 --- /dev/null +++ b/bin/bbackupquery/BoxBackupCompareParams.h @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxBackupCompareParams.h +// Purpose: Parameters and notifiers for a compare operation +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BOXBACKUPCOMPAREPARAMS__H +#define BOXBACKUPCOMPAREPARAMS__H + +#include +#include + +#include "BoxTime.h" +#include "ExcludeList.h" +#include "BackupClientMakeExcludeList.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BoxBackupCompareParams +// Purpose: Parameters and notifiers for a compare operation +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +class BoxBackupCompareParams +{ +private: + std::auto_ptr mapExcludeFiles, mapExcludeDirs; + bool mQuickCompare; + bool mIgnoreExcludes; + bool mIgnoreAttributes; + box_time_t mLatestFileUploadTime; + +public: + BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime) + : mQuickCompare(QuickCompare), + mIgnoreExcludes(IgnoreExcludes), + mIgnoreAttributes(IgnoreAttributes), + mLatestFileUploadTime(LatestFileUploadTime) + { } + + virtual ~BoxBackupCompareParams() { } + + bool QuickCompare() { return mQuickCompare; } + bool IgnoreExcludes() { return mIgnoreExcludes; } + bool IgnoreAttributes() { return mIgnoreAttributes; } + box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; } + + void LoadExcludeLists(const Configuration& rLoc) + { + mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc)); + mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc)); + } + bool IsExcludedFile(const std::string& rLocalPath) + { + if (!mapExcludeFiles.get()) return false; + return mapExcludeFiles->IsExcluded(rLocalPath); + } + bool IsExcludedDir(const std::string& rLocalPath) + { + if (!mapExcludeDirs.get()) return false; + return mapExcludeDirs->IsExcluded(rLocalPath); + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyRemoteFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) = 0; + virtual void NotifyLocalFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + BoxException& rException) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + std::exception& rException) = 0; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) = 0; + virtual void NotifyExcludedFile(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirCompared(const std::string& rLocalPath, + const std::string& rRemotePath, bool HasDifferentAttributes, + bool modifiedAfterLastSync) = 0; + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool modifiedAfterLastSync, bool newAttributesApplied) = 0; +}; + +#endif // BOXBACKUPCOMPAREPARAMS__H diff --git a/bin/bbackupquery/Makefile.extra b/bin/bbackupquery/Makefile.extra new file mode 100644 index 00000000..e1049b6d --- /dev/null +++ b/bin/bbackupquery/Makefile.extra @@ -0,0 +1,6 @@ + +# AUTOGEN SEEDING +autogen_Documentation.cpp: makedocumentation.pl documentation.txt + $(_PERL) makedocumentation.pl + + diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp new file mode 100644 index 00000000..5aa7e97e --- /dev/null +++ b/bin/bbackupquery/bbackupquery.cpp @@ -0,0 +1,441 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupquery.cpp +// Purpose: Backup query utility +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#ifdef HAVE_SYS_TYPES_H + #include +#endif + +#ifdef HAVE_LIBREADLINE + #ifdef HAVE_READLINE_READLINE_H + #include + #elif defined(HAVE_EDITLINE_READLINE_H) + #include + #elif defined(HAVE_READLINE_H) + #include + #endif +#endif +#ifdef HAVE_READLINE_HISTORY + #ifdef HAVE_READLINE_HISTORY_H + #include + #elif defined(HAVE_HISTORY_H) + #include + #endif +#endif + +#include + +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupDaemonConfigVerify.h" +#include "SocketStreamTLS.h" +#include "Socket.h" +#include "TLSContext.h" +#include "SSLLib.h" +#include "BackupStoreConstants.h" +#include "BackupStoreException.h" +#include "autogen_BackupProtocolClient.h" +#include "BackupQueries.h" +#include "FdGetLine.h" +#include "BackupClientCryptoKeys.h" +#include "BannerText.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +void PrintUsageAndExit() +{ + printf("Usage: bbackupquery [-q*|v*|V|W] [-w] " +#ifdef WIN32 + "[-u] " +#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"); + exit(1); +} + +int main(int argc, const char *argv[]) +{ + int returnCode = 0; + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks", + "bbackupquery") + MAINHELPER_START + +#ifdef WIN32 + WSADATA info; + + // Under Win32 we must initialise the Winsock library + // before using it. + + if (WSAStartup(0x0101, &info) == SOCKET_ERROR) + { + // throw error? perhaps give it its own id in the future + THROW_EXCEPTION(BackupStoreException, Internal) + } +#endif + + // Really don't want trace statements happening, even in debug mode + #ifndef BOX_RELEASE_BUILD + BoxDebugTraceOn = false; + #endif + + 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 + + // Flags + bool readWrite = false; + + Logging::SetProgramName("bbackupquery"); + + #ifdef BOX_RELEASE_BUILD + int masterLevel = Log::NOTICE; // need an int to do math with + #else + int masterLevel = Log::INFO; // need an int to do math with + #endif + +#ifdef WIN32 + const char* validOpts = "qvVwuc:l:o:O:W:"; + bool unicodeConsole = false; +#else + const char* validOpts = "qvVwc:l:o:O:W:"; +#endif + + std::string fileLogFile; + Log::Level fileLogLevel = Log::INVALID; + + // See if there's another entry on the command line + int c; + while((c = getopt(argc, (char * const *)argv, validOpts)) != -1) + { + switch(c) + { + case 'q': + { + if(masterLevel == Log::NOTHING) + { + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; + } + masterLevel--; + } + break; + + case 'v': + { + if(masterLevel == Log::EVERYTHING) + { + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; + } + masterLevel++; + } + break; + + case 'V': + { + masterLevel = Log::EVERYTHING; + } + break; + + case 'W': + { + masterLevel = Logging::GetNamedLevel(optarg); + if (masterLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } + } + break; + + case 'w': + // Read/write mode + readWrite = true; + break; + + case 'c': + // store argument + configFilename = optarg; + break; + + case 'l': + // open log file + logFile = ::fopen(optarg, "w"); + if(logFile == 0) + { + BOX_LOG_SYS_ERROR("Failed to open log file " + "'" << optarg << "'"); + } + break; + + case 'o': + fileLogFile = optarg; + fileLogLevel = Log::EVERYTHING; + break; + + case 'O': + { + fileLogLevel = Logging::GetNamedLevel(optarg); + if (fileLogLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } + } + break; + +#ifdef WIN32 + case 'u': + unicodeConsole = true; + break; +#endif + + case '?': + default: + PrintUsageAndExit(); + } + } + // Adjust arguments + argc -= optind; + argv += optind; + + Logging::SetGlobalLevel((Log::Level)masterLevel); + + std::auto_ptr fileLogger; + if (fileLogLevel != Log::INVALID) + { + fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel)); + } + + bool quiet = false; + if (masterLevel < Log::NOTICE) + { + // Quiet mode + quiet = true; + } + + // Print banner? + if(!quiet) + { + const char *banner = BANNER_TEXT("Backup Query Tool"); + BOX_NOTICE(banner); + } + +#ifdef WIN32 + if (unicodeConsole) + { + if (!SetConsoleCP(CP_UTF8)) + { + BOX_ERROR("Failed to set input codepage: " << + GetErrorMessage(GetLastError())); + } + + if (!SetConsoleOutputCP(CP_UTF8)) + { + BOX_ERROR("Failed to set output codepage: " << + GetErrorMessage(GetLastError())); + } + + // enable input of Unicode characters + if (_fileno(stdin) != -1 && + _setmode(_fileno(stdin), _O_TEXT) == -1) + { + perror("Failed to set the console input to " + "binary mode"); + } + } +#endif // WIN32 + + // Read in the configuration file + if(!quiet) BOX_INFO("Using configuration file " << configFilename); + + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_FATAL("Invalid configuration file: " << errs); + return 1; + } + // Easier coding + const Configuration &conf(*config); + + // Setup and connect + // 1. TLS context + SSLLib::Initialise(); + // Read in the certificates creating a TLS context + TLSContext tlsContext; + std::string certFile(conf.GetKeyValue("CertificateFile")); + std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); + std::string caFile(conf.GetKeyValue("TrustedCAsFile")); + tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); + + // Initialise keys + BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); + + // 2. Connect to server + if(!quiet) BOX_INFO("Connecting to store..."); + SocketStreamTLS socket; + socket.Open(tlsContext, Socket::TypeINET, + conf.GetKeyValue("StoreHostname").c_str(), + conf.GetKeyValueInt("StorePort")); + + // 3. Make a protocol, and handshake + if(!quiet) BOX_INFO("Handshake with store..."); + BackupProtocolClient connection(socket); + connection.Handshake(); + + // logging? + if(logFile != 0) + { + connection.SetLogToFile(logFile); + } + + // 4. Log in to server + if(!quiet) BOX_INFO("Login to store..."); + // Check the version of the server + { + std::auto_ptr 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)); + + // 5. Tell user. + if(!quiet) printf("Login complete.\n\nType \"help\" for a list of commands.\n\n"); + + // Set up a context for our work + BackupQueries context(connection, conf, readWrite); + + // Start running commands... first from the command line + { + int c = 0; + while(c < argc && !context.Stop()) + { + context.DoCommand(argv[c++], true); + } + } + + // 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) + { + BOX_ERROR("Failed to initialise locale. International " + "character support may not work."); + } + +#ifdef HAVE_READLINE_HISTORY + using_history(); +#endif + char *last_cmd = 0; + while(!context.Stop()) + { + char *command = readline("query > "); + if(command == NULL) + { + // Ctrl-D pressed -- terminate now + break; + } + context.DoCommand(command, false); + if(last_cmd != 0 && ::strcmp(last_cmd, command) == 0) + { + free(command); + } + else + { +#ifdef HAVE_READLINE_HISTORY + add_history(command); +#else + free(last_cmd); +#endif + last_cmd = command; + } + } +#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()) + { + printf("query > "); + fflush(stdout); + std::string command(getLine.GetLine()); + context.DoCommand(command.c_str(), false); + } + } +#endif + + // Done... stop nicely + if(!quiet) BOX_INFO("Logging off..."); + connection.QueryFinished(); + if(!quiet) BOX_INFO("Session finished."); + + // Return code + returnCode = context.GetReturnCode(); + + // Close log file? + if(logFile) + { + ::fclose(logFile); + } + + // Let everything be cleaned up on exit. + +#ifdef WIN32 + // Clean up our sockets + // FIXME we should do this, but I get an abort() when I try + // WSACleanup(); +#endif + + MAINHELPER_END + + return returnCode; +} + diff --git a/bin/bbackupquery/documentation.txt b/bin/bbackupquery/documentation.txt new file mode 100644 index 00000000..96eda158 --- /dev/null +++ b/bin/bbackupquery/documentation.txt @@ -0,0 +1,187 @@ + +bbackupquery utility -- examine store, compare files, restore, etc. + +This file has markers for automatic help generation script -- '>' marks a start of a command/help topic, +and '<' marks the end of a section. + +Command line: +============= + +> bbackupquery [-q] [-c configfile] [commands ...] + + -q -- quiet, no information prompts + -c -- specify another bbackupd configuation file + +The commands following the options are executed, then (if there was no quit +command) an interactive mode is entered. + +If a command contains a space, enclose it in quotes. Example + + bbackupquery "list testdir1" quit + +to list the contents of testdir1, and then exit without interactive mode. +< + +Commands: +========= + +All directory names relative to a "current" directory, or from root if they +start with '/'. The initial directory is always the root directory. + + +> list [options] [directory-name] + + List contents of current directory, or specified directory. + + -r -- recursively list all files + -d -- list deleted files/directories + -o -- list old versions of files/directories + -I -- don't display object ID + -F -- don't display flags + -t -- show file modification time in local time + (and attr mod time if has the object has attributes, ~ separated) + -T -- show file modification time in GMT + -a -- show updated attribute instead of file modification time + -s -- show file size in blocks used on server + (only very approximate indication of size locally) + -h -- show file attributes hash + +ls can be used as an alias. +< + +> ls + + Alias for 'list'. Type 'help list' for options. +< + +> cd [options] + + Change directory + + -d -- consider deleted directories for traversal + -o -- consider old versions of directories for traversal + (this option should never be useful in a correctly formed store) +< + +> pwd + + Print current directory, always root relative. +< + +> lcd + + Change local directory. + + Type "sh ls" to list the contents. +< + +> sh + + All of the parameters after the "sh" are run as a shell command. + + For example, to list the contents of the location directory, type "sh ls" +< + +> get [] +get -i + + Gets a file from the store. Object is specified as the filename within + the current directory, and local filename is optional. Ignores old and + deleted files when searching the directory for the file to retrieve. + + To get an old or deleted file, use the -i option and select the object + as a hex object ID (first column in listing). The local filename must + be specified. +< + +> compare -a +compare -l +compare + + Compares the (current) data on the store with the data on the disc. + All the data will be downloaded -- this is potentially a very long + operation. + + -a -- compare all locations + -l -- compare one backup location as specified in the configuration file. + -c -- set return code + -q -- quick compare. Only checks file contents against checksums, + doesn't do a full download + -A -- ignore attribute differences + -E -- ignore exclusion settings + + Comparing with the root directory is an error, use -a option instead. + + If -c is set, then the return code (if quit is the next command) will be + 1 Comparison was exact + 2 Differences were found + 3 An error occured + This can be used for automated tests. +< + +> restore [-drif] + + Restores a directory to the local disc. The local directory specified + must not exist (unless a previous restore is being restarted). + + The root cannot be restored -- restore locations individually. + + -d -- restore a deleted directory or deleted files inside + -r -- resume an interrupted restoration + -i -- directory name is actually an ID + -f -- force restore to continue if errors are encountered + + If a restore operation is interrupted for any reason, it can be restarted + using the -r switch. Restore progress information is saved in a file at + regular intervals during the restore operation to allow restarts. +< + +> getobject + + Gets the object specified by the object id (in hex) and stores the raw + contents in the local file specified. + + This is only useful for debugging as it does not decode files from the + stored format, which is encrypted and compressed. +< + +> usage [-m] + + Show space used on the server for this account. + + -m -- display the output in machine-readable form + + Used: Total amount of space used on the server. + Old files: Space used by old files + Deleted files: Space used by deleted files + Directories: Space used by the directory structure. + + When Used exceeds the soft limit, the server will start to remove old and + deleted files until the usage drops below the soft limit. + + After a while, you would expect to see the usage stay at just below the + soft limit. You only need more space if the space used by old and deleted + files is near zero. +< + +> undelete +undelete -i + + Removes the deleted flag from the specified directory name (in the + current directory) or hex object ID. Be careful not to use this + command where a directory already exists with the same name which is + not marked as deleted. +< + +> delete + + Sets the deleted flag on the specified file name (in the current + directory, or with a relative path). +< + +> quit + + End session and exit. +< + + diff --git a/bin/bbackupquery/makedocumentation.pl.in b/bin/bbackupquery/makedocumentation.pl.in new file mode 100755 index 00000000..530c4ff6 --- /dev/null +++ b/bin/bbackupquery/makedocumentation.pl.in @@ -0,0 +1,75 @@ +#!@PERL@ +use strict; + +print "Creating built-in documentation for bbackupquery...\n"; + +open DOC,"documentation.txt" or die "Can't open documentation.txt file"; +my $section; +my %help; +my @in_order; + +while() +{ + if(m/\A>\s+(\w+)/) + { + $section = $1; + m/\A>\s+(.+)\Z/; + $help{$section} = $1."\n"; + push @in_order,$section; + } + elsif(m/\Aautogen_Documentation.cpp" or die "Can't open output file for writing"; + +print OUT <<__E; +// +// Automatically generated file, do not edit. +// + +#include "Box.h" + +#include "MemLeakFindOn.h" + +const char *help_commands[] = +{ +__E + +for(@in_order) +{ + print OUT qq:\t"$_",\n:; +} + +print OUT <<__E; + 0 +}; + +const char *help_text[] = +{ +__E + +for(@in_order) +{ + my $t = $help{$_}; + $t =~ s/\t/ /g; + $t =~ s/\n/\\n/g; + $t =~ s/"/\\"/g; + print OUT qq:\t"$t",\n:; +} + +print OUT <<__E; + 0 +}; + +__E + +close OUT; diff --git a/bin/bbstoreaccounts/bbstoreaccounts.cpp b/bin/bbstoreaccounts/bbstoreaccounts.cpp new file mode 100644 index 00000000..a9d2b0af --- /dev/null +++ b/bin/bbstoreaccounts/bbstoreaccounts.cpp @@ -0,0 +1,626 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbstoreaccounts +// Purpose: backup store administration tool +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "BoxPortsAndFiles.h" +#include "BackupStoreConfigVerify.h" +#include "RaidFileController.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreAccountDatabase.h" +#include "MainHelper.h" +#include "BackupStoreInfo.h" +#include "StoreStructure.h" +#include "NamedLock.h" +#include "UnixUser.h" +#include "BackupStoreCheck.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#include + +// max size of soft limit as percent of hard limit +#define MAX_SOFT_LIMIT_SIZE 97 + +bool sMachineReadableOutput = false; + +void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) +{ + if(SoftLimit > HardLimit) + { + BOX_FATAL("Soft limit must be less than the hard limit."); + exit(1); + } + if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100)) + { + BOX_WARNING("We recommend setting the soft limit below " << + MAX_SOFT_LIMIT_SIZE << "% of the hard limit, or " << + HumanReadableSize((HardLimit * MAX_SOFT_LIMIT_SIZE) + / 100) << " in this case."); + } +} + +int BlockSizeOfDiscSet(int DiscSet) +{ + // Get controller, check disc set number + RaidFileController &controller(RaidFileController::GetController()); + if(DiscSet < 0 || DiscSet >= controller.GetNumDiscSets()) + { + BOX_FATAL("Disc set " << DiscSet << " does not exist."); + exit(1); + } + + // Return block size + return controller.GetDiscSet(DiscSet).GetBlockSize(); +} + +std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int DiscSet) +{ + return FormatUsageBar(Blocks, Blocks * BlockSizeOfDiscSet(DiscSet), + MaxBlocks * BlockSizeOfDiscSet(DiscSet), + sMachineReadableOutput); +} + +int64_t SizeStringToBlocks(const char *string, int DiscSet) +{ + // Find block size + int blockSize = BlockSizeOfDiscSet(DiscSet); + + // Get number + char *endptr = (char*)string; + int64_t number = strtol(string, &endptr, 0); + if(endptr == string || number == LONG_MIN || number == LONG_MAX) + { + BOX_FATAL("'" << string << "' is not a valid number."); + exit(1); + } + + // Check units + switch(*endptr) + { + case 'M': + case 'm': + // Units: Mb + return (number * 1024*1024) / blockSize; + break; + + case 'G': + case 'g': + // Units: Gb + return (number * 1024*1024*1024) / blockSize; + break; + + case 'B': + case 'b': + // Units: Blocks + // Easy! Just return the number specified. + return number; + break; + + default: + BOX_FATAL(string << " has an invalid units specifier " + "(use B for blocks, M for MB, G for GB, eg 2GB)"); + exit(1); + break; + } +} + +bool GetWriteLockOnAccount(NamedLock &rLock, const std::string rRootDir, int DiscSetNum) +{ + std::string writeLockFilename; + StoreStructure::MakeWriteLockFilename(rRootDir, DiscSetNum, writeLockFilename); + + 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); + + if(!gotLock) + { + // Couldn't lock the account -- just stop now + BOX_ERROR("Failed to lock the account, did not change limits. " + "Try again later."); + } + + return gotLock; +} + +int SetLimit(Configuration &rConfig, const std::string &rUsername, int32_t ID, const char *SoftLimitStr, const char *HardLimitStr) +{ + // Become the user specified in the config file? + std::auto_ptr 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 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 + NamedLock writeLock; + if(!GetWriteLockOnAccount(writeLock, rootDir, discSet)) + { + // Failed to get lock + return 1; + } + + // Load the info + std::auto_ptr info(BackupStoreInfo::Load(ID, rootDir, discSet, 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); + + // Save + info->Save(); + + BOX_NOTICE("Limits on account " << BOX_FORMAT_ACCOUNT(ID) << + " changed to " << softlimit << " soft, " << + hardlimit << " hard."); + + return 0; +} + +int AccountInfo(Configuration &rConfig, int32_t ID) +{ + // Load in the account database + std::auto_ptr 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; + } + + // Load it in + BackupStoreAccounts acc(*db); + std::string rootDir; + int discSet; + acc.GetAccountRoot(ID, rootDir, discSet); + std::auto_ptr info(BackupStoreInfo::Load(ID, + rootDir, discSet, true /* ReadOnly */)); + + // Then print out lots of info + std::cout << FormatUsageLineStart("Account ID", sMachineReadableOutput) << + BOX_FORMAT_ACCOUNT(ID) << 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; + std::cout << FormatUsageLineStart("Old files", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInOldFiles(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Deleted files", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDeletedFiles(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Directories", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksInDirectories(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Soft limit", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksSoftLimit(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Hard limit", sMachineReadableOutput) << + BlockSizeToString(info->GetBlocksHardLimit(), + info->GetBlocksHardLimit(), discSet) << std::endl; + std::cout << FormatUsageLineStart("Client store marker", sMachineReadableOutput) << + info->GetLastObjectIDUsed() << std::endl; + + return 0; +} + +int DeleteAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool AskForConfirmation) +{ + // Check user really wants to do this + if(AskForConfirmation) + { + BOX_WARNING("Really delete account " << + BOX_FORMAT_ACCOUNT(ID) << "? (type 'yes' to confirm)"); + char response[256]; + if(::fgets(response, sizeof(response), stdin) == 0 || ::strcmp(response, "yes\n") != 0) + { + BOX_NOTICE("Deletion cancelled."); + return 0; + } + } + + // Load in the account database + std::auto_ptr 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 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); + + // Write back to disc + db->Write(); + + // Remove the store files... + + // First, become the user specified in the config file + std::auto_ptr 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 + } + + // Secondly, work out which directories need wiping + std::vector toDelete; + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet discSet(rcontroller.GetDiscSet(discSetNum)); + for(RaidFileDiscSet::const_iterator i(discSet.begin()); i != discSet.end(); ++i) + { + if(std::find(toDelete.begin(), toDelete.end(), *i) == toDelete.end()) + { + toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir); + } + } + + int retcode = 0; + + // Thirdly, delete the directories... + for(std::vector::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) + { + BOX_NOTICE("Deleting store directory " << (*d) << "..."); + // Just use the rm command to delete the files + std::string cmd("rm -rf "); + cmd += *d; + // Run command + if(::system(cmd.c_str()) != 0) + { + BOX_ERROR("Failed to delete files in " << (*d) << + ", delete them manually."); + retcode = 1; + } + } + + // Success! + return retcode; +} + +int CheckAccount(Configuration &rConfig, const std::string &rUsername, int32_t ID, bool FixErrors, bool Quiet) +{ + // Load in the account database + std::auto_ptr 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); + + // Become the right user + std::auto_ptr 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 + } + + // Check it + BackupStoreCheck check(rootDir, discSetNum, ID, FixErrors, Quiet); + check.Check(); + + 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) +{ + // Load in the account database + std::auto_ptr db(BackupStoreAccountDatabase::Read(rConfig.GetKeyValue("AccountDatabase").c_str())); + + // Already exists? + if(db->EntryExists(ID)) + { + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " already exists."); + return 1; + } + + // Create it. + BackupStoreAccounts acc(*db); + acc.Create(ID, DiscNumber, SoftLimit, HardLimit, rUsername); + + BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created."); + + return 0; +} + +void PrintUsageAndExit() +{ + printf( +"Usage: bbstoreaccounts [-c config_file] action account_id [args]\n" +"Account ID is integer specified in hex\n" +"\n" +"Commands (and arguments):\n" +" create \n" +" Creates the specified account number (in hex with no 0x) on the\n" +" specified raidfile disc set number (see raidfile.conf for valid\n" +" set numbers) with the specified soft and hard limits (in blocks\n" +" if suffixed with B, MB with M, GB with G)\n" +" info [-m] \n" +" Prints information about the specified account including number\n" +" of blocks used. The -m option enable machine-readable output.\n" +" setlimit \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" +" delete [yes]\n" +" Deletes the specified account. Prompts for confirmation unless\n" +" the optional 'yes' parameter is provided.\n" +" check [fix] [quiet]\n" +" Checks the specified account for errors. If the 'fix' option is\n" +" 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" + ); + exit(2); +} + +int main(int argc, const char *argv[]) +{ + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks", + "bbstoreaccounts") + + MAINHELPER_START + + 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 + + // See if there's another entry on the command line + int c; + while((c = getopt(argc, (char * const *)argv, "c:m")) != -1) + { + switch(c) + { + case 'c': + // store argument + configFilename = optarg; + break; + + case 'm': + // enable machine readable output + sMachineReadableOutput = true; + break; + + case '?': + default: + PrintUsageAndExit(); + } + } + // Adjust arguments + argc -= optind; + argv += optind; + + // Read in the configuration file + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupConfigFileVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_ERROR("Invalid configuration file " << configFilename << + ":" << 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()); + + // Then... check we have two arguments + if(argc < 2) + { + PrintUsageAndExit(); + } + + // Get the id + int32_t id; + if(::sscanf(argv[1], "%x", &id) != 1) + { + PrintUsageAndExit(); + } + + // Now do the command. + if(::strcmp(argv[0], "create") == 0) + { + // which disc? + int32_t discnum; + int32_t softlimit; + int32_t hardlimit; + if(argc < 5 + || ::sscanf(argv[2], "%d", &discnum) != 1) + { + BOX_ERROR("create requires raid file disc number, " + "soft and hard limits."); + return 1; + } + + // Decode limits + softlimit = SizeStringToBlocks(argv[3], discnum); + hardlimit = SizeStringToBlocks(argv[4], discnum); + CheckSoftHardLimits(softlimit, hardlimit); + + // Create the account... + return CreateAccount(*config, username, id, discnum, softlimit, hardlimit); + } + else if(::strcmp(argv[0], "info") == 0) + { + // Print information on this account + return AccountInfo(*config, id); + } + else if(::strcmp(argv[0], "setlimit") == 0) + { + // Change the limits on this account + if(argc < 4) + { + BOX_ERROR("setlimit requires soft and hard limits."); + return 1; + } + + return SetLimit(*config, username, id, argv[2], argv[3]); + } + else if(::strcmp(argv[0], "delete") == 0) + { + // Delete an account + bool askForConfirmation = true; + if(argc >= 3 && (::strcmp(argv[2], "yes") == 0)) + { + askForConfirmation = false; + } + return DeleteAccount(*config, username, id, askForConfirmation); + } + else if(::strcmp(argv[0], "check") == 0) + { + bool fixErrors = false; + bool quiet = false; + + // Look at other options + for(int o = 2; o < argc; ++o) + { + if(::strcmp(argv[o], "fix") == 0) + { + fixErrors = true; + } + else if(::strcmp(argv[o], "quiet") == 0) + { + quiet = true; + } + else + { + BOX_ERROR("Unknown option " << argv[o] << "."); + return 2; + } + } + + // Check the account + return CheckAccount(*config, username, id, fixErrors, quiet); + } + else + { + BOX_ERROR("Unknown command '" << argv[0] << "'."); + return 1; + } + + return 0; + + MAINHELPER_END +} + + diff --git a/bin/bbstored/BBStoreDHousekeeping.cpp b/bin/bbstored/BBStoreDHousekeeping.cpp new file mode 100644 index 00000000..7f799008 --- /dev/null +++ b/bin/bbstored/BBStoreDHousekeeping.cpp @@ -0,0 +1,240 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BBStoreDHousekeeping.cpp +// Purpose: Implementation of housekeeping functions for bbstored +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "BackupStoreDaemon.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "HousekeepStoreAccount.h" +#include "BoxTime.h" +#include "Configuration.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::HousekeepingProcess() +// Purpose: Do housekeeping +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::HousekeepingInit() +{ + + mLastHousekeepingRun = 0; +} + +void BackupStoreDaemon::HousekeepingProcess() +{ + HousekeepingInit(); + + // Get the time between housekeeping runs + const Configuration &rconfig(GetConfiguration()); + int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); + + while(!StopRun()) + { + RunHousekeepingIfNeeded(); + + // Stop early? + if(StopRun()) + { + break; + } + + // Calculate how long should wait before doing the next + // housekeeping run + int64_t timeNow = GetCurrentBoxTime(); + time_t secondsToGo = BoxTimeToSeconds( + (mLastHousekeepingRun + housekeepingInterval) - + timeNow); + if(secondsToGo < 1) secondsToGo = 1; + if(secondsToGo > 60) secondsToGo = 60; + int32_t millisecondsToGo = ((int)secondsToGo) * 1000; + + // Check to see if there's any message pending + CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); + } +} + +void BackupStoreDaemon::RunHousekeepingIfNeeded() +{ + // Get the time between housekeeping runs + const Configuration &rconfig(GetConfiguration()); + int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); + + // Time now + int64_t timeNow = GetCurrentBoxTime(); + + // Do housekeeping if the time interval has elapsed since the last check + if((timeNow - mLastHousekeepingRun) < housekeepingInterval) + { + return; + } + + // Store the time + mLastHousekeepingRun = timeNow; + BOX_INFO("Starting housekeeping"); + + // Get the list of accounts + std::vector accounts; + if(mpAccountDatabase) + { + mpAccountDatabase->GetAllAccountIDs(accounts); + } + + SetProcessTitle("housekeeping, active"); + + // Check them all + for(std::vector::const_iterator i = accounts.begin(); i != accounts.end(); ++i) + { + try + { + if(mpAccounts) + { + // 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(); + } + } + catch(BoxException &e) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what() << " (" << + e.GetType() << "/" << e.GetSubType() << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " + "unknown exception"); + } + + int64_t timeNow = GetCurrentBoxTime(); + time_t secondsToGo = BoxTimeToSeconds( + (mLastHousekeepingRun + housekeepingInterval) - + timeNow); + if(secondsToGo < 1) secondsToGo = 1; + if(secondsToGo > 60) secondsToGo = 60; + int32_t millisecondsToGo = ((int)secondsToGo) * 1000; + + // Check to see if there's any message pending + CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); + + // Stop early? + if(StopRun()) + { + break; + } + } + + BOX_INFO("Finished housekeeping"); + + // Placed here for accuracy, if StopRun() is true, for example. + SetProcessTitle("housekeeping, idle"); +} + +void BackupStoreDaemon::OnIdle() +{ + if (!IsSingleProcess()) + { + return; + } + + if (!mHousekeepingInited) + { + HousekeepingInit(); + mHousekeepingInited = true; + } + + RunHousekeepingIfNeeded(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::CheckForInterProcessMsg(int, int) +// Purpose: Process a message, returning true if the housekeeping process +// should abort for the specified account. +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitTime) +{ + if(!mInterProcessCommsSocket.IsOpened()) + { + return false; + } + + // First, check to see if it's EOF -- this means something has gone wrong, and the housekeeping should terminate. + if(mInterProcessComms.IsEOF()) + { + SetTerminateWanted(); + return true; + } + + // Get a line, and process the message + std::string line; + if(mInterProcessComms.GetLine(line, false /* no pre-processing */, MaximumWaitTime)) + { + BOX_TRACE("Housekeeping received command '" << line << + "' over interprocess comms"); + + int account = 0; + + if(line == "h") + { + // HUP signal received by main process + SetReloadConfigWanted(); + return true; + } + else if(line == "t") + { + // Terminate signal received by main process + SetTerminateWanted(); + return true; + } + else if(sscanf(line.c_str(), "r%x", &account) == 1) + { + // Main process is trying to lock an account -- are we processing it? + if(account == AccountNum) + { + // Yes! -- need to stop now so when it retries to get the lock, it will succeed + BOX_INFO("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(AccountNum) << + "giving way to client connection"); + return true; + } + } + } + + return false; +} + + diff --git a/bin/bbstored/BackupCommands.cpp b/bin/bbstored/BackupCommands.cpp new file mode 100644 index 00000000..c27cb7ab --- /dev/null +++ b/bin/bbstored/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 +#include + +#include "autogen_BackupProtocolServer.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 CHECK_PHASE(phase) \ + if(rContext.GetPhase() != BackupStoreContext::phase) \ + { \ + return std::auto_ptr(new BackupProtocolServerError( \ + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_NotInRightProtocolPhase)); \ + } + +#define CHECK_WRITEABLE_SESSION \ + if(rContext.SessionIsReadOnly()) \ + { \ + return std::auto_ptr(new BackupProtocolServerError( \ + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_SessionReadOnly)); \ + } + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerVersion::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 BackupProtocolServerVersion::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Version) + + // Correct version? + if(mVersion != BACKUP_STORE_SERVER_VERSION) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_WrongVersion)); + } + + // Mark the next phase + rContext.SetPhase(BackupStoreContext::Phase_Login); + + // Return our version + return std::auto_ptr(new BackupProtocolServerVersion(BACKUP_STORE_SERVER_VERSION)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerLogin::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 BackupProtocolServerLogin::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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 std::auto_ptr( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_BadLogin)); + } + + if(!rContext.GetClientHasAccount()) + { + BOX_WARNING("Failed login from client ID " << + BOX_FORMAT_ACCOUNT(mClientID) << + ": no such account on this server"); + return std::auto_ptr( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::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 std::auto_ptr( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_CannotLockStoreForWriting)); + } + + // Debug: check we got the lock + ASSERT(!rContext.SessionIsReadOnly()); + } + + // Load the store info + rContext.LoadStoreInfo(); + + // 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) << + " " << + (((mFlags & Flags_ReadOnly) != Flags_ReadOnly) + ?"Read/Write":"Read-only")); + + // 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(new BackupProtocolServerLoginConfirmed(clientStoreMarker, blocksUsed, blocksSoftLimit, blocksHardLimit)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerFinished::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Marks end of conversation (Protocol framework handles this) +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerFinished::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + BOX_NOTICE("Session finished for Client ID " << + BOX_FORMAT_ACCOUNT(rContext.GetClientID())); + + // Let the context know about it + rContext.ReceivedFinishCommand(); + + // can be called in any phase + return std::auto_ptr(new BackupProtocolServerFinished); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerListDirectory::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Command to list a directory +// Created: 2003/09/02 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerListDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // Store the listing to a stream + std::auto_ptr 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 std::auto_ptr( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::Err_DoesNotExist)); + } + throw; + } + + stream->SetForReading(); + + // Get the protocol to send the stream + rProtocol.SendStreamAfterCommand(stream.release()); + + return std::auto_ptr( + new BackupProtocolServerSuccess(mObjectID)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerStoreFile::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Command to store a file on the server +// Created: 2003/09/02 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerStoreFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + std::auto_ptr 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DiffFromFileDoesNotExist)); + } + } + + // A stream follows, which contains the file + std::auto_ptr dirstream(rProtocol.ReceiveStream()); + + // Ask the context to store it + int64_t id = 0; + try + { + id = rContext.AddFile(*dirstream, mDirectoryObjectID, mModificationTime, mAttributesHash, mDiffFromFileID, + mFilename, true /* mark files with same name as old versions */); + } + catch(BackupStoreException &e) + { + if(e.GetSubType() == BackupStoreException::AddedFileDoesNotVerify) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_FileDoesNotVerify)); + } + else if(e.GetSubType() == BackupStoreException::AddedFileExceedsStorageLimit) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_StorageLimitExceeded)); + } + else + { + throw; + } + } + + // Tell the caller what the file was + return std::auto_ptr(new BackupProtocolServerSuccess(id)); +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetObject::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Command to get an arbitary object from the server +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // Check the object exists + if(!rContext.ObjectExists(mObjectID)) + { + return std::auto_ptr(new BackupProtocolServerSuccess(NoObject)); + } + + // Open the object + std::auto_ptr object(rContext.OpenObject(mObjectID)); + + // Stream it to the peer + rProtocol.SendStreamAfterCommand(object.release()); + + // Tell the caller what the file was + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetFile::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 BackupProtocolServerGetFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // Check the objects exist + if(!rContext.ObjectExists(mObjectID) + || !rContext.ObjectExists(mInDirectory)) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExistInDirectory)); + } + + // The result + std::auto_ptr 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 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 std::auto_ptr( + new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, + BackupProtocolServerError::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 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 diff(rContext.OpenObject(patchID)); + std::auto_ptr 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 combined; + try + { + { + // Write nastily to allow this to work with gcc 2.x + std::auto_ptr 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 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 object(rContext.OpenObject(mObjectID)); + BufferedStream buf(*object); + + // Verify it + if(!BackupStoreFile::VerifyEncodedFileFormat(buf)) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::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 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.get()); + + // Don't delete the stream here + stream.release(); + + // Tell the caller what the file was + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerCreateDirectory::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Create directory command +// Created: 2003/09/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerCreateDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Get the stream containing the attributes + std::auto_ptr 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_StorageLimitExceeded)); + } + + bool alreadyExists = false; + int64_t id = rContext.AddDirectory(mContainingDirectoryID, mDirectoryName, attr, mAttributesModTime, alreadyExists); + + if(alreadyExists) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DirectoryAlreadyExists)); + } + + // Tell the caller what the file was + return std::auto_ptr(new BackupProtocolServerSuccess(id)); +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerChangeDirAttributes::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Change attributes on directory +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerChangeDirAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Get the stream containing the attributes + std::auto_ptr 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(new BackupProtocolServerSuccess(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerSetReplacementFileAttributes::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Change attributes on directory +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerSetReplacementFileAttributes::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Get the stream containing the attributes + std::auto_ptr 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); + } + + // Tell the caller what the file was + return std::auto_ptr(new BackupProtocolServerSuccess(objectID)); +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Delete a file +// Created: 2003/10/21 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerDeleteFile::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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(new BackupProtocolServerSuccess(objectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerUndeleteFile::DoCommand( +// BackupProtocolServer &, BackupStoreContext &) +// Purpose: Undelete a file +// Created: 2008-09-12 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerUndeleteFile::DoCommand( + BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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( + new BackupProtocolServerSuccess(result ? mObjectID : 0)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Delete a directory +// Created: 2003/10/21 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerDeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotDeleteRoot)); + } + + // Context handles this + rContext.DeleteDirectory(mObjectID); + + // return the object ID + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Undelete a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerUndeleteDirectory::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_CannotDeleteRoot)); + } + + // Context handles this + rContext.DeleteDirectory(mObjectID, true /* undelete */); + + // return the object ID + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Command to set the client's store marker +// Created: 2003/10/29 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerSetClientStoreMarker::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Set the marker + rContext.SetClientStoreMarker(mClientStoreMarker); + + // return store marker set + return std::auto_ptr(new BackupProtocolServerSuccess(mClientStoreMarker)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Command to move an object from one directory to another +// Created: 2003/11/12 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerMoveObject::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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 std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_DoesNotExist)); + } + else if(e.GetSubType() == BackupStoreException::NameAlreadyExistsInDirectory) + { + return std::auto_ptr(new BackupProtocolServerError( + BackupProtocolServerError::ErrorType, BackupProtocolServerError::Err_TargetNameExists)); + } + else + { + throw; + } + } + + // Return the object ID + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Command to find the name of an object +// Created: 12/11/03 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetObjectName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // Create a stream for the list of filenames + std::auto_ptr 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(new BackupProtocolServerObjectName(BackupProtocolServerObjectName::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(new BackupProtocolServerObjectName(BackupProtocolServerObjectName::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(stream.release()); + } + + // Make reply + return std::auto_ptr(new BackupProtocolServerObjectName(numNameElements, modTime, attrModHash, objectFlags)); +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Get the block index from a file, by ID +// Created: 19/1/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetBlockIndexByID::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // Open the file + std::auto_ptr stream(rContext.OpenObject(mObjectID)); + + // Move the file pointer to the block index + BackupStoreFile::MoveStreamPositionToBlockIndex(*stream); + + // Return the stream to the client + rProtocol.SendStreamAfterCommand(stream.release()); + + // Return the object ID + return std::auto_ptr(new BackupProtocolServerSuccess(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Get the block index from a file, by name within a directory +// Created: 19/1/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetBlockIndexByName::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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(new BackupProtocolServerSuccess(0)); + } + + // Open the file + std::auto_ptr stream(rContext.OpenObject(objectID)); + + // Move the file pointer to the block index + BackupStoreFile::MoveStreamPositionToBlockIndex(*stream); + + // Return the stream to the client + rProtocol.SendStreamAfterCommand(stream.release()); + + // Return the object ID + return std::auto_ptr(new BackupProtocolServerSuccess(objectID)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Return the amount of disc space used +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetAccountUsage::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + 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(new BackupProtocolServerAccountUsage( + rinfo.GetBlocksUsed(), + rinfo.GetBlocksInOldFiles(), + rinfo.GetBlocksInDeletedFiles(), + rinfo.GetBlocksInDirectories(), + rinfo.GetBlocksSoftLimit(), + rinfo.GetBlocksHardLimit(), + rdiscSet.GetBlockSize() + )); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &, BackupStoreContext &) +// Purpose: Return the amount of disc space used +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr BackupProtocolServerGetIsAlive::DoCommand(BackupProtocolServer &rProtocol, BackupStoreContext &rContext) +{ + CHECK_PHASE(Phase_Commands) + + // + // NOOP + // + return std::auto_ptr(new BackupProtocolServerIsAlive()); +} diff --git a/bin/bbstored/BackupConstants.h b/bin/bbstored/BackupConstants.h new file mode 100644 index 00000000..19d06a15 --- /dev/null +++ b/bin/bbstored/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/bin/bbstored/BackupStoreContext.cpp b/bin/bbstored/BackupStoreContext.cpp new file mode 100644 index 00000000..5ee13faa --- /dev/null +++ b/bin/bbstored/BackupStoreContext.cpp @@ -0,0 +1,1785 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreContext.cpp +// Purpose: Context for backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "BackupConstants.h" +#include "BackupStoreContext.h" +#include "BackupStoreDaemon.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) + : 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::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(mpStoreInfo.get() && !(mpStoreInfo->IsReadOnly()) && mpStoreInfo->IsModified()) + { + mpStoreInfo->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 && mpStoreInfo.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(mpStoreInfo.get() != 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoAlreadyLoaded) + } + + // Load it up! + std::auto_ptr i(BackupStoreInfo::Load(mClientID, mStoreRoot, mStoreDiscSet, mReadOnly)); + + // Check it + if(i->GetAccountID() != mClientID) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoForWrongAccount) + } + + // Keep the pointer to it + mpStoreInfo = 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(mpStoreInfo.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 + mpStoreInfo->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::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::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 objectFile(RaidFileRead::Open(mStoreDiscSet, filename, &revID)); + ASSERT(revID != 0); + + // New directory object + std::auto_ptr 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(mpStoreInfo.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 = mpStoreInfo->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(mpStoreInfo.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 blocksUsed = 0; + RaidFileWrite *ppreviousVerStoreFile = 0; + bool reversedDiffIsCompletelyDifferent = false; + int64_t oldVersionNewBlocksUsed = 0; + try + { + RaidFileWrite storeFile(mStoreDiscSet, fn); + storeFile.Open(false /* no overwriting */); + int64_t spaceAdjustFromDiff = 0; // size adjustment from use of patch in old file + + // 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 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 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 + spaceAdjustFromDiff = 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 + blocksUsed = storeFile.GetDiscUsageInBlocks(); + + // Exceeds the hard limit? + if((mpStoreInfo->GetBlocksUsed() + blocksUsed - spaceAdjustFromDiff) > mpStoreInfo->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 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->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) == 0) + { + // 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, blocksUsed, 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 + blocksUsed += (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 + mpStoreInfo->ChangeBlocksUsed(blocksUsed); + mpStoreInfo->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(); + + // 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(mpStoreInfo.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 + mpStoreInfo->ChangeBlocksInDeletedFiles(blocksDel); + + // Maybe postponed save of store info + SaveStoreInfo(); + } + } + 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(mpStoreInfo.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 + mpStoreInfo->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::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(mpStoreInfo.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(); + mpStoreInfo->ChangeBlocksUsed(sizeAdjustment); + mpStoreInfo->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(mpStoreInfo.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); + mpStoreInfo->ChangeBlocksUsed(dirSize); + mpStoreInfo->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 be postponed) + SaveStoreInfo(); + + // 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(mpStoreInfo.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 + mpStoreInfo->ChangeBlocksInDeletedFiles(Undelete?(0 - blocksDeleted):(blocksDeleted)); + + // Save store info, may be postponed + SaveStoreInfo(); + } + 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 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::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(mpStoreInfo.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(mpStoreInfo.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(mpStoreInfo.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 > (mpStoreInfo->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 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 BackupStoreContext::OpenObject(int64_t ObjectID) +{ + if(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + // Attempt to open the file + std::string fn; + MakeObjectFilename(ObjectID, fn); + return std::auto_ptr(RaidFileRead::Open(mStoreDiscSet, fn).release()); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreContext::GetClientStoreMarker() +// Purpose: Retrieve the client store marker +// Created: 2003/10/29 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreContext::GetClientStoreMarker() +{ + if(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + return mpStoreInfo->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(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + rBlocksUsed = mpStoreInfo->GetBlocksUsed(); + rBlocksSoftLimit = mpStoreInfo->GetBlocksSoftLimit(); + rBlocksHardLimit = mpStoreInfo->GetBlocksHardLimit(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreContext::HardLimitExceeded() +// Purpose: Returns true if the hard limit has been exceeded +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreContext::HardLimitExceeded() +{ + if(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + return mpStoreInfo->GetBlocksUsed() > mpStoreInfo->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(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, ContextIsReadOnly) + } + + mpStoreInfo->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 moving; + + // list of directory IDs which need to have containing dir id changed + std::vector 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::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::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::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::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::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(mpStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + return *(mpStoreInfo.get()); +} + + diff --git a/bin/bbstored/BackupStoreContext.h b/bin/bbstored/BackupStoreContext.h new file mode 100644 index 00000000..6053e4b8 --- /dev/null +++ b/bin/bbstored/BackupStoreContext.h @@ -0,0 +1,186 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreContext.h +// Purpose: Context for backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCONTEXT__H +#define BACKUPCONTEXT__H + +#include +#include +#include + +#include "BackupStoreRefCountDatabase.h" +#include "NamedLock.h" +#include "ProtocolObject.h" +#include "Utils.h" + +class BackupStoreDirectory; +class BackupStoreFilename; +class BackupStoreDaemon; +class BackupStoreInfo; +class IOStream; +class BackupProtocolObject; +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); + ~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;} + 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; + + // 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 OpenObject(int64_t ObjectID); + + // Info + int32_t GetClientID() const {return mClientID;} + +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(); + + 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 mpStoreInfo; + + // Refcount database + std::auto_ptr mapRefCount; + + // Directory cache + std::map mDirectoryCache; + +public: + class TestHook + { + public: + virtual std::auto_ptr StartCommand(BackupProtocolObject& + rCommand) = 0; + virtual ~TestHook() { } + }; + void SetTestHook(TestHook& rTestHook) + { + mpTestHook = &rTestHook; + } + std::auto_ptr StartCommandHook(BackupProtocolObject& rCommand) + { + if(mpTestHook) + { + return mpTestHook->StartCommand(rCommand); + } + return std::auto_ptr(); + } + +private: + TestHook* mpTestHook; +}; + +#endif // BACKUPCONTEXT__H + diff --git a/bin/bbstored/BackupStoreDaemon.cpp b/bin/bbstored/BackupStoreDaemon.cpp new file mode 100644 index 00000000..4de0a078 --- /dev/null +++ b/bin/bbstored/BackupStoreDaemon.cpp @@ -0,0 +1,371 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreDaemon.cpp +// Purpose: Backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#ifdef HAVE_SYSLOG_H + #include +#endif + +#include "BackupStoreContext.h" +#include "BackupStoreDaemon.h" +#include "BackupStoreConfigVerify.h" +#include "autogen_BackupProtocolServer.h" +#include "RaidFileController.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "BannerText.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::BackupStoreDaemon() +// Purpose: Constructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreDaemon::BackupStoreDaemon() + : mpAccountDatabase(0), + mpAccounts(0), + mExtendedLogging(false), + mHaveForkedHousekeeping(false), + mIsHousekeepingProcess(false), + mHousekeepingInited(false), + mInterProcessComms(mInterProcessCommsSocket), + mpTestHook(NULL) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::~BackupStoreDaemon() +// Purpose: Destructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreDaemon::~BackupStoreDaemon() +{ + // Must delete this one before the database ... + if(mpAccounts != 0) + { + delete mpAccounts; + mpAccounts = 0; + } + // ... as the mpAccounts object has a reference to it + if(mpAccountDatabase != 0) + { + delete mpAccountDatabase; + mpAccountDatabase = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::DaemonName() +// Purpose: Name of daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +const char *BackupStoreDaemon::DaemonName() const +{ + return "bbstored"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::DaemonBanner() +// Purpose: Daemon banner +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string BackupStoreDaemon::DaemonBanner() const +{ + return BANNER_TEXT("Backup Store Server"); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::GetConfigVerify() +// Purpose: Configuration definition +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *BackupStoreDaemon::GetConfigVerify() const +{ + return &BackupConfigFileVerify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::SetupInInitialProcess() +// Purpose: Setup before we fork -- get raid file controller going +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::SetupInInitialProcess() +{ + const Configuration &config(GetConfiguration()); + + // Initialise the raid files controller + RaidFileController &rcontroller = RaidFileController::GetController(); + + std::string raidFileConfig; + + #ifdef WIN32 + if (!config.KeyExists("RaidFileConf")) + { + raidFileConfig = BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE; + } + else + { + raidFileConfig = config.GetKeyValue("RaidFileConf"); + } + #else + raidFileConfig = config.GetKeyValue("RaidFileConf"); + #endif + + rcontroller.Initialise(raidFileConfig); + + // Load the account database + std::auto_ptr pdb(BackupStoreAccountDatabase::Read(config.GetKeyValue("AccountDatabase").c_str())); + mpAccountDatabase = pdb.release(); + + // Create a accounts object + mpAccounts = new BackupStoreAccounts(*mpAccountDatabase); + + // Ready to go! +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Run() +// Purpose: Run shim for the store daemon -- read some config details +// Created: 2003/10/24 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Run() +{ + // Get extended logging flag + mExtendedLogging = false; + const Configuration &config(GetConfiguration()); + mExtendedLogging = config.GetKeyValueBool("ExtendedLogging"); + + // Fork off housekeeping daemon -- must only do this the first + // time Run() is called. Housekeeping runs synchronously on Win32 + // because IsSingleProcess() is always true + +#ifndef WIN32 + if(!IsSingleProcess() && !mHaveForkedHousekeeping) + { + // Open a socket pair for communication + int sv[2] = {-1,-1}; + if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) + { + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + int whichSocket = 0; + + // Fork + switch(::fork()) + { + case -1: + { + // Error + THROW_EXCEPTION(ServerException, ServerForkError) + } + break; + case 0: + { + // In child process + mIsHousekeepingProcess = true; + SetProcessTitle("housekeeping, idle"); + whichSocket = 1; + // Change the log name + ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); + // Log that housekeeping started + BOX_INFO("Housekeeping process started"); + // Ignore term and hup + // Parent will handle these and alert the + // child via the socket, don't want to + // randomly die! + ::signal(SIGHUP, SIG_IGN); + ::signal(SIGTERM, SIG_IGN); + } + break; + default: + { + // Parent process + whichSocket = 0; + } + break; + } + + // Mark that this has been, so -HUP doesn't try and do this again + mHaveForkedHousekeeping = true; + + // Attach the comms thing to the right socket, and close the other one + mInterProcessCommsSocket.Attach(sv[whichSocket]); + + if(::close(sv[(whichSocket == 0)?1:0]) != 0) + { + THROW_EXCEPTION(ServerException, SocketCloseError) + } + } +#endif // WIN32 + + if(mIsHousekeepingProcess) + { + // Housekeeping process -- do other stuff + HousekeepingProcess(); + } + else + { + // In server process -- use the base class to do the magic + ServerTLS::Run(); + + if (!mInterProcessCommsSocket.IsOpened()) + { + return; + } + + // Why did it stop? Tell the housekeeping process to do the same + if(IsReloadConfigWanted()) + { + mInterProcessCommsSocket.Write("h\n", 2); + } + + if(IsTerminateWanted()) + { + mInterProcessCommsSocket.Write("t\n", 2); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Connection(SocketStreamTLS &) +// Purpose: Handles a connection, by catching exceptions and +// delegating to Connection2 +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Connection(SocketStreamTLS &rStream) +{ + try + { + Connection2(rStream); + } + catch(BoxException &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what() << " (" << e.GetType() << "/" << + e.GetSubType() << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Error in child process, terminating connection: " << + "unknown exception"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Connection2(SocketStreamTLS &) +// Purpose: Handles a connection from bbackupd +// Created: 2006/11/12 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Connection2(SocketStreamTLS &rStream) +{ + // Get the common name from the certificate + std::string clientCommonName(rStream.GetPeerCommonName()); + + // Log the name + BOX_INFO("Client certificate CN: " << clientCommonName); + + // Check it + int32_t id; + if(::sscanf(clientCommonName.c_str(), "BACKUP-%x", &id) != 1) + { + // Bad! Disconnect immediately + return; + } + + // Make ps listings clearer + std::ostringstream tag; + tag << "client=" << BOX_FORMAT_ACCOUNT(id); + SetProcessTitle(tag.str().c_str()); + Logging::Tagger tagWithClientID(tag.str()); + + // Create a context, using this ID + BackupStoreContext context(id, *this); + + if (mpTestHook) + { + context.SetTestHook(*mpTestHook); + } + + // See if the client has an account? + if(mpAccounts && mpAccounts->AccountExists(id)) + { + std::string root; + int discSet; + mpAccounts->GetAccountRoot(id, root, discSet); + context.SetClientHasAccount(root, discSet); + } + + // Handle a connection with the backup protocol + BackupProtocolServer server(rStream); + server.SetLogToSysLog(mExtendedLogging); + server.SetTimeout(BACKUP_STORE_TIMEOUT); + try + { + server.DoServer(context); + } + catch(...) + { + LogConnectionStats(clientCommonName.c_str(), rStream); + throw; + } + LogConnectionStats(clientCommonName.c_str(), rStream); + context.CleanUp(); +} + +void BackupStoreDaemon::LogConnectionStats(const char *commonName, + const SocketStreamTLS &s) +{ + // Log the amount of data transferred + BOX_NOTICE("Connection statistics for " << commonName << ":" + " IN=" << s.GetBytesRead() << + " OUT=" << s.GetBytesWritten() << + " TOTAL=" << (s.GetBytesRead() + s.GetBytesWritten())); +} diff --git a/bin/bbstored/BackupStoreDaemon.h b/bin/bbstored/BackupStoreDaemon.h new file mode 100644 index 00000000..49af5b81 --- /dev/null +++ b/bin/bbstored/BackupStoreDaemon.h @@ -0,0 +1,100 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreDaemon.h +// Purpose: Backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREDAEMON__H +#define BACKUPSTOREDAEMON__H + +#include "ServerTLS.h" +#include "BoxPortsAndFiles.h" +#include "BackupConstants.h" +#include "BackupStoreContext.h" +#include "HousekeepStoreAccount.h" +#include "IOStreamGetLine.h" + +class BackupStoreAccounts; +class BackupStoreAccountDatabase; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreDaemon +// Purpose: Backup store daemon implementation +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +class BackupStoreDaemon : public ServerTLS, + HousekeepingInterface, HousekeepingCallback +{ +public: + BackupStoreDaemon(); + ~BackupStoreDaemon(); +private: + BackupStoreDaemon(const BackupStoreDaemon &rToCopy); +public: + + // For BackupStoreContext to communicate with housekeeping process + void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) + { +#ifndef WIN32 + mInterProcessCommsSocket.Write(Msg, MsgLen); +#endif + } + +protected: + + virtual void SetupInInitialProcess(); + + virtual void Run(); + + virtual void Connection(SocketStreamTLS &rStream); + void Connection2(SocketStreamTLS &rStream); + + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + + const ConfigurationVerify *GetConfigVerify() const; + + // Housekeeping functions + void HousekeepingProcess(); + + void LogConnectionStats(const char *commonName, const SocketStreamTLS &s); + +public: + // HousekeepingInterface implementation + virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0); + +private: + BackupStoreAccountDatabase *mpAccountDatabase; + BackupStoreAccounts *mpAccounts; + bool mExtendedLogging; + bool mHaveForkedHousekeeping; + bool mIsHousekeepingProcess; + bool mHousekeepingInited; + + SocketStream mInterProcessCommsSocket; + IOStreamGetLine mInterProcessComms; + + virtual void OnIdle(); + void HousekeepingInit(); + void RunHousekeepingIfNeeded(); + int64_t mLastHousekeepingRun; + +public: + void SetTestHook(BackupStoreContext::TestHook& rTestHook) + { + mpTestHook = &rTestHook; + } + +private: + BackupStoreContext::TestHook* mpTestHook; +}; + + +#endif // BACKUPSTOREDAEMON__H + diff --git a/bin/bbstored/HousekeepStoreAccount.cpp b/bin/bbstored/HousekeepStoreAccount.cpp new file mode 100644 index 00000000..0ccbcf23 --- /dev/null +++ b/bin/bbstored/HousekeepStoreAccount.cpp @@ -0,0 +1,1067 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HousekeepStoreAccount.cpp +// Purpose: +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include + +#include "HousekeepStoreAccount.h" +#include "BackupStoreDaemon.h" +#include "StoreStructure.h" +#include "BackupStoreConstants.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreInfo.h" +#include "NamedLock.h" +#include "autogen_BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BufferedStream.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), + mCountUntilNextInterprocessMsgCheck(POLL_INTERPROCESS_MSG_CHECK_FREQUENCY) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HousekeepStoreAccount::~HousekeepStoreAccount() +// Purpose: Destructor +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +HousekeepStoreAccount::~HousekeepStoreAccount() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HousekeepStoreAccount::DoHousekeeping() +// Purpose: Perform the housekeeping +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void HousekeepStoreAccount::DoHousekeeping(bool KeepTryingForever) +{ + // 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; + } + } + + // Load the store info to find necessary info for the housekeeping + std::auto_ptr info(BackupStoreInfo::Load(mAccountID, + mStoreRoot, mStoreDiscSet, false /* Read/Write */)); + + // 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->Save(); + } + + return; + } + + // 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->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 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]); + 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]); + } + } + + // 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); + } + } + + // 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->Save(); + + // Explicity release the lock (would happen automatically on + // going out of scope, included for code clarity) + writeLock.ReleaseLock(); +} + + + +// -------------------------------------------------------------------------- +// +// 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 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 version_t; + std::map 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::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::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::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 targetted 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::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 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 senarios. +// 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 commiting if the directory save goes OK + std::auto_ptr 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 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 pdiff(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder)); + std::auto_ptr pdiff2(RaidFileRead::Open(mStoreDiscSet, objFilenameOlder)); + // Open this file + std::string objFilename; + MakeObjectFilename(ObjectID, objFilename); + std::auto_ptr pobjectBeingDeleted(RaidFileRead::Open(mStoreDiscSet, objFilename)); + // And open a write file to overwrite the other directory entry + padjustedEntry.reset(new RaidFileWrite(mStoreDiscSet, objFilenameOlder)); + 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); + 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) + { + BOX_TRACE("Removing unreferenced object " << + BOX_FORMAT_OBJECTID(ObjectID)); + std::string objFilename; + MakeObjectFilename(ObjectID, objFilename); + RaidFileWrite del(mStoreDiscSet, objFilename); + 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 toExamine; + + // Go through list + for(std::vector::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& 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 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 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); + 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; + } + + // Delete the directory itself + { + RaidFileWrite del(mStoreDiscSet, dirFilename); + 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/bin/bbstored/HousekeepStoreAccount.h b/bin/bbstored/HousekeepStoreAccount.h new file mode 100644 index 00000000..1dd6d79c --- /dev/null +++ b/bin/bbstored/HousekeepStoreAccount.h @@ -0,0 +1,111 @@ +// -------------------------------------------------------------------------- +// +// 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 +#include +#include + +class BackupStoreDaemon; +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(); + + void DoHousekeeping(bool KeepTryingForever = false); + + +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& 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 mPotentialDeletions; + int64_t mPotentialDeletionsTotalSize; + int64_t mMaxSizeInPotentialDeletions; + + // List of directories which are empty, and might be good for deleting + std::vector 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 mNewRefCounts; + bool mSuppressRefCountChangeWarnings; + + // Poll frequency + int mCountUntilNextInterprocessMsgCheck; +}; + +#endif // HOUSEKEEPSTOREACCOUNT__H + diff --git a/bin/bbstored/Makefile.extra b/bin/bbstored/Makefile.extra new file mode 100644 index 00000000..6562647d --- /dev/null +++ b/bin/bbstored/Makefile.extra @@ -0,0 +1,9 @@ + +MAKEPROTOCOL = ../../lib/server/makeprotocol.pl + +GEN_CMD_SRV = $(MAKEPROTOCOL) Server backupprotocol.txt + +# AUTOGEN SEEDING +autogen_BackupProtocolServer.cpp autogen_BackupProtocolServer.h: $(MAKEPROTOCOL) backupprotocol.txt + $(_PERL) $(GEN_CMD_SRV) + diff --git a/bin/bbstored/backupprotocol.txt b/bin/bbstored/backupprotocol.txt new file mode 100644 index 00000000..3eca514a --- /dev/null +++ b/bin/bbstored/backupprotocol.txt @@ -0,0 +1,234 @@ +# +# backup protocol definition +# + +Name Backup +IdentString Box-Backup:v=C +ServerContextClass BackupStoreContext BackupStoreContext.h + +ClientType Filename BackupStoreFilenameClear BackupStoreFilenameClear.h +ServerType Filename BackupStoreFilename BackupStoreFilename.h + +ImplementLog Server syslog +ImplementLog Client syslog +ImplementLog Client file + +LogTypeToText Client Filename \"%s\" VAR.GetClearFilename().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 + +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/bin/bbstored/bbstored-certs.in b/bin/bbstored/bbstored-certs.in new file mode 100755 index 00000000..85560748 --- /dev/null +++ b/bin/bbstored/bbstored-certs.in @@ -0,0 +1,319 @@ +#!@PERL@ +use strict; + +# validity period for root certificates -- default is 2038, the best we can do for now +my $root_sign_period = int(((1<<31) - time()) / 86400); + +# but less so for client certificates +my $sign_period = '5000'; + +# check and get command line parameters +if($#ARGV < 1) +{ + print <<__E; + +bbstored certificates utility. + +Bad command line parameters. +Usage: + bbstored-certs certs-dir command [arguments] + +certs-dir is the directory holding the root keys and certificates for the backup system +command is the action to perform, taking parameters. + +Commands are + + init + -- generate initial root certificates (certs-dir must not already exist) + sign certificate-name + -- sign a client certificate + sign-server certificate-name + -- sign a server certificate + +Signing requires confirmation that the certificate is correct and should be signed. + +__E + exit(1); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# directory structure: +# +# roots/ +# clientCA.pem -- root certificate for client (used on server) +# serverCA.pem -- root certificate for servers (used on clients) +# keys/ +# clientRootKey.pem -- root key for clients +# serverRootKey.pem -- root key for servers +# servers/ +# hostname.pem -- certificate for server 'hostname' +# clients/ +# account.pem -- certficiate for account 'account' (ID in hex) +# + + +# check parameters +my ($cert_dir,$command,@args) = @ARGV; + +# check directory exists +if($command ne 'init') +{ + if(!-d $cert_dir) + { + die "$cert_dir does not exist"; + } +} + +# run command +if($command eq 'init') {&cmd_init;} +elsif($command eq 'sign') {&cmd_sign;} +elsif($command eq 'sign-server') {&cmd_sign_server;} +else +{ + die "Unknown command $command" +} + +sub cmd_init +{ + # create directories + unless(mkdir($cert_dir,0700) + && mkdir($cert_dir.'/roots',0700) + && mkdir($cert_dir.'/keys',0700) + && mkdir($cert_dir.'/servers',0700) + && mkdir($cert_dir.'/clients',0700)) + { + die "Failed to create directory structure" + } + + # create root keys and certrs + cmd_init_create_root('client'); + cmd_init_create_root('server'); +} + +sub cmd_init_create_root +{ + my $entity = $_[0]; + + my $cert = "$cert_dir/roots/".$entity.'CA.pem'; + my $serial = "$cert_dir/roots/".$entity.'CA.srl'; + my $key = "$cert_dir/keys/".$entity.'RootKey.pem'; + my $csr = "$cert_dir/keys/".$entity.'RootCSR.pem'; + + # generate key + if(system("openssl genrsa -out $key 2048") != 0) + { + die "Couldn't generate private key." + } + + # make CSR + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $key -sha1 -out $csr"); + print CSR <<__E; +. +. +. +. +. +Backup system $entity root +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $csr; + + # sign it to make a self-signed root CA key + if(system("openssl x509 -req -in $csr -sha1 -extensions v3_ca -signkey $key -out $cert -days $root_sign_period") != 0) + { + die "Couldn't generate root certificate." + } + + # write the initial serial number + open SERIAL,">$serial" or die "Can't open $serial for writing"; + print SERIAL "00\n"; + close SERIAL; +} + +sub cmd_sign +{ + my $csr = $args[0]; + + if(!-f $csr) + { + die "$csr does not exist"; + } + + # get the common name specified in this certificate + my $common_name = get_csr_common_name($csr); + + # look OK? + unless($common_name =~ m/\ABACKUP-([A-Fa-f0-9]+)\Z/) + { + die "The certificate presented does not appear to be a backup client certificate" + } + + my $acc = $1; + + # check against filename + if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) + { + die "Certificate request filename does not match name in certificate ($common_name)" + } + + print <<__E; + +This certificate is for backup account + + $acc + +Ensure this matches the account number you are expecting. The filename is + + $csr + +which should include this account number, and additionally, you should check +that you received it from the right person. + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +__E + + return unless get_confirmation(); + + # out certificate + my $out_cert = "$cert_dir/clients/$acc"."-cert.pem"; + + # sign it! + if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/clientCA.pem -CAkey $cert_dir/keys/clientRootKey.pem -out $out_cert -days $sign_period") != 0) + { + die "Signing failed" + } + + # tell user what to do next + print <<__E; + + +Certificate signed. + +Send the files + + $out_cert + $cert_dir/roots/serverCA.pem + +to the client. + +__E +} + +sub cmd_sign_server +{ + my $csr = $args[0]; + + if(!-f $csr) + { + die "$csr does not exist"; + } + + # get the common name specified in this certificate + my $common_name = get_csr_common_name($csr); + + # look OK? + if($common_name !~ m/\A[-a-zA-Z0-9.]+\Z/) + { + die "Invalid server name" + } + + print <<__E; + +This certificate is for backup server + + $common_name + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +__E + + return unless get_confirmation(); + + # out certificate + my $out_cert = "$cert_dir/servers/$common_name"."-cert.pem"; + + # sign it! + if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/serverCA.pem -CAkey $cert_dir/keys/serverRootKey.pem -out $out_cert -days $sign_period") != 0) + { + die "Signing failed" + } + + # tell user what to do next + print <<__E; + + +Certificate signed. + +Install the files + + $out_cert + $cert_dir/roots/clientCA.pem + +on the server. + +__E +} + + +sub get_csr_common_name +{ + my $csr = $_[0]; + + open CSRTEXT,"openssl req -text -in $csr |" or die "Can't open openssl for reading"; + + my $subject; + while() + { + $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ + } + close CSRTEXT; + + if($subject eq '') + { + die "No subject found in CSR $csr" + } + + return $subject +} + +sub get_confirmation() +{ + my $line = ; + chomp $line; + if(lc $line ne 'yes') + { + print "CANCELLED\n"; + return 0; + } + + return 1; +} + + + + + diff --git a/bin/bbstored/bbstored-config.in b/bin/bbstored/bbstored-config.in new file mode 100755 index 00000000..83305c4f --- /dev/null +++ b/bin/bbstored/bbstored-config.in @@ -0,0 +1,245 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +# check and get command line parameters +if($#ARGV < 2) +{ + print <<__E; + +Setup bbstored config utility. + +Bad command line parameters. +Usage: + bbstored-config config-dir server-hostname username [raidfile-config] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + server-hostname is the hostname that clients will use to connect to + this server + username is the user to run the server under + raidfile-config is optional. Use if you have a non-standard + raidfile.conf file. + +__E + exit(1); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# default locations +my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbstored.conf'; + +# command line parameters +my ($config_dir,$server,$username,$raidfile_config) = @ARGV; + +$raidfile_config = $config_dir . '/raidfile.conf' unless $raidfile_config ne ''; + +# check server exists, but don't bother checking that it's actually this machine. +{ + my @r = gethostbyname($server); + if($#r < 0) + { + die "Server '$server' not found. (check server name, test DNS lookup failed.)" + } +} + +# check this exists +if(!-f $raidfile_config) +{ + print "The RaidFile configuration file $raidfile_config doesn't exist.\nYou may need to create it with raidfile-config.\nWon't configure bbstored without it.\n"; + exit(1); +} + +# check that the user exists +die "You shouldn't run bbstored as root" if $username eq 'root'; +my $user_uid = 0; +(undef,undef,$user_uid) = getpwnam($username); +if($user_uid == 0) +{ + die "User $username doesn't exist\n"; +} + +# check that directories are writeable +open RAIDCONF,$raidfile_config or die "Can't open $raidfile_config"; +{ + my %done = (); + while() + { + next unless m/Dir\d\s*=\s*(.+)/; + my $d = $1; + $d = $d.'/backup' if -e $d.'/backup'; + print "Checking permissions on $d\n"; + my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($d); + my $req_perms = ($uid == $user_uid)?0700:0007; + if(($mode & $req_perms) != $req_perms) + { + print "$username doesn't appear to have the necessary permissions on $d\n"; + print "Either adjust permissions, or create a directory 'backup' inside the\n"; + print "directory specified in raidfile.conf which is writable.\n"; + exit(1); + } + } +} +close RAIDCONF; + +# ssl stuff +my $private_key = "$config_dir/bbstored/$server-key.pem"; +my $certificate_request = "$config_dir/bbstored/$server-csr.pem"; +my $certificate = "$config_dir/bbstored/$server-cert.pem"; +my $ca_root_cert = "$config_dir/bbstored/clientCA.pem"; + +# other files +my $config_file = "$config_dir/bbstored.conf"; +my $accounts_file = "$config_dir/bbstored/accounts.txt"; + +# summarise configuration + +print <<__E; + +Setup bbstored config utility. + +Configuration: + Writing configuration file: $config_file + Writing empty accounts file: $accounts_file + Server hostname: $server + RaidFile config: $raidfile_config + +__E + +# create directories +if(!-d $config_dir) +{ + print "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +if(!-d "$config_dir/bbstored") +{ + print "Creating $config_dir/bbstored\n"; + mkdir "$config_dir/bbstored",0755 or die "Can't create $config_dir/bbstored"; +} + +# create blank accounts file +if(!-f $accounts_file) +{ + print "Creating blank accounts file\n"; + open ACC,">$accounts_file"; + close ACC; +} + +# generate the private key for the server +if(!-f $private_key) +{ + print "Generating private key...\n"; + if(system("openssl genrsa -out $private_key 2048") != 0) + { + die "Couldn't generate private key." + } +} + +# generate a certificate request +if(!-f $certificate_request) +{ + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); + print CSR <<__E; +. +. +. +. +. +$server +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $certificate_request +} + +# write the configuration file +print "Writing configuration file $config_file\n"; +open CONFIG,">$config_file" or die "Can't open config file for writing"; +print CONFIG <<__E; + +RaidFileConf = $raidfile_config +AccountDatabase = $accounts_file + +# Uncomment this line to see exactly what commands are being received from clients. +# ExtendedLogging = yes + +# scan all accounts for files which need deleting every 15 minutes. + +TimeBetweenHousekeeping = 900 + +Server +{ + PidFile = @localstatedir_expanded@/run/bbstored.pid + User = $username + ListenAddresses = inet:$server + CertificateFile = $certificate + PrivateKeyFile = $private_key + TrustedCAsFile = $ca_root_cert +} + + +__E + +close CONFIG; + +# explain to the user what they need to do next +my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; + +print <<__E; + +=================================================================== + +bbstored basic configuration complete. + +What you need to do now... + +1) Sign $certificate_request + using the bbstored-certs utility. + +2) Install the server certificate and root CA certificate as + $certificate + $ca_root_cert + +3) You may wish to read the configuration file + $config_file + and adjust as appropraite. + +4) Create accounts with bbstoreaccounts + +5) Start the backup store daemon with the command + @sbindir_expanded@/bbstored$daemon_args + in /etc/rc.local, or your local equivalent. + +=================================================================== + +__E + + + diff --git a/bin/bbstored/bbstored.cpp b/bin/bbstored/bbstored.cpp new file mode 100644 index 00000000..21a9e5f1 --- /dev/null +++ b/bin/bbstored/bbstored.cpp @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbstored.cpp +// Purpose: main file for backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupStoreDaemon.h" +#include "MainHelper.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + Logging::SetProgramName("bbstored"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + + 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 + + MAINHELPER_END +} + diff --git a/bin/s3simulator/s3simulator.cpp b/bin/s3simulator/s3simulator.cpp new file mode 100644 index 00000000..9a10635c --- /dev/null +++ b/bin/s3simulator/s3simulator.cpp @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: s3simulator.cpp +// Purpose: main file for S3 simulator daemon +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "S3Simulator.h" +#include "MainHelper.h" + +#include "MemLeakFindOn.h" + +int main(int argc, const char *argv[]) +{ + int ExitCode = 0; + + MAINHELPER_START + + Logging::SetProgramName("s3simulator"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + + S3Simulator daemon; + ExitCode = daemon.Main("s3simulator.conf", argc, argv); + + MAINHELPER_END + + return ExitCode; +} diff --git a/bootstrap b/bootstrap new file mode 100755 index 00000000..14fc19e3 --- /dev/null +++ b/bootstrap @@ -0,0 +1,5 @@ +#!/bin/sh + +aclocal -I infrastructure/m4 +autoheader +autoconf diff --git a/cleanupforcvs.pl b/cleanupforcvs.pl new file mode 100755 index 00000000..3379a7ad --- /dev/null +++ b/cleanupforcvs.pl @@ -0,0 +1,196 @@ +#!/usr/bin/perl +use strict; + +my @del_macos_files; +my @bad_cpp; +my @test_main; +my @makefiles; +my @autogen_cpp; +my $cleaned = 1; +my $dist_archives_exist = 0; +my @bad_h; + +open EVERYTHING,'find . -type d \( -name docs \) -prune -o -type f |' or die "Can't open find for file listing"; + +my %exclude_from_memtest_checks = ('PollEmulator.cpp'=>1,'DebugMemLeakFinder.cpp'=>1,'MemLeakFinder.h'=>1,'MemLeakFindOn.h'=>1,'MemLeakFindOff.h'=>1,'Box.h'=>1); + +while() +{ + chomp; + next if -d; + if(m~/autogen_\w+\.(h|cpp)~) + { + push @autogen_cpp,$_ + } + if(m~/\._[^/]+\Z~ || m~/\.DS_Store\Z~) + { + # mac OS files we don't want + push @del_macos_files,$_ + } + elsif(m/\/(\w+\.cpp)/) + { + my $leafname = $1; + # check that Box.h is first include + open CPP,$_ or die "Can't open $_ for reading"; + + my $box_found = 0; + my $last_was_memteston = 0; + my $ok = 1; + + while(my $l = ) + { + if($l =~ m/#include\s+["<](.+?)[">]/) + { + my $inc_name = $1; + if($inc_name eq 'Box.h') + { + $box_found = 1; + } + else + { + # Box.h must be first include file in every cpp file + $ok = 0 unless $box_found; + } + # is it the mem test on thing? (ignoring the wire packing .h files) + if($inc_name ne 'BeginStructPackForWire.h' && $inc_name ne 'EndStructPackForWire.h') + { + $last_was_memteston = ($inc_name eq 'MemLeakFindOn.h'); + } + } + } + if(!exists $exclude_from_memtest_checks{$leafname}) + { + $ok = 0 unless $last_was_memteston; + } + push @bad_cpp,$_ unless $ok; + + close CPP; + } + elsif(m/\/(\w+\.h)/) + { + my $leafname = $1; + + open H,$_ or die "Can't open $_ for reading"; + + my $ok = 1; + my $memteston = 0; + + while(my $l = ) + { + if($l =~ m/#include\s+["<](.+?)[">]/) + { + if($1 eq 'MemLeakFindOn.h') + { + $memteston = 1; + } + elsif($1 eq 'MemLeakFindOff.h') + { + $memteston = 0; + } + else + { + # don't allow #include within mem test on + $ok = 0 unless !$memteston; + } + } + else + { + # strip comments + my $lsc = $l; + $lsc =~ s~//.+$~~; + if($lsc =~ m/\b(new|delete|malloc|free|realloc)\b/) + { + # only allow this if memory checking is ON + $ok = 0 unless $memteston; + } + } + } + # mem test must be off at the end of this .h file + $ok = 0 if $memteston; + + if($_ !~ /testfiles/ && !exists $exclude_from_memtest_checks{$leafname}) + { + push @bad_h,$_ unless $ok; + } + close H; + } + elsif(m~/Makefile\Z~) + { + push @makefiles,$_ + } + + if(m~/_(main\.cpp|t|t-gdb)\Z~) + { + push @test_main,$_ + } + + if(m~\./boxbackup~) + { + $dist_archives_exist = 1; + } +} + +close EVERYTHING; + +ask_about_delete(\@del_macos_files, "supurious MacOS X files"); +ask_about_delete(\@makefiles, "automatically generated Makefiles"); +ask_about_delete(\@test_main, "automatically generated test files"); +ask_about_delete(\@autogen_cpp, "automatically generated source files"); + +if($#bad_cpp >= 0) +{ + print "\n"; + print $_,"\n" for @bad_cpp; + print "There are some .cpp file where Box.h is not the first included file or MemLeakFindOn.h is not the last .h file included\n"; + $cleaned = 0; +} +if($#bad_h >= 0) +{ + print "\n"; + print $_,"\n" for @bad_h; + print "There are some .h files which use memory functions without memory leak finding on, or leave memory leak finding on at end\n"; + $cleaned = 0; +} + +if(-d 'debug') {print "debug directory exists\n"; $cleaned = 0;} +if(-d 'release') {print "release directory exists\n"; $cleaned = 0;} +if(-d 'parcels') {print "parcels directory exists\n"; $cleaned = 0;} +if($dist_archives_exist) {print "boxbackup* files/dirs exist\n"; $cleaned = 0;} + +if(!$cleaned) +{ + print <<__E; + +======================================================== + NOT CLEANED! +======================================================== +__E +} + + +sub ask_about_delete +{ + my ($del_r, $name) = @_; + return if $#$del_r < 0; + + print "\n"; + for(@$del_r) + { + print $_,"\n"; + } + print "Delete these ",$#$del_r + 1, " $name? "; + my $in = ; + chomp $in; + if($in eq 'yes') + { + print "Deleting...\n"; + unlink $_ for @$del_r + } + else + { + $cleaned = 0; + } +} + + + diff --git a/config.guess b/config.guess new file mode 100755 index 00000000..0773d0f6 --- /dev/null +++ b/config.guess @@ -0,0 +1,1456 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003 Free Software Foundation, Inc. + +timestamp='2004-03-03' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit 0 ;; + amd64:OpenBSD:*:*) + echo x86_64-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + amiga:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + arc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + cats:OpenBSD:*:*) + echo arm-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + hp300:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mac68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + macppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme68k:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvme88k:OpenBSD:*:*) + echo m88k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + mvmeppc:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pegasos:OpenBSD:*:*) + echo powerpc-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + pmax:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sgi:OpenBSD:*:*) + echo mipseb-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + sun3:OpenBSD:*:*) + echo m68k-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + wgrisc:OpenBSD:*:*) + echo mipsel-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:OpenBSD:*:*) + echo ${UNAME_MACHINE}-unknown-openbsd${UNAME_RELEASE} + exit 0 ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit 0 ;; + macppc:MirBSD:*:*) + echo powerppc-unknown-mirbsd${UNAME_RELEASE} + exit 0 ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit 0 ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit 0 ;; + Alpha*:OpenVMS:*:*) + echo alpha-hp-vms + exit 0 ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit 0 ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit 0 ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit 0;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit 0 ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit 0 ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit 0 ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit 0 ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit 0;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit 0;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit 0 ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit 0 ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit 0 ;; + DRS?6000:UNIX_SV:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7 && exit 0 ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit 0 ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit 0 ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit 0 ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit 0 ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit 0 ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit 0 ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit 0 ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit 0 ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit 0 ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit 0 ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit 0 ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit 0 ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit 0 ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c \ + && $dummy `echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` \ + && exit 0 + echo mips-mips-riscos${UNAME_RELEASE} + exit 0 ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit 0 ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit 0 ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit 0 ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit 0 ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit 0 ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit 0 ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit 0 ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit 0 ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit 0 ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit 0 ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit 0 ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit 0 ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit 0 ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit 0 ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo rs6000-ibm-aix3.2.5 + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit 0 ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit 0 ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit 0 ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit 0 ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit 0 ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit 0 ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit 0 ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit 0 ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit 0 ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + # avoid double evaluation of $set_cc_for_build + test -n "$CC_FOR_BUILD" || eval $set_cc_for_build + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E -) | grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit 0 ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit 0 ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && $dummy && exit 0 + echo unknown-hitachi-hiuxwe2 + exit 0 ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit 0 ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit 0 ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit 0 ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit 0 ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit 0 ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit 0 ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit 0 ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit 0 ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit 0 ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit 0 ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit 0 ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + *:UNICOS/mp:*:*) + echo nv1-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit 0 ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit 0 ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit 0 ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit 0 ;; + *:FreeBSD:*:*) + # Determine whether the default compiler uses glibc. + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #if __GLIBC__ >= 2 + LIBC=gnu + #else + LIBC= + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + # GNU/KFreeBSD systems have a "k" prefix to indicate we are using + # FreeBSD's kernel, but not the complete OS. + case ${LIBC} in gnu) kernel_only='k' ;; esac + echo ${UNAME_MACHINE}-unknown-${kernel_only}freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`${LIBC:+-$LIBC} + exit 0 ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit 0 ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit 0 ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit 0 ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit 0 ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit 0 ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit 0 ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit 0 ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit 0 ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit 0 ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit 0 ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit 0 ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit 0 ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit 0 ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && echo "${CPU}-unknown-linux-gnu" && exit 0 + ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit 0 ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit 0 ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit 0 ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit 0 ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit 0 ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit 0 ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit 0 ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit 0 ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit 0 ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit 0 ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit 0 ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && echo "${UNAME_MACHINE}-pc-linux-${LIBC}" && exit 0 + test x"${TENTATIVE}" != x && echo "${TENTATIVE}" && exit 0 + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit 0 ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit 0 ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit 0 ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit 0 ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit 0 ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit 0 ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit 0 ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit 0 ;; + i*86:*:5:[78]*) + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit 0 ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit 0 ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit 0 ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit 0 ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit 0 ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit 0 ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit 0 ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit 0 ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit 0 ;; + M68*:*:R3V[567]*:*) + test -r /sysV68 && echo 'm68k-motorola-sysv' && exit 0 ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4.3${OS_REL} && exit 0 + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && echo i586-ncr-sysv4.3${OS_REL} && exit 0 ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && echo i486-ncr-sysv4 && exit 0 ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit 0 ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit 0 ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit 0 ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit 0 ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit 0 ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit 0 ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit 0 ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit 0 ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit 0 ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit 0 ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit 0 ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit 0 ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit 0 ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit 0 ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit 0 ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit 0 ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit 0 ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit 0 ;; + *:Darwin:*:*) + case `uname -p` in + *86) UNAME_PROCESSOR=i686 ;; + powerpc) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit 0 ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit 0 ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit 0 ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit 0 ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit 0 ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit 0 ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit 0 ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit 0 ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit 0 ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit 0 ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit 0 ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit 0 ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit 0 ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit 0 ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit 0 ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit 0 ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && $dummy && exit 0 + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit 0; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit 0 ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit 0 ;; + c34*) + echo c34-convex-bsd + exit 0 ;; + c38*) + echo c38-convex-bsd + exit 0 ;; + c4*) + echo c4-convex-bsd + exit 0 ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +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` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/config.sub b/config.sub new file mode 100755 index 00000000..264f820a --- /dev/null +++ b/config.sub @@ -0,0 +1,1549 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003 Free Software Foundation, Inc. + +timestamp='2004-02-23' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit 0 ;; + --version | -v ) + echo "$version" ; exit 0 ;; + --help | --h* | -h ) + echo "$usage"; exit 0 ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit 0;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ + kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | m32r | m68000 | m68k | m88k | mcore \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | msp430 \ + | ns16k | ns32k \ + | openrisc | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | sh | sh[1234] | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc86x | sparclet | sparclite | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xscale | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* \ + | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | m32r-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | msp430-* \ + | none-* | np1-* | nv1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[23]e-* | sh[34]eb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc86x-* | sparclet-* | sparclite-* \ + | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xps100-* | xscale-* | xstormy16-* \ + | xtensa-* \ + | ymp-* \ + | z8k-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + cr16c) + basic_machine=cr16c-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + mmix*) + basic_machine=mmix-knuth + os=-mmixware + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nv1) + basic_machine=nv1-cray + os=-unicosmp + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + or32 | or32-*) + basic_machine=or32-unknown + os=-coff + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh3 | sh4 | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparc | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-ibm) + os=-aix + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit 0 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..48fb510f --- /dev/null +++ b/configure.ac @@ -0,0 +1,420 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT([Box Backup], 0.11, [boxbackup@boxbackup.org]) +AC_CONFIG_SRCDIR([lib/common/Box.h]) +AC_CONFIG_HEADERS([lib/common/BoxConfig.h]) + +touch install-sh +AC_CANONICAL_SYSTEM +test -s install-sh || rm install-sh + +### Checks for programs. + +AC_LANG([C++]) +AC_PROG_CXX +AC_CXX_EXCEPTIONS +AC_CXX_NAMESPACES +if test "x$ac_cv_cxx_exceptions" != "xyes" || \ + test "x$ac_cv_cxx_namespaces" != "xyes"; then + 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 + #include + ]]) +AC_CHECK_MEMBERS([DIR.d_fd],,, [[#include ]]) +AC_CHECK_MEMBERS([DIR.dd_fd],,, [[#include ]]) + +AC_CHECK_DECLS([INFTIM],,, [[#include ]]) +AC_CHECK_DECLS([SO_PEERCRED],,, [[#include ]]) +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 ]]) +AC_CHECK_DECLS([dirfd],,, + [[ + #include + #include + ]]) + +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 ]]) +;; +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 ]]) + + +### 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 ]]) +AC_CHECK_DECLS([F_SETLK],,, [[#include ]]) + +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 + +## Get tmpdir +temp_directory_name="/tmp" +AC_ARG_WITH( + [tmp-dir], + [AC_HELP_STRING([--with-tmp-dir=DIR], [Directory for temporary files [/tmp]])], + [temp_directory_name="$withval"]) +AC_DEFINE_UNQUOTED([TEMP_DIRECTORY_NAME], ["$temp_directory_name"], [TMP directory name]) + +## Allow linking binaries with static libraries +AC_ARG_ENABLE( + [static-bin], + [AC_HELP_STRING([--enable-static-bin], [Link binaries with static libraries])]) +if test "x$enable_static_bin" = "xyes"; then + AC_CHECK_LIB([ssl],[SSL_read],,, [crypto]) + LIBS="-Wl,-Bstatic $LIBS -Wl,-Bdynamic" +fi + +# override default sysconfdir, for backwards compatibility +test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc +test "$localstatedir" = '${prefix}/var' && localstatedir=/var + +## Kludge to allow makeparcels.pl to use bindir. This is not a good long term +## solution because it prevents use of "make exec_prefix=/some/dir" +saved_prefix=$prefix +saved_exec_prefix=$exec_prefix +test "x$prefix" = xNONE && prefix=$ac_default_prefix +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' +eval bindir_expanded=` eval "echo $bindir"` +eval sbindir_expanded=` eval "echo $sbindir"` +eval sysconfdir_expanded=` eval "echo $sysconfdir"` +eval localstatedir_expanded=`eval "echo $localstatedir"` +prefix=$saved_prefix +exec_prefix=$saved_exec_prefix +AC_SUBST([bindir_expanded]) +AC_SUBST([sbindir_expanded]) +AC_SUBST([sysconfdir_expanded]) +AC_SUBST([localstatedir_expanded]) + +## Figure out the client parcel directory and substitute it +build_dir=`dirname $0` +build_dir=`cd $build_dir && pwd` +client_parcel_dir=`$PERL infrastructure/parcelpath.pl backup-client $target_os` + +if test "$build_os" = "cygwin"; then + client_parcel_dir=`cygpath -wa $client_parcel_dir | sed -e 's|\\\|/|g'` + build_dir=` cygpath -wa $build_dir | sed -e 's|\\\|/|g'` +fi + +AC_SUBST([client_parcel_dir]) +AC_SUBST([build_dir]) + +## Figure out version and substitute it in +box_version=`$PERL infrastructure/printversion.pl` +AC_SUBST([box_version]) + +### Output files +AC_CONFIG_FILES([infrastructure/BoxPlatform.pm + contrib/mac_osx/org.boxbackup.bbackupd.plist + contrib/mac_osx/org.boxbackup.bbstored.plist + contrib/solaris/bbackupd-manifest.xml + contrib/solaris/bbstored-manifest.xml + lib/common/BoxPortsAndFiles.h + test/bbackupd/testfiles/bbackupd.conf + test/bbackupd/testfiles/bbackupd-exclude.conf + test/bbackupd/testfiles/bbackupd-snapshot.conf + test/bbackupd/testfiles/bbackupd-symlink.conf + ]) +AX_CONFIG_SCRIPTS([bin/bbackupd/bbackupd-config + bin/bbackupquery/makedocumentation.pl + bin/bbstored/bbstored-certs + bin/bbstored/bbstored-config + contrib/debian/bbackupd + contrib/debian/bbstored + contrib/redhat/bbackupd + contrib/redhat/bbstored + contrib/suse/bbackupd + contrib/suse/bbstored + contrib/solaris/bbackupd-smf-method + contrib/solaris/bbstored-smf-method + contrib/windows/installer/boxbackup.mpi + infrastructure/makebuildenv.pl + infrastructure/makeparcels.pl + infrastructure/makedistribution.pl + lib/common/makeexception.pl + lib/raidfile/raidfile-config + lib/server/makeprotocol.pl + runtest.pl + test/backupstorefix/testfiles/testbackupstorefix.pl + test/bbackupd/testfiles/extcheck1.pl + test/bbackupd/testfiles/extcheck2.pl + test/bbackupd/testfiles/notifyscript.pl + test/bbackupd/testfiles/syncallowscript.pl]) +# TODO: Need to do contrib/cygwin/install-cygwin-service.pl but location varies +AC_OUTPUT + +# Configure the Box build system +echo +if ! $PERL ./infrastructure/makebuildenv.pl \ +|| ! $PERL ./infrastructure/makeparcels.pl; then + echo "Making infrastructure failed!" + exit 1 +fi + +# Write summary of important info +tee config.log.features <param("download")) +{ + my ($filename, $acct_no); + + if ($download eq "cert") + { + $acct_no = $cgi->param("account"); + $acct_no =~ tr/0-9a-fA-F//cd; + $filename = "$acct_no-cert.pem"; + } + elsif ($download eq "cacert") + { + $filename = "serverCA.pem"; + } + else + { + die "No such download method $download"; + } + + print $cgi->header(-type => "text/plain", + -"content-disposition" => "attachment; filename=$filename"); + + my $send_file; + + if ($download eq "cert") + { + $send_file = "$ca_dir/clients/$filename"; + } + elsif ($download eq "cacert") + { + $send_file = "$ca_dir/roots/serverCA.pem"; + } + + die "File does not exist: $send_file" + unless -f $send_file; + die "File is not readable by user " . getpwuid($UID) . + ": $send_file" unless -r $send_file; + + open SENDFILE, "< $send_file" or die "Failed to open file " . + "$send_file: $!"; + while (my $line = ) + { + print $line; + } + close SENDFILE; + exit 0; +} + +print $cgi->header(), $cgi->start_html(-title=>"Box Backup Certificates", + -style=>'bb.css'); +print $cgi->h1("Box Backup Certificates"); + +check_access($bbstored_conf_file, "BBStoreD configuration file"); + +my $bbstored_conf = Config::Scoped->new(file => $bbstored_conf_file)->parse(); + +$accounts_db_file ||= $bbstored_conf->{'Server'}{'AccountDatabase'}; +die "Missing AccountDatabase in $bbstored_conf_file" unless $accounts_db_file; +check_access($accounts_db_file, "Accounts Database"); + +$raidfile_conf_file ||= $bbstored_conf->{'Server'}{'RaidFileConf'}; +die "Missing RaidFileConf in $bbstored_conf_file" unless $raidfile_conf_file; +check_access($raidfile_conf_file, "RaidFile configuration file"); + +my $accounts_db = BoxBackup::Config::Accounts->new($accounts_db_file); + +check_executable($bbstoreaccounts, "bbstoreaccounts program"); + +sub error($) +{ + my ($message) = @_; + unless ($message =~ /^p($message); + } + print $cgi->div({-class=>"error"}, $message); + return 0; +} + +sub url +{ + my $cgi = shift @_; + my %params = @_; + my $uri = URI->new($cgi->url(-absolute=>1)); + foreach my $param (keys %params) + { + $uri->query_param($param, $params{$param}); + } + return $uri; +} + +sub create_account($) +{ + my ($cgi) = @_; + + my $upload = $cgi->upload('req'); + unless ($upload) + { + return error("Please attach a certificate request file."); + } + + my $tempfile = File::Temp->new("bbaccount-certreq-XXXXXX.pem"); + my $csr_data = ""; + + while (my $line = <$upload>) + { + print $tempfile $line; + $csr_data .= $line; + } + + my @accounts = $accounts_db->getAccountIDs(); + my $new_acc_no = $cgi->param('account'); + if (not $new_acc_no) + { + return error("Please enter an account number."); + } + + foreach my $account_no (@accounts) + { + if ($account_no == $new_acc_no) + { + return error("The account number $new_acc_no " . + "already exists, please use one which " . + "does not."); + } + } + + my $req = Convert::X509::Request->new($csr_data); + my $cn; + foreach my $part ($req->subject) + { + if ($part =~ /^cn=(.*)/i) + { + $cn = $1; + last; + } + } + + unless ($cn) + { + return error("The certificate request does not include a " . + "common name, which should be BACKUP-$new_acc_no."); + } + + unless ($cn eq "BACKUP-$new_acc_no") + { + return error("The certificate request includes the wrong " . + "common name. Expected " . + "BACKUP-$new_acc_no but found " . + "$cn."); + } + + my $out_cert_dir = "$ca_dir/clients"; + unless (-w $out_cert_dir) + { + return error("Cannot write to certificate directory " . + "$out_cert_dir as user " . + "" . getpwuid($UID) . "."); + } + + my $out_cert = "$out_cert_dir/$new_acc_no-cert.pem"; + if (-f $out_cert and not -w $out_cert) + { + return error("The certificate file $out_cert " . + "exists and is not writable as user " . + "$out_cert_dir as user " . + "" . getpwuid($UID) . "."); + } + + my $client_ca_cert_file = "$ca_dir/roots/clientCA.pem"; + unless (-r $client_ca_cert_file) + { + return error("The client CA certificate file " . + "$client_ca_cert_file " . + "is not readable by user " . + "" . getpwuid($UID) . "."); + } + + my $client_ca_key_file = "$ca_dir/keys/clientRootKey.pem"; + unless (-r $client_ca_key_file) + { + return error("The client CA key file " . + "$client_ca_key_file " . + "is not readable by user " . + "" . getpwuid($UID) . "."); + } + + my $serial_file = "$ca_dir/roots/clientCA.srl"; + unless (-w $serial_file) + { + return error("The certificate serial number file " . + "$serial_file " . + "is not writable by user " . + "" . getpwuid($UID) . "."); + } + + my $outputfile = File::Temp->new("bbaccounts-openssl-output-XXXXXX"); + + if (system("openssl x509 -req -in $tempfile -sha1 " . + "-extensions usr_crt " . + "-CA $client_ca_cert_file " . + "-CAkey $client_ca_key_file " . + "-out $out_cert -days $sign_period " . + ">$outputfile 2>&1") != 0) + { + open ERR, "< $outputfile" or die "$outputfile: $!"; + my $errors = join("", ); + close ERR; + return error($cgi->p("Failed to sign certificate:") . + $cgi->pre($errors)); + } + + my $cert_uri = url($cgi, download => "cert", account => $new_acc_no); + my $ca_uri = url($cgi, download => "cacert"); + + print $cgi->div({-class=>"success"}, + $cgi->p("Account created. Please download the following " . + "files:") . + $cgi->ul( + $cgi->li($cgi->a({href=>$cert_uri}, + "Client Certificate")), + $cgi->li($cgi->a({href=>$ca_uri}, + "CA Certificate")) + ) + ); + + return 1; +} + +if ($cgi->param("create")) +{ + print $cgi->h2("Account Creation"); + create_account($cgi); +} + +print $cgi->h2("Accounts"); +print $cgi->start_table({-border=>0, -class=>"numbers"}); + +print $cgi->Tr( + $cgi->th("Account"), + $cgi->th('Used'), $cgi->th('%'), + $cgi->th('Old files'), $cgi->th('%'), + $cgi->th('Deleted files'), $cgi->th('%'), + $cgi->th('Directories'), $cgi->th('%'), + $cgi->th('Soft limit'), $cgi->th('%'), + $cgi->th('Hard limit'), + $cgi->th('Actions') + ); + +sub human_format($) +{ + my ($kb) = @_; + die "bad format in value: expected number followed by kB, " . + "found '$kb'" unless $kb =~ /^(\d+) (kB)$/; + + my $value = $1; + my $units = $2; + + if ($value > 1024) + { + $value /= 1024; + $units = "MB"; + } + + if ($value > 1024) + { + $value /= 1024; + $units = "GB"; + } + + $value = sprintf("%.1f", $value); + return "$value $units"; +} + +sub bbstoreaccounts_format($) +{ + my ($kb) = @_; + die unless $kb =~ /^(\d+) (kB)$/; + + my $value = $1; + my $units = "K"; + + unless ($value % 1024) + { + $value /= 1024; + $units = "M"; + } + + unless ($value % 1024) + { + $value /= 1024; + $units = "G"; + } + + return "$value$units"; +} + +sub get_account_info($) +{ + my ($account) = @_; + + open BBSA, "$bbstoreaccounts -c $bbstored_conf_file -m info $account |" + or die "Failed to get account info for $account: $!"; + + my $account_info = {}; + + while (my $line = ) + { + unless ($line =~ m/([^:]*): (.*)/) + { + die "Bad format in bbstoreaccounts info output " . + "for account $account: '$line'"; + } + + my $name = $1; + my $value = $2; + + if ($value =~ /(.*), (.*)/) + { + $account_info->{$name} = [$1, $2]; + } + else + { + $account_info->{$name} = $value; + } + } + + return $account_info; +} + +sub format_account_info($) +{ + my ($values) = @_; + my $kb = $values->[0]; + my $pc = $values->[1]; + return $cgi->td(human_format($kb)), $cgi->td($values->[1]); +} + +my %account_numbers; + +my @accounts = $accounts_db->getAccountIDs(); +foreach my $i (@accounts) +{ + die "Duplicate account number $i" if $account_numbers{hex($i)}; + $account_numbers{hex($i)} = 1; + + # Find out what account is on what diskset. + my $disk = $accounts_db->getDisk($i); + + # store limits + my $account_info = get_account_info($i); + + print $cgi->Tr( + $cgi->td($i), + format_account_info($account_info->{'Used'}), + format_account_info($account_info->{'Old files'}), + format_account_info($account_info->{'Deleted files'}), + format_account_info($account_info->{'Directories'}), + format_account_info($account_info->{'Soft limit'}), + $cgi->td(human_format($account_info->{'Hard limit'}[0])), + $cgi->td($cgi->a({-href=>url($cgi, account=>$i)}, + "Edit")) + ); +} + +print $cgi->end_table(); + +my $account_no = $cgi->param("account"); +$account_no =~ tr/0-9a-fA-F//cd; + +if (not $cgi->param("showcreate")) +{ + print $cgi->start_form, + $cgi->submit(-name=>"showcreate", + -value=>"Create Account"), + $cgi->end_form(); +} + +if ($account_no) +{ + print $cgi->h2("Edit Account"); + my $account_info = get_account_info($account_no); + $cgi->param("account", $account_no); + $cgi->param("soft_limit", + bbstoreaccounts_format($account_info->{'Soft limit'}[0])); + $cgi->param("hard_limit", + bbstoreaccounts_format($account_info->{'Hard limit'}[0])); +} +elsif ($cgi->param("showcreate")) +{ + print $cgi->h2("Create Account"); +} + +if ($account_no or $cgi->param("showcreate")) +{ + my $new_account_no = 1; + while ($account_numbers{$new_account_no}) + { + $new_account_no++; + } + + my $disksets_conf = BoxBackup::Config::DiskSets->new($raidfile_conf_file); + my @disk_names = $disksets_conf->getListofDisks(); + my @disk_numbers; + my %disk_labels; + + foreach my $name (@disk_names) + { + my $num = $disksets_conf->getParamVal($name, "SetNumber"); + push @disk_numbers, $num; + $disk_labels{$num} = $name; + } + + print $cgi->start_multipart_form(), + $cgi->start_table(); + + if ($account_no) + { + print $cgi->Tr( + $cgi->th("Account Number"), + $cgi->td($account_no . + $cgi->hidden("account", $account_no)) + ); + } + else + { + print $cgi->Tr( + $cgi->th("Account Number"), + $account_no ? $account_no : + $cgi->td($cgi->textfield(-name => "account", + -default => sprintf("%x", $new_account_no))), + ); + } + + if (not $account_no) + { + print $cgi->Tr( + $cgi->th("Disk Set"), + $cgi->td($cgi->popup_menu(-name => "disk_set", + -values => \@disk_numbers, + -labels => \%disk_labels)) + ); + } + + print $cgi->Tr( + $cgi->th("Soft Limit"), + $cgi->td($cgi->textfield(-name => "soft_limit", + -default => "10G")) + ), + $cgi->Tr( + $cgi->th("Hard Limit"), + $cgi->td($cgi->textfield(-name => "hard_limit", + -default => "20G")) + ), + $cgi->Tr( + $cgi->th("Certificate Request"), + $cgi->td($cgi->filefield({ + -name => "req", + -default => "*.crt" + })) + ); + + if ($account_no) + { + print $cgi->Tr( + $cgi->th(), + $cgi->td($cgi->submit(-name => "update", + -value => "Update Account")) + ); + } + else + { + print $cgi->Tr( + $cgi->th(), + $cgi->td($cgi->submit(-name => "create", + -value => "Create Account")) + ); + } + + print $cgi->end_table(), $cgi->end_form(); +} + +print $cgi->end_html; + +exit 0; diff --git a/contrib/bbadmin/apache.conf b/contrib/bbadmin/apache.conf new file mode 100644 index 00000000..e22668ab --- /dev/null +++ b/contrib/bbadmin/apache.conf @@ -0,0 +1,14 @@ +Alias /bbadmin /var/www/localhost/bbadmin + + + AuthType basic + AuthName "Box Backup Web Management Interface" + AuthUserFile /etc/apache2/bbadmin.cgi.htpasswd + Require valid-user + + Allow from all + + Options ExecCGI + AddHandler cgi-script .cgi + DirectoryIndex accounts.cgi + diff --git a/contrib/bbadmin/bb.css b/contrib/bbadmin/bb.css new file mode 100644 index 00000000..76d48e93 --- /dev/null +++ b/contrib/bbadmin/bb.css @@ -0,0 +1,70 @@ +body +{ + background: #edeef3; +} + +table +{ + border-spacing: 0px; +} + +h1, th +{ + background: #e4e6ed; +} + +h1 +{ + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid #fff; +} + +td, th +{ + border-top: 1px solid #fff; + border-bottom: 1px solid #c4c4d5; + margin: 0px; + padding: 0.2em 0.5em; +} + +th +{ + text-align: left; +} + +table.numbers td +{ + text-align: right; +} + +div.error, div.success +{ + margin: 1em; +} + +div.error>*, div.success>* +{ + margin: 0.5em; +} + +div.error +{ + background: #fdd; + border: 2px solid #c00; +} + +div.success +{ + background: #dfd; + border: 2px solid #0c0; +} + +h2, table, p +{ + margin: 0.5em; +} + +form +{ + margin: 0.5em 0; +} diff --git a/contrib/bbreporter/LICENSE b/contrib/bbreporter/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/contrib/bbreporter/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/contrib/bbreporter/bbreporter.py b/contrib/bbreporter/bbreporter.py new file mode 100755 index 00000000..9c8253f1 --- /dev/null +++ b/contrib/bbreporter/bbreporter.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +# BoxBackupReporter - Simple script to report on backups that have been +# performed using BoxBackup. +# +# Copyright: (C) 2007 Three A IT Limited +# Author: Kenny Millington +# +# Credit: This script is based on the ideas of BoxReport.pl by Matt Brown of +# Three A IT Support Limited. +# +################################################################################ +# !! Important !! +# To make use of this script you need to run the boxbackup client with the -v +# commandline option and set LogAllFileAccess = yes in your bbackupd.conf file. +# +# Notes on lazy mode: +# If reporting on lazy mode backups you absolutely must ensure that +# logrotate (or similar) rotates the log files at the same rate at +# which you run this reporting script or you will report on the same +# backup sessions on each execution. +# +# Notes on --rotate and log rotation in general: +# The use-case for --rotate that I imagine is that you'll add a line like the +# following into your syslog.conf file:- +# +# local6.* -/var/log/box +# +# Then specifying --rotate to this script will make it rotate the logs +# each time you report on the backup so that you don't risk a backup session +# being spread across two log files (e.g. syslog and syslog.0). +# +# NB: To do this you'll need to prevent logrotate/syslog from rotating your +# /var/log/box file. On Debian based distros you'll need to edit two files. +# +# First: /etc/cron.daily/sysklogd, find the following line and make the +# the required change: +# Change: for LOG in `syslogd-listfiles` +# To: for LOG in `syslogd-listfiles -s box` +# +# Second: /etc/cron.weekly/sysklogd, find the following line and make the +# the required change: +# Change: for LOG in `syslogd-listfiles --weekly` +# To: for LOG in `syslogd-listfiles --weekly -s box` +# +# Alternatively, if suitable just ensure the backups stop before the +# /etc/cron.daily/sysklogd file runs (usually 6:25am) and report on it +# before the files get rotated. (If going for this option I'd just use +# the main syslog file instead of creating a separate log file for box +# backup since you know for a fact the syslog will get rotated daily.) +# +################################################################################ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# If sendmail is not in one of these paths, add the path. +SENDMAIL_PATHS = ["/usr/sbin/", "/usr/bin/", "/bin/" , "/sbin/"] + +# The name of the sendmail binary, you probably won't need to change this. +SENDMAIL_BIN = "sendmail" + +# Number of files to rotate around +ROTATE_COUNT = 7 + +# Import the required libraries +import sys, os, re, getopt, shutil, gzip + +class BoxBackupReporter: + class BoxBackupReporterError(Exception): + pass + + def __init__(self, config_file="/etc/box/bbackupd.conf", + log_file="/var/log/syslog", email_to=None, + email_from="report@boxbackup", rotate=False, + verbose=False, stats=False, sort=False, debug=False): + + # Config options + self.config_file = config_file + self.log_file = log_file + self.email_to = email_to + self.email_from = email_from + self.rotate_log_file = rotate + self.verbose_report = verbose + self.usage_stats = stats + self.sort_files = sort + self.debug = debug + + # Regex's + self.re_automatic_backup = re.compile(" *AutomaticBackup *= *no", re.I) + self.re_syslog = re.compile("(\S+) +(\S+) +([\d:]+) +(\S+) +([^:]+): +"+ + "(?:[A-Z]+:)? *([^:]+): *(.*)") + + # Initialise report + self.reset() + + def _debug(self, msg): + if self.debug: + sys.stderr.write("[bbreporter.py Debug]: %s\n" % msg) + + def reset(self): + # Reset report data to default values + self.hostname = "" + self.patched_files = [] + self.synced_files = [] + self.uploaded_files = [] + self.warnings = [] + self.errors = [] + self.stats = None + self.start_datetime = "Unknown" + self.end_datetime = "Unfinished" + self.report = "No report generated" + + def run(self): + try: + self._determine_operating_mode() + + if self.lazy_mode: + self._debug("Operating in LAZY MODE.") + else: + self._debug("Operating in SNAPSHOT MODE.") + + except IOError: + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Config file \"%s\" could not be read." % self.config_file) + + try: + self._parse_syslog() + except IOError: + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Log file \"%s\" could not be read." % self.log_file) + + self._parse_stats() + self._generate_report() + + def deliver(self): + # If we're not e-mailing the report then just dump it to stdout + # and return. + if self.email_to is None: + print self.report + # Now that we've delivered the report it's time to rotate the logs + # if we're requested to do so. + self._rotate_log() + return + + # Locate the sendmail binary + sendmail = self._locate_sendmail() + if(sendmail is None): + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Could not find sendmail binary - Unable to send e-mail!") + + + # Set the subject based on whether we think we failed or not. + # (suffice it to say I consider getting an error and backing up + # no files a failure or indeed not finding a start time in the logs). + subject = "BoxBackup Reporter (%s) - " % self.hostname + if self.start_datetime == "Unknown" or\ + (len(self.patched_files) == 0 and len(self.synced_files) == 0 and\ + len(self.uploaded_files) == 0): + subject = subject + "FAILED" + else: + subject = subject + "SUCCESS" + + if len(self.errors) > 0: + subject = subject + " (with errors)" + + # Prepare the e-mail message. + mail = [] + mail.append("To: " + self.email_to) + mail.append("From: " + self.email_from) + mail.append("Subject: " + subject) + mail.append("") + mail.append(self.report) + + # Send the mail. + p = os.popen(sendmail + " -t", "w") + p.write("\r\n".join(mail)) + p.close() + + # Now that we've delivered the report it's time to rotate the logs + # if we're requested to do so. + self._rotate_log() + + def _determine_operating_mode(self): + # Scan the config file and determine if we're running in lazy or + # snapshot mode. + cfh = open(self.config_file) + + for line in cfh: + if not line.startswith("#"): + if self.re_automatic_backup.match(line): + self.lazy_mode = False + cfh.close() + return + + self.lazy_mode = True + cfh.close() + + def _parse_syslog(self): + lfh = open(self.log_file) + + patched_files = {} + uploaded_files = {} + synced_files = {} + + for line in lfh: + # Only run the regex if we find a box backup entry. + if line.find("Box Backup") > -1 or line.find("bbackupd") > -1: + raw_data = self.re_syslog.findall(line) + try: + data = raw_data[0] + except IndexError: + # If the regex didn't match it's not a message that we're + # interested in so move to the next line. + continue + + # Set the hostname, it shouldn't change in a log file + self.hostname = data[3] + + # If we find the backup-start event then set the start_datetime. + if data[6].find("backup-start") > -1: + # If we're not in lazy mode or the start_datetime hasn't + # been set then reset the data and set it. + # + # If we're in lazy mode and encounter a second backup-start + # we don't want to change the start_datetime likewise if + # we're not in lazy mode we do want to and we want to reset + # so we only capture the most recent session. + if not self.lazy_mode or self.start_datetime == "Unknown": + self._debug("Reset start dtime with old time: %s." % + self.start_datetime) + + # Reset ourselves + self.reset() + + # Reset our temporary variables which we store + # the files in. + patched_files = {} + uploaded_files = {} + synced_files = {} + + self.start_datetime = data[1]+" "+data[0]+ " "+data[2] + self._debug("Reset start dtime with new time %s." % + self.start_datetime) + + # If we find the backup-finish event then set the end_datetime. + elif data[6].find("backup-finish") > -1: + self.end_datetime = data[1] + " " + data[0] + " " + data[2] + self._debug("Set end dtime: %s" % self.end_datetime) + + # Only log the events if we have our start time. + elif self.start_datetime != "Unknown": + # We found a patch event, add the file to the patched_files. + if data[5] == "Uploading patch to file": + patched_files[data[6]] = "" + + # We found an upload event, add to uploaded files. + elif data[5] == "Uploading complete file": + uploaded_files[data[6]] = "" + + # We found another upload event. + elif data[5] == "Uploaded file": + uploaded_files[data[6]] = "" + + # We found a sync event, add the file to the synced_files. + elif data[5] == "Synchronised file": + synced_files[data[6]] = "" + + # We found a warning, add the warning to the warnings. + elif data[5] == "WARNING": + self.warnings.append(data[6]) + + # We found an error, add the error to the errors. + elif data[5] == "ERROR": + self.errors.append(data[6]) + + + self.patched_files = patched_files.keys() + self.uploaded_files = uploaded_files.keys() + self.synced_files = synced_files.keys() + + # There's no point running the sort functions if we're not going + # to display the resultant lists. + if self.sort_files and self.verbose_report: + self.patched_files.sort() + self.uploaded_files.sort() + + + lfh.close() + + def _parse_stats(self): + if(not self.usage_stats): + return + + # Grab the stats from bbackupquery + sfh = os.popen("bbackupquery usage quit", "r") + raw_stats = sfh.read() + sfh.close() + + # Parse the stats + stats_re = re.compile("commands.[\n ]*\n(.*)\n+", re.S) + stats = stats_re.findall(raw_stats) + + try: + self.stats = stats[0] + except IndexError: + self.stats = "Unable to retrieve usage information." + + def _generate_report(self): + if self.start_datetime == "Unknown": + self.report = "No report data has been found." + return + + total_files = len(self.patched_files) + len(self.uploaded_files) + + report = [] + report.append("--------------------------------------------------") + report.append("Report Title : Box Backup - Backup Statistics") + report.append("Report Period : %s - %s" % (self.start_datetime, + self.end_datetime)) + report.append("--------------------------------------------------") + report.append("") + report.append("This is your box backup report, in summary:") + report.append("") + report.append("%d file(s) have been backed up." % total_files) + report.append("%d file(s) were uploaded." % len(self.uploaded_files)) + report.append("%d file(s) were patched." % len(self.patched_files)) + report.append("%d file(s) were synchronised." % len(self.synced_files)) + + report.append("") + report.append("%d warning(s) occurred." % len(self.warnings)) + report.append("%d error(s) occurred." % len(self.errors)) + report.append("") + report.append("") + + # If we asked for the backup stats and they're available + # show them. + if(self.stats is not None and self.stats != ""): + report.append("Your backup usage information follows:") + report.append("") + report.append(self.stats) + report.append("") + report.append("") + + # List the files if we've been asked for a verbose report. + if(self.verbose_report): + if len(self.uploaded_files) > 0: + report.append("Uploaded Files (%d)" % len(self.uploaded_files)) + report.append("---------------------") + for file in self.uploaded_files: + report.append(file) + report.append("") + report.append("") + + if len(self.patched_files) > 0: + report.append("Patched Files (%d)" % len(self.patched_files)) + report.append("---------------------") + for file in self.patched_files: + report.append(file) + report.append("") + report.append("") + + # Always output the warnings/errors. + if len(self.warnings) > 0: + report.append("Warnings (%d)" % len(self.warnings)) + report.append("---------------------") + for warning in self.warnings: + report.append(warning) + report.append("") + report.append("") + + if len(self.errors) > 0: + report.append("Errors (%d)" % len(self.errors)) + report.append("---------------------") + for error in self.errors: + report.append(error) + report.append("") + report.append("") + + self.report = "\r\n".join(report) + + def _locate_sendmail(self): + for path in SENDMAIL_PATHS: + sendmail = os.path.join(path, SENDMAIL_BIN) + if os.path.isfile(sendmail): + return sendmail + + return None + + def _rotate_log(self): + # If we're not configured to rotate then abort. + if(not self.rotate_log_file): + return + + # So we have these files to possibly account for while we process the + # rotation:- + # self.log_file, self.log_file.0, self.log_file.1.gz, self.log_file.2.gz + # self.log_file.3.gz....self.log_file.(ROTATE_COUNT-1).gz + # + # Algorithm:- + # * Delete last file. + # * Work backwards moving 5->6, 4->5, 3->4, etc... but stop at .0 + # * For .0 move it to .1 then gzip it. + # * Move self.log_file to .0 + # * Done. + + # If it exists, remove the oldest file. + if(os.path.isfile(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1))): + os.unlink(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1)) + + # Copy through the other gzipped log files. + for i in range(ROTATE_COUNT - 1, 1, -1): + src_file = self.log_file + ".%d.gz" % (i - 1) + dst_file = self.log_file + ".%d.gz" % i + + # If the source file exists move/rename it. + if(os.path.isfile(src_file)): + shutil.move(src_file, dst_file) + + # Now we need to handle the .0 -> .1.gz case. + if(os.path.isfile(self.log_file + ".0")): + # Move .0 to .1 + shutil.move(self.log_file + ".0", self.log_file + ".1") + + # gzip the file. + fh = open(self.log_file + ".1", "r") + zfh = gzip.GzipFile(self.log_file + ".1.gz", "w") + zfh.write(fh.read()) + zfh.flush() + zfh.close() + fh.close() + + # If gzip worked remove the original .1 file. + if(os.path.isfile(self.log_file + ".1.gz")): + os.unlink(self.log_file + ".1") + + # Finally move the current logfile to .0 + shutil.move(self.log_file, self.log_file + ".0") + + +def stderr(text): + sys.stderr.write("%s\n" % text) + +def usage(): + stderr("Usage: %s [OPTIONS]\n" % sys.argv[0]) + stderr("Valid Options:-") + stderr(" --logfile=LOGFILE\t\t\tSpecify the logfile to process,\n"+\ + "\t\t\t\t\tdefault: /var/log/syslog\n") + + stderr(" --configfile=CONFIGFILE\t\tSpecify the bbackupd config file,\n "+\ + "\t\t\t\t\tdefault: /etc/box/bbackupd.conf\n") + + stderr(" --email-to=user@example.com\t\tSpecify the e-mail address(es)\n"+\ + "\t\t\t\t\tto send the report to, default is to\n"+\ + "\t\t\t\t\tdisplay the report on the console.\n") + + stderr(" --email-from=user@example.com\t\tSpecify the e-mail address(es)"+\ + "\n\t\t\t\t\tto set the From: address to,\n "+\ + "\t\t\t\t\tdefault: report@boxbackup\n") + + stderr(" --stats\t\t\t\tIncludes the usage stats retrieved from \n"+\ + "\t\t\t\t\t'bbackupquery usage' in the report.\n") + + stderr(" --sort\t\t\t\tSorts the file lists in verbose mode.\n") + + stderr(" --debug\t\t\t\tEnables debug output.\n") + + stderr(" --verbose\t\t\t\tList every file that was backed up to\n"+\ + "\t\t\t\t\tthe server, default is to just display\n"+\ + "\t\t\t\t\tthe summary.\n") + + stderr(" --rotate\t\t\t\tRotates the log files like logrotate\n"+\ + "\t\t\t\t\twould, see the comments for a use-case.\n") + +def main(): + # The defaults + logfile = "/var/log/syslog" + configfile = "/etc/box/bbackupd.conf" + email_to = None + email_from = "report@boxbackup" + rotate = False + verbose = False + stats = False + sort = False + debug = False + # Parse the options + try: + opts, args = getopt.getopt(sys.argv[1:], "dosrvhl:c:t:f:", + ["help", "logfile=", "configfile=","email-to=", + "email-from=","rotate","verbose","stats","sort", + "debug"]) + except getopt.GetoptError: + usage() + return + + for opt, arg in opts: + if(opt in ("--logfile","-l")): + logfile = arg + elif(opt in ("--configfile", "-c")): + configfile = arg + elif(opt in ("--email-to", "-t")): + email_to = arg + elif(opt in ("--email-from", "-f")): + email_from = arg + elif(opt in ("--rotate", "-r")): + rotate = True + elif(opt in ("--verbose", "-v")): + verbose = True + elif(opt in ("--stats", "-s")): + stats = True + elif(opt in ("--sort", "-o")): + sort = True + elif(opt in ("--debug", "-d")): + debug = True + elif(opt in ("--help", "-h")): + usage() + return + + # Run the reporter + bbr = BoxBackupReporter(configfile, logfile, email_to, email_from, + rotate, verbose, stats, sort, debug) + try: + bbr.run() + bbr.deliver() + except BoxBackupReporter.BoxBackupReporterError, error_msg: + print error_msg + +if __name__ == "__main__": + main() diff --git a/contrib/debian/README.txt b/contrib/debian/README.txt new file mode 100644 index 00000000..ebe5fdf7 --- /dev/null +++ b/contrib/debian/README.txt @@ -0,0 +1,9 @@ +These start scripts are for Debian GNU/Linux. If installed manually they should +be placed in /etc/init.d. To create the symbolic links for the appropriate run +levels execute the following commands: + +update-rc.d bbackupd defaults 90 +update-rc.d bbstored defaults 80 + +James Stark + diff --git a/contrib/debian/bbackupd.in b/contrib/debian/bbackupd.in new file mode 100644 index 00000000..c340939a --- /dev/null +++ b/contrib/debian/bbackupd.in @@ -0,0 +1,59 @@ +#! /bin/sh + +# Start and stop the Box Backup client daemon. +# Originally by James Stark, modified by Chris Wilson and James O'Gorman +# For support, visit http://www.boxbackup.org/trac/wiki/MailingLists + +NAME=bbackupd +LONGNAME="Box Backup Client daemon" +BINARY=@sbindir_expanded@/$NAME +CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf +PIDFILE=@localstatedir_expanded@/bbackupd/$NAME.pid + +test -x $BINARY || exit 0 +test -f $CONFIG || exit 0 + +start_stop() { + start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" +} + +start_stop_verbose() { + if start_stop "$@"; then + echo "." + else + echo " failed!" + exit 1 + fi +} + +case $1 in + start) + echo -n "Starting $LONGNAME: $NAME" + start_stop_verbose --start + ;; + + stop) + echo -n "Stopping $LONGNAME: $NAME" + start_stop_verbose --stop + ;; + + reload|force-reload) + echo -n "Reloading $LONGNAME configuration" + start_stop_verbose --stop --signal 1 + ;; + + restart) + echo -n "Restarting $LONGNAME: $NAME" + if start_stop --stop --retry 5 && start_stop --start; then + echo "." + else + echo " failed!" + exit 1 + fi + ;; + + *) + echo "Usage: $0 {start|stop|reload|force-reload|restart}" +esac + +exit 0 diff --git a/contrib/debian/bbstored.in b/contrib/debian/bbstored.in new file mode 100644 index 00000000..c9214537 --- /dev/null +++ b/contrib/debian/bbstored.in @@ -0,0 +1,59 @@ +#! /bin/sh + +# Start and stop the Box Backup server daemon. +# Originally by James Stark, modified by Chris Wilson and James O'Gorman +# For support, visit http://www.boxbackup.org/trac/wiki/MailingLists + +NAME=bbstored +LONGNAME="Box Backup Server daemon" +BINARY=@sbindir_expanded@/$NAME +CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf +PIDFILE=@localstatedir_expanded@/run/$NAME.pid + +test -x $BINARY || exit 0 +test -f $CONFIG || exit 0 + +start_stop() { + start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" +} + +start_stop_verbose() { + if start_stop "$@"; then + echo "." + else + echo " failed!" + exit 1 + fi +} + +case $1 in + start) + echo -n "Starting $LONGNAME: $NAME" + start_stop_verbose --start + ;; + + stop) + echo -n "Stopping $LONGNAME: $NAME" + start_stop_verbose --stop + ;; + + reload|force-reload) + echo -n "Reloading $LONGNAME configuration" + start_stop_verbose --stop --signal 1 + ;; + + restart) + echo -n "Restarting $LONGNAME: $NAME" + if start_stop --stop --retry 5 && start_stop --start; then + echo "." + else + echo " failed!" + exit 1 + fi + ;; + + *) + echo "Usage: $0 {start|stop|reload|force-reload|restart}" +esac + +exit 0 diff --git a/contrib/mac_osx/org.boxbackup.bbackupd.plist.in b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in new file mode 100644 index 00000000..803deece --- /dev/null +++ b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in @@ -0,0 +1,20 @@ + + + + + Label + org.boxbackup.bbackupd + RunAtLoad + + ProgramArguments + + @prefix@/sbin/bbackupd + -F + @prefix@/etc/boxbackup/bbackupd.conf + + LowPriorityIO + + Nice + 1 + + diff --git a/contrib/mac_osx/org.boxbackup.bbstored.plist.in b/contrib/mac_osx/org.boxbackup.bbstored.plist.in new file mode 100644 index 00000000..bfe8c88c --- /dev/null +++ b/contrib/mac_osx/org.boxbackup.bbstored.plist.in @@ -0,0 +1,21 @@ + + + + + Label + org.boxbackup.bbstored + RunAtLoad + + ProgramArguments + + @prefix@/sbin/bbstored + -F + @prefix@/etc/boxbackup/bbackupd.conf + + + LowPriorityIO + + Nice + 1 + + diff --git a/contrib/redhat/README.txt b/contrib/redhat/README.txt new file mode 100644 index 00000000..cfc8d968 --- /dev/null +++ b/contrib/redhat/README.txt @@ -0,0 +1,7 @@ +These start scripts are for Fedora Core or RedHat Enterprise Linux. If +installed manually they should be placed in /etc/rc.d/init.d. + +They may also work for Mandrake. + +Martin Ebourne +martin@zepler.org diff --git a/contrib/redhat/bbackupd.in b/contrib/redhat/bbackupd.in new file mode 100644 index 00000000..2f51137a --- /dev/null +++ b/contrib/redhat/bbackupd.in @@ -0,0 +1,83 @@ +#! /bin/bash +# +# bbackupd Start/Stop the box backup client daemon. +# +# chkconfig: 345 93 07 +# description: bbackupd is the client side deamon for Box Backup, \ +# a completely automatic on-line backup system. +# processname: bbackupd +# config: @sysconfdir_expanded@/box +# pidfile: @localstatedir_expanded@/bbackupd.pid + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +# See how we were called. + +prog="bbackupd" + +# Check that configuration exists. +[ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 + +start() { + echo -n $"Starting $prog: " + daemon @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog + return $RETVAL +} + +rhstatus() { + status @sbindir_expanded@/$prog +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog configuration: " + killproc @sbindir_expanded@/$prog -HUP + retval=$? + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + reload + ;; + status) + rhstatus + ;; + condrestart) + [ -f /var/lock/subsys/$prog ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" + exit 1 +esac + +exit $? diff --git a/contrib/redhat/bbstored.in b/contrib/redhat/bbstored.in new file mode 100644 index 00000000..755a8569 --- /dev/null +++ b/contrib/redhat/bbstored.in @@ -0,0 +1,83 @@ +#! /bin/bash +# +# bbstored Start/Stop the box backup server daemon. +# +# chkconfig: 345 93 07 +# description: bbstored is the server side daemon for Box Backup, \ +# a completely automatic on-line backup system. +# processname: bbstored +# config: @sysconfdir_expanded@/box +# pidfile: @localstatedir_expanded@/bbstored.pid + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +# See how we were called. + +prog="bbstored" + +# Check that configuration exists. +[ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 + +start() { + echo -n $"Starting $prog: " + daemon @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog + return $RETVAL +} + +rhstatus() { + status @sbindir_expanded@/$prog +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog configuration: " + killproc @sbindir_expanded@/$prog -HUP + retval=$? + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + reload + ;; + status) + rhstatus + ;; + condrestart) + [ -f /var/lock/subsys/$prog ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" + exit 1 +esac + +exit $? diff --git a/contrib/rpm/README.txt b/contrib/rpm/README.txt new file mode 100644 index 00000000..290a5252 --- /dev/null +++ b/contrib/rpm/README.txt @@ -0,0 +1,16 @@ +BUILDING AN RPM + +The easy way is to: + +rpmbuild -ta + +where is the archive you downloaded of Box Backup. + +This RPM should work on RedHat Enterprise, Fedora Core, Mandrake, SUSE, and +any similar distributions. It has been developed and tested on Fedora Core. + +Changes for SUSE Linux were provided by Chris Smith +(chris.smith@nothingbutnet.co.nz). + +Martin Ebourne +martin@zepler.org diff --git a/contrib/rpm/boxbackup.spec b/contrib/rpm/boxbackup.spec new file mode 100644 index 00000000..e782a6c3 --- /dev/null +++ b/contrib/rpm/boxbackup.spec @@ -0,0 +1,244 @@ +%define bb_user_id 171 +%define ident %{name}-%{version} + +# In official distribution tarballs, distribution files are copied to +# the base directory (where configure is), so distribution_dir should be empty. +# This is the default, overridden by the following block in non-distribution +# builds. +%define distribution_dir '' + +# BOX_PRIVATE_BEGIN +# In unofficial tarballs, made from svn, distribution files are still in +# distribution/boxbackup, so the following line overrides the default above: +# (this section will be removed automatically from distribution tarballs +# by infrastructure/makedistribution.pl) +%define distribution_dir distribution/boxbackup/ +# BOX_PRIVATE_END + +# Detect distribution. So far we only special-case SUSE. If you need to make +# any distro specific changes to get the package building on your system +# please email them to boxbackup-dev@boxbackup.org +#%define is_fc %(test -e %{_sysconfdir}/fedora-release && echo 1 || echo 0) +#%define is_mdk %(test -e %{_sysconfdir}/mandrake-release && echo 1 || echo 0) +#%define is_rh %(test -e %{_sysconfdir}/redhat-release && echo 1 || echo 0) +%define is_suse %(test -e %{_sysconfdir}/SuSE-release && echo 1 || echo 0) + +%if %{is_suse} +%define init_dir %{_sysconfdir}/init.d +%define distribution suse +%define rc_start rc +%else +%define init_dir %{_sysconfdir}/rc.d/init.d +%define distribution redhat +%define rc_start "service " +%endif + +Summary: An automatic on-line backup system for UNIX. +Name: boxbackup +Version: ###DISTRIBUTION-VERSION-NUMBER### +Release: 1%{?dist} +License: BSD +Group: Applications/Archiving +Packager: boxbackup-dev@boxbackup.org +URL: http://www.boxbackup.org/ +Source0: %{ident}.tgz +Requires: openssl >= 0.9.7a +BuildRoot: %{_tmppath}/%{ident}-%{release}-root +BuildRequires: openssl >= 0.9.7a, openssl-devel + +%description +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +%package client +Summary: An automatic on-line backup system for UNIX. +Group: Applications/Archiving + +%description client +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +This package contains the client. + +%package server +Summary: An automatic on-line backup system for UNIX. +Group: System Environment/Daemons + +%description server +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +This package contains the server. + +%prep +%setup -q + +%build +echo -e '%{version}\n%{name}' > VERSION.txt +test -e configure || ./bootstrap +%configure + +make + +%install +rm -rf $RPM_BUILD_ROOT + +mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident} +mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter +mkdir -p $RPM_BUILD_ROOT%{_bindir} +mkdir -p $RPM_BUILD_ROOT%{_sbindir} +mkdir -p $RPM_BUILD_ROOT%{init_dir} +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored +mkdir -p $RPM_BUILD_ROOT%{_var}/lib/box + +install -m 644 -t $RPM_BUILD_ROOT%{_docdir}/%{ident} \ + BUGS.txt \ + VERSION.txt \ + ExceptionCodes.txt \ + LICENSE-GPL.txt \ + LICENSE-DUAL.txt \ + %{distribution_dir}CONTACT.txt \ + %{distribution_dir}DOCUMENTATION.txt \ + %{distribution_dir}LINUX.txt \ + %{distribution_dir}THANKS.txt + +install -m 644 contrib/bbreporter/LICENSE \ + $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter +install -m 755 contrib/bbreporter/bbreporter.py \ + $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter + +# Client +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd.conf +install -m 755 contrib/%{distribution}/bbackupd $RPM_BUILD_ROOT%{init_dir} +%if %{is_suse} +ln -s ../../%{init_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir}/rcbbackupd +%endif +%define client_dir parcels/%{ident}-backup-client-linux-gnu +install %{client_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupquery $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupctl $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupd-config $RPM_BUILD_ROOT%{_sbindir} + +# Server +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored.conf +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/raidfile.conf +install -m 755 contrib/%{distribution}/bbstored $RPM_BUILD_ROOT%{init_dir} +%if %{is_suse} +ln -s ../../%{init_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir}/rcbbstored +%endif +%define server_dir parcels/%{ident}-backup-server-linux-gnu +install %{server_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstoreaccounts $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstored-certs $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstored-config $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/raidfile-config $RPM_BUILD_ROOT%{_sbindir} + +%pre server +%{_sbindir}/useradd -c "Box Backup" -u %{bb_user_id} \ + -s /sbin/nologin -r -d / box 2> /dev/null || : + +%post client +/sbin/chkconfig --add bbackupd +if [ ! -f %{_sysconfdir}/box/bbackupd.conf ]; then + echo "You should run the following to configure the client:" + echo "bbackupd-config %{_sysconfdir}/box lazy " \ + "%{_var}/lib/box " + echo "Then follow the instructions. Use this to start the client:" + echo "%{rc_start}bbackupd start" +fi + +%post server +/sbin/chkconfig --add bbstored +if [ ! -f %{_sysconfdir}/box/bbstored.conf ]; then + echo "You should run the following to configure the server:" + echo "raidfile-config %{_sysconfdir}/box 2048 []" + echo "bbstored-config %{_sysconfdir}/box" `hostname` box + echo "Then follow the instructions. Use this to start the server:" + echo "%{rc_start}bbstored start" +fi + +%preun client +if [ $1 = 0 ]; then + %{init_dir}/bbackupd stop > /dev/null 2>&1 + /sbin/chkconfig --del bbackupd +fi + +%preun server +if [ $1 = 0 ]; then + %{init_dir}/bbstored stop > /dev/null 2>&1 + /sbin/chkconfig --del bbstored +fi + + +%clean +rm -rf $RPM_BUILD_ROOT + +%files client +%defattr(-,root,root,-) +%dir %attr(700,root,root) %{_sysconfdir}/box/bbackupd +%dir %attr(755,root,root) %{_var}/lib/box +%doc %{_docdir}/%{ident}/*.txt +%config %{init_dir}/bbackupd +%if %{is_suse} +%{_sbindir}/rcbbackupd +%endif +%config %ghost %{_sysconfdir}/box/bbackupd.conf +%{_sbindir}/bbackupd +%{_sbindir}/bbackupquery +%{_sbindir}/bbackupctl +%{_sbindir}/bbackupd-config + +%files server +%defattr(-,root,root,-) +%dir %attr(700,box,root) %{_sysconfdir}/box/bbstored +%config %{init_dir}/bbstored +%if %{is_suse} +%{_sbindir}/rcbbstored +%endif +%config %ghost %{_sysconfdir}/box/bbstored.conf +%config %ghost %{_sysconfdir}/box/raidfile.conf +%{_sbindir}/bbstored +%{_sbindir}/bbstoreaccounts +%{_sbindir}/bbstored-certs +%{_sbindir}/bbstored-config +%{_sbindir}/raidfile-config +%doc %{_docdir}/%{ident}/bbreporter + +%changelog +* Thu Apr 23 2009 Martin Ebourne +- Use dist tag in version + +* Thu May 29 2008 Martin Ebourne +- Fix paths to bbreporter files + +* Sat Jan 13 2006 Chris Wilson +- Support building from an unofficial tarball (from svn) by changing + %{distribution_dir} at the top. +- Write our RPM version number into VERSION.txt and hence compile it in + +* Wed Dec 28 2005 Martin Ebourne +- Box now uses autoconf so use configure macro + +* Fri Oct 1 2004 Martin Ebourne - 0.08-3 +- Moved most of the exes to /usr/sbin +- SUSE updates from Chris Smith + +* Fri Sep 24 2004 Martin Ebourne - 0.08-2 +- Added support for other distros +- Changes for SUSE provided by Chris Smith + +* Mon Sep 16 2004 Martin Ebourne - 0.07-1 +- Initial build diff --git a/contrib/solaris/bbackupd-manifest.xml.in b/contrib/solaris/bbackupd-manifest.xml.in new file mode 100644 index 00000000..ab30a78e --- /dev/null +++ b/contrib/solaris/bbackupd-manifest.xml.in @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/solaris/bbackupd-smf-method.in b/contrib/solaris/bbackupd-smf-method.in new file mode 100755 index 00000000..0ff610bf --- /dev/null +++ b/contrib/solaris/bbackupd-smf-method.in @@ -0,0 +1,24 @@ + +PIDFILE=@localstatedir_expanded@/bbackupd.pid + +case $1 in + + # SMF arguments (start and restart [really "refresh"]) +'start') + @sbindir_expanded@/bbackupd + ;; + +'restart') + if [ -f "$PIDFILE" ]; then + /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` + fi + ;; + +*) + echo "Usage: $0 { start | restart }" + exit 1 + ;; +esac + +exit $? + diff --git a/contrib/solaris/bbstored-manifest.xml.in b/contrib/solaris/bbstored-manifest.xml.in new file mode 100644 index 00000000..f7133677 --- /dev/null +++ b/contrib/solaris/bbstored-manifest.xml.in @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/solaris/bbstored-smf-method.in b/contrib/solaris/bbstored-smf-method.in new file mode 100755 index 00000000..dbdb3e69 --- /dev/null +++ b/contrib/solaris/bbstored-smf-method.in @@ -0,0 +1,23 @@ +PIDFILE=@localstatedir_expanded@/bbstored.pid + +case $1 in + + # SMF arguments (start and restart [really "refresh"]) +'start') + @sbindir_expanded@/bbstored + ;; + +'restart') + if [ -f "$PIDFILE" ]; then + /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` + fi + ;; + +*) + echo "Usage: $0 { start | restart }" + exit 1 + ;; +esac + +exit $? + diff --git a/contrib/suse/README.txt b/contrib/suse/README.txt new file mode 100644 index 00000000..0f260b7a --- /dev/null +++ b/contrib/suse/README.txt @@ -0,0 +1,5 @@ +These start scripts are for SUSE Linux. If installed manually they should be +placed in /etc/init.d. + +Copyright (c)2004, Nothing But Net Limited + diff --git a/contrib/suse/bbackupd.in b/contrib/suse/bbackupd.in new file mode 100644 index 00000000..77fd40ba --- /dev/null +++ b/contrib/suse/bbackupd.in @@ -0,0 +1,103 @@ +#!/bin/sh +# +# Copyright (c)2004, Nothing But Net Limited +# +# +###################################################################### +# RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP +# SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: +# +# http://www.boxbackup.org/license.html +###################################################################### +# +# /etc/init.d/bbackupd +# and its symbolic link +# /(usr/)sbin/rcbbackupd +# +### BEGIN INIT INFO +# Provides: bbackupd +# Required-Start: $named $network $local_fs $syslog +# X-UnitedLinux-Should-Start: $time ypbind sendmail +# Required-Stop: $named $network $localfs $syslog +# X-UnitedLinux-Should-Stop: $time ypbind sendmail +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: BoxBackup client side daemon +# Description: Client daemon for the BoxBackup software +# that allows you to communicate with a bbstored server. +### END INIT INFO + +# Check for missing binaries (stale symlinks should not happen) +BBACKUPD_BIN=@sbindir_expanded@/bbackupd +if [ ! -x $BBACKUPD_BIN ] ; then + echo "$BBACKUPD_BIN not installed" + exit 5 +fi + +. /etc/rc.status + +# Reset status of this service +rc_reset + +case "$1" in + start) + echo -n "Starting bbackupd " + startproc $BBACKUPD_BIN + rc_status -v + ;; + + stop) + echo -n "Shutting down bbackupd " + killproc -TERM $BBACKUPD_BIN + rc_status -v + ;; + + try-restart|condrestart) + if test "$1" = "condrestart"; then + echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" + fi + $0 status + if test $? = 0; then + $0 restart + else + rc_reset # Not running is not a failure. + fi + rc_status + ;; + + restart) + $0 stop + $0 start + rc_status + ;; + + force-reload) + echo -n "Reload service bbackupd " + killproc -HUP $BBACKUPD_BIN + rc_status -v + ;; + + reload) + echo -n "Reload service bbackupd " + killproc -HUP $BBACKUPD_BIN + rc_status -v + ;; + + status) + echo -n "Checking for service bbackupd " + checkproc $BBACKUPD_BIN + rc_status -v + ;; + + probe) + test @sysconfdir_expanded@/box/bbackupd.conf \ + -nt @localstatedir_expanded@/bbackupd/bbackupd.pid \ + && echo reload + ;; + + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + +esac +rc_exit diff --git a/contrib/suse/bbstored.in b/contrib/suse/bbstored.in new file mode 100644 index 00000000..e84edfd1 --- /dev/null +++ b/contrib/suse/bbstored.in @@ -0,0 +1,104 @@ +#!/bin/sh +# +# Copyright (c)2004, Nothing But Net Limited +# +# +###################################################################### +# RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP +# SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: +# +# http://www.boxbackup.org/license.html +###################################################################### +# +# /etc/init.d/bbstored +# and its symbolic link +# /(usr/)sbin/rcbbstored +# +### BEGIN INIT INFO +# Provides: bbstored +# Required-Start: $named $network $local_fs $syslog +# X-UnitedLinux-Should-Start: $time ypbind sendmail +# Required-Stop: $named $network $localfs $syslog +# X-UnitedLinux-Should-Stop: $time ypbind sendmail +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: BoxBackup server side daemon +# Description: Server daemon for the BoxBackup software, +# to which bbackupd clients connect. +### END INIT INFO +# + +# Check for missing binaries (stale symlinks should not happen) +BBSTORED_BIN=@sbindir_expanded@/bbstored +if [ ! -x $BBSTORED_BIN ] ; then + echo "$BBSTORED_BIN not installed" + exit 5 +fi + +. /etc/rc.status + +# Reset status of this service +rc_reset + +case "$1" in + start) + echo -n "Starting bbstored " + startproc $BBSTORED_BIN + rc_status -v + ;; + + stop) + echo -n "Shutting down bbstored " + killproc -TERM $BBSTORED_BIN + rc_status -v + ;; + + try-restart|condrestart) + if test "$1" = "condrestart"; then + echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" + fi + $0 status + if test $? = 0; then + $0 restart + else + rc_reset # Not running is not a failure. + fi + rc_status + ;; + + restart) + $0 stop + $0 start + rc_status + ;; + + force-reload) + echo -n "Reload service bbstored " + killproc -HUP $BBSTORED_BIN + rc_status -v + ;; + + reload) + echo -n "Reload service bbstored " + killproc -HUP $BBSTORED_BIN + rc_status -v + ;; + + status) + echo -n "Checking for service bbstored " + checkproc $BBSTORED_BIN + rc_status -v + ;; + + probe) + test @sysconfdir_expanded@/box/bbstored.conf \ + -nt @localstatedir_expanded@/run/bbstored.pid && echo reload + ;; + + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + ;; + +esac +rc_exit diff --git a/contrib/windows/installer/boxbackup.mpi.in b/contrib/windows/installer/boxbackup.mpi.in new file mode 100755 index 00000000..12f6d62d --- /dev/null +++ b/contrib/windows/installer/boxbackup.mpi.in @@ -0,0 +1,3392 @@ +array set info { +AccountNo +10005005 + +AllowLanguageSelection +No + +AppName +<%BrandName%> + +ApplicationID +E10C6FD9-E524-28BD-B0AB3588F16C + +ApplicationURL +http://www.boxbackup.org/ + +AutoFileGroups +No + +AutoRefreshFiles +Yes + +BBVersionNo +@box_version@ + +BrandName +{Box Backup} + +BuildFailureAction +{Fail (recommended)} + +CancelledInstallAction +{Rollback and Stop} + +CleanupCancelledInstall +Yes + +CommandLineFailureAction +{Fail (recommended)} + +Company +{Tebuco, Inc. and Ben Summers and Contributors} + +CompressionLevel +6 + +CompressionMethod +zlib + +ConfigFileName +{<%InstallDir%>\bbackupd.conf} + +ConfigFileTemplate +{<%InstallDir%>\templates\template.conf} + +Copyright +{2003-2008 Tebuco, Inc. and Ben Summers and Contributors} + +CreateDesktopShortcut +No + +CreateQuickLaunchShortcut +No + +DefaultDirectoryLocation +{} + +DefaultLanguage +English + +EncryptedKeyFilePassword +Enter_EncryptedKeys_Password_Here + +Ext +.exe + +ExtractSolidArchivesOnStartup +No + +Icon +{} + +Image +@build_dir@/docs/html/images/bblogo.png + +IncludeDebugging +Yes + +InstallDirSuffix +<%ShortAppName%> + +InstallPassword +{} + +InstallVersion +@box_version@ + +Language,de +No + +Language,en +Yes + +Language,es +No + +Language,fr +No + +Language,hu +Yes + +Language,it +Yes + +Language,nl +Yes + +Language,pl +No + +Language,pt_br +No + +Language,ru +Yes + +LaunchApplication +No + +PackageDescription +{<%BrandName%> Backup Service} + +PackageLicense +{} + +PackageMaintainer +{Tebuco, Inc. and Ben Summers and Contributors} + +PackageName +<%ShortAppName%> + +PackagePackager +{Tebuco, Inc. and Ben Summers and Contributors} + +PackageRelease +<%PatchVersion%> + +PackageSummary +{} + +PackageVersion +<%MajorVersion%>.<%MinorVersion%> + +PreserveFileAttributes +Yes + +PreserveFilePermissions +Yes + +ProjectID +140B9882-3327-FEA8-13415A62FBB2 + +ProjectVersion +1.2.9.0 + +SaveOnlyToplevelDirs +No + +ScriptExt +.bat + +ServiceExeName +bbackupd.exe + +ServiceName +<%BrandName%> + +ShortAppName +<%BrandName%> + +SkipUnusedFileGroups +Yes + +SystemLanguage +en_us + +Theme +Modern_Wizard + +ThemeDir +Modern_Wizard + +ThemeVersion +1 + +UpgradeApplicationID +{} + +UserInfoAcctNo +<%AccountNo%> + +UserInfoCompany +{} + +UserInfoEmail +{} + +UserInfoName +{} + +UserInfoPhone +{} + +Version +@box_version@ + +ViewReadme +No + +WizardHeight +365 + +WizardWidth +500 + +} + +array set ::InstallJammer::InstallCommandLineOptions { +D +{{} Prefix No No {} {set the value of an option in the installer}} + +S +{InstallMode Switch No No Silent {run the installer in silent mode}} + +T +{Testing Switch Yes No {} {run installer without installing any files}} + +Y +{InstallMode Switch No No Default {accept all defaults and run the installer}} + +debug +{Debugging Switch Yes No {} {run installer in debug mode}} + +debugconsole +{ShowConsole Switch Yes No {} {run installer with a debug console open}} + +mode +{InstallMode Choice No No {Console Default Silent Standard} {set the mode to run the installer in}} + +prefix +{InstallDir String No No {} {set the installation directory}} + +test +{Testing Switch Yes No {} {run installer without installing any files}} + +} +array set ::InstallJammer::UninstallCommandLineOptions { +S +{InstallMode Switch No No Silent {run the uninstaller in silent mode}} + +Y +{InstallMode Switch No No Default {accept all defaults and run the uninstaller}} + +debugconsole +{ShowConsole Switch Yes No {} {run uninstaller with a debug console open}} + +mode +{UninstallMode Choice No No {Console Silent Standard} {set the mode to run the uninstaller in}} + +test +{Testing Switch Yes No {} {run uninstaller without uninstalling any files}} + +} +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 +File ::D6E262BC-8A84-B6DB-794B-8FDC8AECB079 -name mgwz.dll -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::E56A0360-7D7F-D99E-E9A4-3C20BC4C2B99 -name mingwm10.dll -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::47602DF7-8463-AB89-E13F-11983610CAA2 -type dir -name tools -location @build_dir@/contrib/windows/installer/tools -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::F7F61231-C340-5CD5-686B-01F521994B0C -name InstallService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::68DAE474-165D-81FE-1396-FDD2E6081B41 -name KillBackupProcess.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::2436C940-3332-13AA-7613-8EE67C35CE9B -name ReloadConfig.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::336DDAC3-F3BA-1117-73D4-11DFEF9E98AB -name RemoveService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::0C15AE46-0FF3-3B7F-FC55-D91EF279DBD3 -name RestartService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::58D97EDE-58F2-15D7-7113-BEE3047F0782 -name StartService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::BE7CDB16-D3FE-30FA-2153-7C0509CD5E78 -name StopService.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::73BD5859-FB38-71F8-24BD-BDCF871F9FD3 -name Sync.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::67B3838F-4EF7-2C1C-2E86-78DB8ADD6682 -name ShowUsage.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::2646A97C-C0D9-A29C-E145-C5C371F44938 -name QueryOutputAll.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::2F41E3D2-DA4D-2FCB-B3D5-F04032D17A63 -name QueryOutputCurrent.bat -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::C6430BDD-8A07-B80E-FC0C-426C16EB4187 -name RemoteControl.exe -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 -type dir -name .svn -active 0 -parent 47602DF7-8463-AB89-E13F-11983610CAA2 +File ::31429CC4-525E-4E30-9328-4774AFA9F619 -name entries -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::A27BEFB6-1421-4030-8F11-F04316BCE57C -name format -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::C99700CE-1035-498C-9A96-B60835652077 -type dir -name prop-base -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::6FF9DFDE-4BB7-4319-AC85-BF1E88731C1C -name InstallService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::6AAE8600-5CFC-4240-A47F-5CFCF4E571C6 -name KillBackupProcess.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::834C33D3-4B53-4B2D-9380-A05420AEE75F -name QueryOutputAll.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::854AD23A-5DDE-44C4-900C-34F5845E6747 -name QueryOutputCurrent.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::9A587DE7-B17C-4CDF-B92C-0D267E30E361 -name ReloadConfig.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::197AAEFA-C62E-4E79-890F-C2E4C8440239 -name RemoteControl.exe.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::912E0F50-53DD-4483-A4F4-CA69944A5959 -name RemoveService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::BDD695BF-9C7E-4F06-BBCE-EC89536DCF27 -name RestartService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::61615EE4-BF66-40E7-B89A-E6A50B92AF93 -name ShowUsage.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::A6F7C6B7-9759-4B86-9388-4A42E6F7C5C3 -name StartService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::45D3E7F5-277B-4E52-81BA-ED6D2BB441D7 -name StopService.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::58966972-8387-4D14-A06E-15AA176633A3 -name Sync.bat.svn-base -active 0 -parent C99700CE-1035-498C-9A96-B60835652077 +File ::2A77AF5B-4761-45B5-A543-6328A7F0F39B -type dir -name props -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 -type dir -name text-base -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::D972D6B2-40E5-40B3-BC06-66B8B7F51B04 -name InstallService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::2F14E4F3-5331-4AC5-93F7-C4748970C7F4 -name KillBackupProcess.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::9F3663B2-8BAA-4EAE-B606-53D5C922E703 -name QueryOutputAll.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::BE9BD4FB-DF44-4F4B-BB55-15285A8566BA -name QueryOutputCurrent.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::3B4E0BF4-7FDD-4903-8D43-76C43F38C6C4 -name ReloadConfig.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::038CEEA0-3F21-48F6-B109-BBE1EF7D3E96 -name RemoteControl.exe.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::0C177E04-DF2D-43AB-8A42-9E7A389AB1D1 -name RemoveService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::9BE16F40-9D1D-4C84-843D-955A44069040 -name RestartService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::4E503A3C-EB42-4870-9849-D508A3D9BAB7 -name ShowUsage.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::131B7D8B-1BEB-456C-8F05-386C2EAFBEAE -name StartService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::D3D0AFC1-CB6C-42D4-8C05-21898505DA40 -name StopService.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::C89F78B2-25A7-432B-9D92-DC2AB636CFB5 -name Sync.bat.svn-base -active 0 -parent BF74F2C1-3CE7-4875-9B52-CD0F527E01C7 +File ::90695C82-0000-4F6A-8FE7-0ABDEAA17CAE -type dir -name tmp -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::7DFF0EE4-7298-4C8C-A5BC-56BBDD81CFC8 -type dir -name prop-base -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE +File ::4C60E473-119E-4B0B-9B01-56240F24D9D5 -type dir -name props -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE +File ::E1E25ACC-487B-4191-B8CF-9E7C8C88EA09 -type dir -name text-base -active 0 -parent 90695C82-0000-4F6A-8FE7-0ABDEAA17CAE +File ::E7258732-3D21-4E89-AA41-24AA8B8EBF29 -name all-wcprops -active 0 -parent 5EADAB59-F559-44CB-A3EE-9A56D4EF17C8 +File ::9CEBA2A0-C68B-48BA-944E-2E8EE9A35D97 -name bbackupctl.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::497483A6-7264-4361-86F2-F2703F719908 -name bbackupd-config -active 0 -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::204358FC-610F-47DF-8928-1D0E39921700 -targetfilename templates/original.conf -name bbackupd.conf -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::98C376E2-A6C3-4B6C-BBD4-F70CAC2E6A7B -name bbackupd.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +File ::5C3EAB34-7CD4-4DF3-9DEB-0FC23A6F5812 -name bbackupquery.exe -parent B9F58CFC-EE7A-BEE4-62CB-2C10665095A2 +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 +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 +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 +InstallComponent 1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E -setup Install -type pane -title {User Information} -component UserInformation -active Yes -parent StandardInstall +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 +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 +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 +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 +InstallComponent 3CFFF099-6122-46DD-9CE4-F5819434AC53 -setup Install -type action -title {Stop running service} -component ExecuteExternalProgram -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent FB697A88-2842-468E-9776-85E84B009340 -setup Install -type action -title {Remove installed service} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 41CDE776-2667-5CEB-312A-FC4C33A83E7F -setup Install -type action -title {Backup File} -component BackupFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 0D93323D-779D-44A8-1E0614E5285D -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 5CA3EA16-E37C-AABE-E576C4636EB0 -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent F5F21749-8B3A-49C6-9138-9C4D6D703D26 -setup Install -type action -title {Unpack Keys} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent FDF68FD6-BEA8-4A74-867D-5139F4D9E793 -setup Install -type action -title Wait -component Wait -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent E56ADFF4-C15E-AEDB-A599-C468AF72C4BB -setup Install -type action -title {Copy File NotifySysAdmin} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent D9F88AC1-3D2D-F6DB-871E-3A0E016770B1 -setup Install -type action -title {Copy File config} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B -setup Install -type action -title {Replace Text In File} -component ReplaceTextInFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +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 +InstallComponent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 -setup Install -type pane -title SetBackupLocations -component CustomBlankPane2 -command reorder -active Yes -parent StandardInstall +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 +InstallComponent 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 2E2963BD-DDBD-738D-A910-B7F3F04946F9 -setup Install -type action -title ShowAddAnotherValue -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 93AA298C-B64E-5683-14D2-7B86F7DEFD2C -setup Install -type action -title BackupLocationName -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 3FDB57ED-598D-8A4E-CEF7-D90833305558 -setup Install -type action -title {Backup Directory} -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A -setup Install -type action -title BackupLocationShortName -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 855DE408-060E-3D35-08B5-1D9AB05C2865 -setup Install -type action -title Exclusions -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +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 +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 +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 +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 +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 +InstallComponent 6FEE2889-0338-1D49-60BF-1471F465AB26 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8 -setup Install -type action -title {Copy File} -component CopyFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent D23DD94C-E517-7F34-FD59-802CB18AB887 -setup Install -type action -title {Adjust Line Feeds} -component AdjustLineFeeds -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 7D8E1902-2BC4-80D8-2C18771E7C22 -setup Install -type action -title {Installing service} -component ExecuteExternalProgram -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 1C14291C-0971-4283-92E9-3808401303F5 -setup Install -type action -title {Starting service} -component ExecuteExternalProgram -active No -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 6C323815-B9AB-FA94-4F5D152EBC51 -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent StandardInstall +InstallComponent 574198A7-7322-2F5E-02EF185D965C -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent DefaultInstall +InstallComponent 8A761DBD-0640-D98C-9B3AD7672A8F -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 574198A7-7322-2F5E-02EF185D965C +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 +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 +InstallComponent CFFA27AF-A641-E41C-B4A0E3BB3CBB -setup Install -type action -conditions {592F46AE-8CEE-01F3-0BA7EBDCA4F4 793D8178-0F51-7F07-BC5886586D3C} -title {Launch Application Checkbutton} -component AddWidget -command insert -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 -command insert -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 -command insert -active Yes -parent 1F0926EE-6884-1330-B4A1DB11C1BF +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 +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 +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 +InstallComponent 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6 -setup Install -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleInstall +InstallComponent 6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1 -setup Install -type action -title Exit -component Exit -active Yes -parent ConsoleInstall +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 +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 +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 +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 +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 +InstallComponent B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E -setup Install -type action -title {stopservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7 -setup Install -type action -title {Install Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 25AA533E-02FC-47D9-9273-25266B8FA1F9 -setup Install -type action -title {Remove Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent CDD84DE3-C970-458F-9162-1A3CE0AA716B -setup Install -type action -title {startservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent B5DFEC63-92A9-4686-909E-0CE78A7069D6 -setup Install -type action -title {restartservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent C0452595-F3EB-43AD-BCA2-661437584636 -setup Install -type action -title {editconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 1AF5CD58-65C0-49CB-9A9D-994816CF414E -setup Install -type action -title {QueryUpload Shortcut} -component InstallProgramFolderShortcut -active No -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D -setup Install -type action -title {killbackupprocess Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968 -setup Install -type action -title {reloadconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 6F61CDA8-30C9-454F-82A3-9987E1203079 -setup Install -type action -title {sync Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 9A663209-495B-ED16-09BE-457B61148022 -setup Install -type action -title QueryCurrent -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +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 +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 +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 -command insert -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 -command insert -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 -command insert -active No -parent FEFD090D-C133-BC95-B3564F693CD3 +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 +InstallComponent E23AC50D-7CFB-800E-A99C6F4068F8 -setup Install -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsInstall +InstallComponent 3B8CDC8E-1239-D2E9-DF4CA6B1756D -setup Uninstall -type pane -title Uninstall -component Uninstall -active Yes -parent StandardUninstall +InstallComponent 19ADBDDB-1690-4A57-913E32A026C4 -setup Uninstall -type action -title {Modify Widget} -component ModifyWidget -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D +InstallComponent 7A983CD8-302C-4942-BE59-525C5B5FA2F2 -setup Uninstall -type action -title {Stop Backup Process} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D +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 +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 +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 -command insert -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 -command insert -active Yes -parent ConsoleUninstall +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 +InstallComponent 20CBDBEA-2217-457B-8D98-D692C4F591E9 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall +InstallComponent 7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE -setup Uninstall -type action -title Exit -component Exit -active Yes -parent ConsoleUninstall +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 +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 +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 +InstallComponent 39D7394E-04E9-CA70-0034DB830BFE -setup Uninstall -type action -title {Uninstall Selected Files} -component UninstallSelectedFiles -active Yes -parent F4024A3E-9A6D-2726-5E0CFFA93054 +InstallComponent 39270FD8-932E-6132-7EF795ED9B93 -setup Uninstall -type actiongroup -title {Finish Actions} -active Yes -parent ActionGroupsUninstall +InstallComponent 905DA2E9-988C-2F27-BB1F5F274AC9 -setup Uninstall -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsUninstall + +array set Properties { +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Active +No + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Comment +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Conditions +{0 conditions} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ExecuteAction +{Before Next Pane is Displayed} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ResultVirtualText +BackupLocationName + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,TclScript +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +0357FAE9-FCFD-26D8-6541D810CD61,CheckCondition +{Before Action is Executed} + +0357FAE9-FCFD-26D8-6541D810CD61,Filename +<%ProgramReadme%> + +05060263-E852-87AB-8D0F2954CAA6,Conditions +{0 conditions} + +0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,Prompt +<%ConsoleSelectDestinationText%> + +0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,VirtualText +InstallDir + +0D93323D-779D-44A8-1E0614E5285D,Conditions +{0 conditions} + +0D93323D-779D-44A8-1E0614E5285D,State +disabled + +0D93323D-779D-44A8-1E0614E5285D,Widget +{Back Button;Next Button} + +0FDBA082-90AB-808C-478A-A13E7C525336,Conditions +{0 conditions} + +0FDBA082-90AB-808C-478A-A13E7C525336,ExecuteAction +{Before Next Pane is Displayed} + +0FDBA082-90AB-808C-478A-A13E7C525336,ResultVirtualText +BackupLocationNumber + +0FDBA082-90AB-808C-478A-A13E7C525336,TclScript +{set BackupLocationNumber 1} + +13BD88FE-CD71-5AC7-E99C10B6CB28,CheckCondition +{Before Action is Executed} + +13BD88FE-CD71-5AC7-E99C10B6CB28,Filename +<%ProgramReadme%> + +1528F4F0-145C-A48D-A8526DBB6289,CheckCondition +{Before Action is Executed} + +1528F4F0-145C-A48D-A8526DBB6289,Filename +<%ProgramExecutable%> + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Alias +{Stop Backup Windows Process} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Conditions +{0 conditions} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,FileName +<%ShortAppName%>-program-killbackupprocess + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,ShortcutName +{Stop backup process} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,TargetFileName +<%InstallDir%>/tools/KillBackupProcess.bat + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,WorkingDirectory +<%InstallDir%> + +16D53E40-546B-54C3-088B1B5E3BBB,Background +white + +16D53E40-546B-54C3-088B1B5E3BBB,Conditions +{2 conditions} + +16D53E40-546B-54C3-088B1B5E3BBB,Text,subst +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 + +17D8BA8E-5992-AA5C-F5ECB73A3433,Action +{Uninstall Actions} + +17D8BA8E-5992-AA5C-F5ECB73A3433,Conditions +{0 conditions} + +18C00430-D6B1-151F-307762B3A045,CheckCondition +{Before Action is Executed} + +18C00430-D6B1-151F-307762B3A045,Platform +Windows + +198905FB-9FAC-23DE-7422D25B8ECA,Alias +{Install Actions} + +198905FB-9FAC-23DE-7422D25B8ECA,Conditions +{0 conditions} + +19ADBDDB-1690-4A57-913E32A026C4,Conditions +{0 conditions} + +19ADBDDB-1690-4A57-913E32A026C4,State +disabled + +19ADBDDB-1690-4A57-913E32A026C4,Widget +{NextButton; CancelButton} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Active +No + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Alias +{Upload File Listing} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Comment +{Upload list of backed up files for Tech Support purposes. May be blocked by firewalls.} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Conditions +{0 conditions} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,FileName +<%ShortAppName%>-program-TebucoSafeQuerypload + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,ShortcutName +{Upload Filelisting to TebucoSafe for review} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,TargetFileName +<%InstallDir%>/tools/TebucoSafeQueryUpload.bat + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,WorkingDirectory +<%InstallDir%> + +1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Active +Yes + +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 + +1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Conditions +{0 conditions} + +1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Message,subst +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 + +1C14291C-0971-4283-92E9-3808401303F5,Comment +{Don't start it yet, need to install keys by hand.} + +1C14291C-0971-4283-92E9-3808401303F5,Conditions +{0 conditions} + +1C14291C-0971-4283-92E9-3808401303F5,ProgramCommandLine +{net start <%ServiceName%>} + +1C14291C-0971-4283-92E9-3808401303F5,WorkingDirectory +<%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 + +1F9E8CB8-02C1-0416-1F7445B4147F,Comment +{Ask the user if they want to proceed with the install.} + +1F9E8CB8-02C1-0416-1F7445B4147F,Conditions +{2 conditions} + +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message,subst +1 + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,CheckCondition +{Before Action is Executed} + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,Operator +false + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,String +<%Answer%> + +2583A547-11DE-1C27-B6D04B023CC0,CheckCondition +{Before Action is Executed} + +2583A547-11DE-1C27-B6D04B023CC0,Operator +false + +2583A547-11DE-1C27-B6D04B023CC0,String +<%SilentMode%> + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Alias +{Remove Backup Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Comment +{Remove the Backup Windows Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Conditions +{0 conditions} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,FileName +<%ShortAppName%>-program-removeService + +25AA533E-02FC-47D9-9273-25266B8FA1F9,ShortcutName +{Remove Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,TargetFileName +<%InstallDir%>/tools/RemoveService.bat + +25AA533E-02FC-47D9-9273-25266B8FA1F9,WorkingDirectory +<%InstallDir%> + +28E76C8B-2605-4739-9FFE-9C2880C17E59,Active +No + +28E76C8B-2605-4739-9FFE-9C2880C17E59,Conditions +{0 conditions} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,ProgramCommandLine +{notepad <%ConfigFileName%>} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,WorkingDirectory +<%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 + +2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Conditions +{1 condition} + +2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Message,subst +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 + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,Conditions +{0 conditions} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,ExecuteAction +{Before Next Pane is Displayed} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,ResultVirtualText +AddBackupLocation + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,TclScript +{set AddBackupLocation no} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message,subst +1 + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Conditions +{0 conditions} + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text,subst +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 + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Comment +{.conf doesn't exist yet} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Conditions +{0 conditions} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,FileOpenAction +{Append to file} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Files +<%ConfigFileTemplate%> + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite,subst +1 + +32B08FB1-99DF-234E-8BAF-333E80AAC9F5,Conditions +{0 conditions} + +32B08FB1-99DF-234E-8BAF-333E80AAC9F5,FileName +<%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%> + +32DC8FB1-A04B-71AA-EC18496D4BD0,Conditions +{0 conditions} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,CheckCondition +{Before Action is Executed} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Message,subst +1 + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Title,subst +1 + +32F5B0AF-EB83-7A03-D8FAE1ECE473,TrueValue +No + +3379F80B-36D6-73DC-6FC1D6223A26,CheckCondition +{Before Action is Executed} + +3379F80B-36D6-73DC-6FC1D6223A26,Operator +false + +3379F80B-36D6-73DC-6FC1D6223A26,String +<%InstallStopped%> + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Active +No + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Conditions +{0 conditions} + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,ResultVirtualText +BackupLocationExclusions + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,TclScript +{set BackupLocationExclusions ""} + +37E627F2-E04B-AEF2-D566C017A4D6,BackButton,subst +1 + +37E627F2-E04B-AEF2-D566C017A4D6,CancelButton,subst +1 + +37E627F2-E04B-AEF2-D566C017A4D6,Caption,subst +1 + +37E627F2-E04B-AEF2-D566C017A4D6,Conditions +{0 conditions} + +37E627F2-E04B-AEF2-D566C017A4D6,FileLabel,subst +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 + +39270FD8-932E-6132-7EF795ED9B93,Alias +{Finish Actions} + +39270FD8-932E-6132-7EF795ED9B93,Conditions +{0 conditions} + +39B2B666-78D8-75E6-6EA071594D34,Conditions +{1 condition} + +39B2B666-78D8-75E6-6EA071594D34,ShortcutName +{Uninstall <%BrandName%>} + +39B2B666-78D8-75E6-6EA071594D34,TargetFileName +<%Uninstaller%> + +39B2B666-78D8-75E6-6EA071594D34,WorkingDirectory +<%InstallDir%> + +39D7394E-04E9-CA70-0034DB830BFE,Conditions +{0 conditions} + +3B6E2E7C-1A26-27F1-D578E383B128,Background +white + +3B6E2E7C-1A26-27F1-D578E383B128,Conditions +{2 conditions} + +3B6E2E7C-1A26-27F1-D578E383B128,Text,subst +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 + +3B8CDC8E-1239-D2E9-DF4CA6B1756D,Conditions +{0 conditions} + +3B8CDC8E-1239-D2E9-DF4CA6B1756D,FileValue,subst +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 + +3C7130B3-3206-403D-B09E-59D4A758FBAD,Action +{Uninstall Actions} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,Conditions +{0 conditions} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,IgnoreErrors +Yes + +3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgramCommandLine +{net stop <%ServiceName%>} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgressiveOutputWidget +Message + +3CFFF099-6122-46DD-9CE4-F5819434AC53,WorkingDirectory +<%Temp%> + +3D0D1898-4C65-3E66-F82F56581E87,CheckCondition +{Before Action is Executed} + +3D0D1898-4C65-3E66-F82F56581E87,Operator +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 + +3D33AA8C-0037-204B-39A339FD38BD,Conditions +{0 conditions} + +3D33AA8C-0037-204B-39A339FD38BD,Message,subst +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 + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Conditions +{0 conditions} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message,subst +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 + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Conditions +{0 conditions} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,LabelSide +left + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Text,subst +1 + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Type +{browse entry} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,VirtualText +BackupLocationPath + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Y +70 + +3FE82C17-A3E2-4A57-A563-F80818B00B81,Default +Yes + +3FE82C17-A3E2-4A57-A563-F80818B00B81,Prompt +<%InstallStartupText%> + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,Conditions +{0 conditions} + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,Files +{*/*.conf;*/*.txt;*/*.pem;*/*.raw;*/*.exe;*/*.bat;*/*.dll} + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,RenameFiles +Yes + +41D3E165-C263-5F80-0FEEC0AEE47A,BackButton,subst +1 + +41D3E165-C263-5F80-0FEEC0AEE47A,CancelButton,subst +1 + +41D3E165-C263-5F80-0FEEC0AEE47A,Caption,subst +1 + +41D3E165-C263-5F80-0FEEC0AEE47A,Conditions +{1 condition} + +41D3E165-C263-5F80-0FEEC0AEE47A,Message,subst +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 +15288767 + +481451CC-F49C-D389-8645076F595B,Name +{Program Files} + +49E59F91-27F7-46D1-A1C1-19865C2392D3,Default +Yes + +49E59F91-27F7-46D1-A1C1-19865C2392D3,Prompt +<%UninstallStartupText%> + +49E80443-62DB-1C10-392D-1091AEA5ED88,Conditions +{1 condition} + +49E80443-62DB-1C10-392D-1091AEA5ED88,ExecuteAction +{Before Next Pane is Displayed} + +49E80443-62DB-1C10-392D-1091AEA5ED88,Pane +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 + +4A9C852B-647E-EED5-5482FFBCC2AF,Name +{Default Component} + +4A9C852B-647E-EED5-5482FFBCC2AF,RequiredComponent +Yes + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,CheckCondition +{Before Action is Executed} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message,subst +1 + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title,subst +1 + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,TrueValue +No + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,Conditions +{0 conditions} + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFilePercentage +Yes + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFileText +Yes + +4E643D8A-CA31-018D-57D7053C2CE8,CheckCondition +{Before Action is Executed} + +4E643D8A-CA31-018D-57D7053C2CE8,Filename +<%ProgramExecutable%> + +4EE35849-FAD7-170B-0E45-FA30636467B1,CheckCondition +{Before Next Pane is Displayed} + +4EE35849-FAD7-170B-0E45-FA30636467B1,EncryptedPassword +<%InstallPasswordEncrypted%> + +4EE35849-FAD7-170B-0E45-FA30636467B1,FailureFocus +{Password Entry} + +4EE35849-FAD7-170B-0E45-FA30636467B1,FailureMessage +<%PasswordIncorrectText%> + +4EE35849-FAD7-170B-0E45-FA30636467B1,UnencryptedPassword +<%InstallPassword%> + +4EF787E3-0643-DE46-15E64BAF0816,CheckCondition +{Before Action is Executed} + +4EF787E3-0643-DE46-15E64BAF0816,Platform +Windows + +52F0A238-57E1-A578-2CE4DA177B32,Conditions +{0 conditions} + +53588803-6B41-D9FC-A385906A5106,Conditions +{0 conditions} + +574198A7-7322-2F5E-02EF185D965C,BackButton,subst +1 + +574198A7-7322-2F5E-02EF185D965C,CancelButton,subst +1 + +574198A7-7322-2F5E-02EF185D965C,Caption,subst +1 + +574198A7-7322-2F5E-02EF185D965C,Conditions +{0 conditions} + +574198A7-7322-2F5E-02EF185D965C,FileLabel,subst +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 + +58E1119F-639E-17C9-5D3898F385AA,Conditions +{1 condition} + +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 + +592F46AE-8CEE-01F3-0BA7EBDCA4F4,CheckCondition +{Before Action is Executed} + +592F46AE-8CEE-01F3-0BA7EBDCA4F4,Filename +<%ProgramExecutable%> + +5CA3EA16-E37C-AABE-E576C4636EB0,Action +{Install Actions} + +5CA3EA16-E37C-AABE-E576C4636EB0,Conditions +{0 conditions} + +5EE78EF7-37CA-D440-3DB5-09136CD566B3,CheckCondition +{Before Action is Executed} + +5EE78EF7-37CA-D440-3DB5-09136CD566B3,String +<%AddBackupLocation%> + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Conditions +{0 conditions} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Files +{<%ConfigFileTemplate%>;*/*.bat;<%InstallDir%>/*.vbs} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,LineFeed +Windows + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,StringMap +{"@@CUSTOMERCOMPANY@@" <%UserInfoCompany%> +"@@BRANDNAME@@" <%BrandName%> +"@@SERVICENAME@@" <%ServiceName%> +"@@CUSTOMERUSERNAME@@" <%UserInfoName%> +"@@CUSTOMERUSERPHONE@@" <%UserInfoPhone%> +"@@CUSTOMERUSEREMAIL@@" <%UserInfoEmail%> +"@@ACCTNO@@" <%UserInfoAcctNo%> +"@@INSTALLDIR@@" <%InstallDir%>} + +614C45B2-7515-780C-E444-7F165CF02DD7,Active +No + +614C45B2-7515-780C-E444-7F165CF02DD7,Conditions +{0 conditions} + +614C45B2-7515-780C-E444-7F165CF02DD7,ResultVirtualText +BackupLocationShortName + +614C45B2-7515-780C-E444-7F165CF02DD7,TclScript +{set BackupLocationShortName ""} + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Active +No + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Comment +{PJ removed. Is this the one at the top leve?} + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Conditions +{0 conditions} + +69FD7409-5E2A-143B-DABD1C3B1E67,Conditions +{2 conditions} + +6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1,ExitType +Finish + +6B966959-05D9-DB32-8D9C4AD2A3DF,CheckCondition +{Before Action is Executed} + +6B966959-05D9-DB32-8D9C4AD2A3DF,Platform +Windows + +6C323815-B9AB-FA94-4F5D152EBC51,BackButton,subst +1 + +6C323815-B9AB-FA94-4F5D152EBC51,CancelButton,subst +1 + +6C323815-B9AB-FA94-4F5D152EBC51,Caption,subst +1 + +6C323815-B9AB-FA94-4F5D152EBC51,Conditions +{0 conditions} + +6C323815-B9AB-FA94-4F5D152EBC51,Message,subst +1 + +6C323815-B9AB-FA94-4F5D152EBC51,NextButton,subst +1 + +6D9D1ABC-7146-443F-9EE9-205D5CA6C830,CheckCondition +{Before Action is Executed} + +6D9D1ABC-7146-443F-9EE9-205D5CA6C830,String +{<%Property <%CurrentPane%> UserMustAcceptLicense%>} + +6E70FB1F-6A43-6C23-3242E965A0D0,Action +{Install Actions} + +6E70FB1F-6A43-6C23-3242E965A0D0,Conditions +{0 conditions} + +6F61CDA8-30C9-454F-82A3-9987E1203079,Alias +{Start a sync now.} + +6F61CDA8-30C9-454F-82A3-9987E1203079,Conditions +{0 conditions} + +6F61CDA8-30C9-454F-82A3-9987E1203079,FileName +<%ShortAppName%>-program-sync + +6F61CDA8-30C9-454F-82A3-9987E1203079,ShortcutName +{Sync now} + +6F61CDA8-30C9-454F-82A3-9987E1203079,TargetFileName +<%InstallDir%>/tools/Sync.bat + +6F61CDA8-30C9-454F-82A3-9987E1203079,WorkingDirectory +<%InstallDir%> + +6F94698F-0839-3ABF-0CF2DF05A4C8,CheckCondition +{Before Action is Executed} + +6F94698F-0839-3ABF-0CF2DF05A4C8,String +<%CreateQuickLaunchShortcut%> + +6FEE2889-0338-1D49-60BF-1471F465AB26,AppendNewline +No + +6FEE2889-0338-1D49-60BF-1471F465AB26,Comment +{Closing final BackupLocations bracket} + +6FEE2889-0338-1D49-60BF-1471F465AB26,Conditions +{0 conditions} + +6FEE2889-0338-1D49-60BF-1471F465AB26,FileOpenAction +{Append to file} + +6FEE2889-0338-1D49-60BF-1471F465AB26,Files +<%ConfigFileTemplate%> + +6FEE2889-0338-1D49-60BF-1471F465AB26,LineFeed +Windows + +6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite,subst +1 + +738DD098-7E3B-BC89-875CDB93CBE2,CheckCondition +{Before Action is Executed} + +738DD098-7E3B-BC89-875CDB93CBE2,Platform +Windows + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Conditions +{0 conditions} + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Destination +<%ConfigFileName%> + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Source +<%ConfigFileTemplate%> + +73EA65C1-3BE3-B190-55C3E99F6269,Conditions +{1 condition} + +748D673B-DFE6-5F74-329903ACE4DB,CheckCondition +{Before Action is Executed} + +748D673B-DFE6-5F74-329903ACE4DB,Filename +<%ProgramExecutable%> + +793D8178-0F51-7F07-BC5886586D3C,CheckCondition +{Before Action is Executed} + +793D8178-0F51-7F07-BC5886586D3C,Operator +false + +793D8178-0F51-7F07-BC5886586D3C,String +<%InstallStopped%> + +795EE61F-6C0D-4A8B-93E02AA3894A,CheckCondition +{Before Action is Executed} + +795EE61F-6C0D-4A8B-93E02AA3894A,String +<%LaunchApplication%> + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,CheckCondition +{Before Action is Executed} + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,Operator +false + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,String +<%LicenseAccepted%> + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,Conditions +{0 conditions} + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,ProgramCommandLine +{ServiceControl terminate} + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,WorkingDirectory +<%InstallDir%> + +7B770A07-A785-5215-956FA82CF14E,Active +No + +7B770A07-A785-5215-956FA82CF14E,Conditions +{3 conditions} + +7B770A07-A785-5215-956FA82CF14E,ShortcutDirectory +<%QUICK_LAUNCH%> + +7B770A07-A785-5215-956FA82CF14E,ShortcutName +<%BrandName%> + +7B770A07-A785-5215-956FA82CF14E,TargetFileName +<%ProgramExecutable%> + +7B770A07-A785-5215-956FA82CF14E,WorkingDirectory +<%InstallDir%> + +7D8E1902-2BC4-80D8-2C18771E7C22,Conditions +{0 conditions} + +7D8E1902-2BC4-80D8-2C18771E7C22,ProgramCommandLine +{<%ServiceExeName%> -i -S <%ServiceName%> -c "<%ConfigFileName%>"} + +7D8E1902-2BC4-80D8-2C18771E7C22,ProgressiveOutputWidget +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 + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Conditions +{0 conditions} + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text,subst +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 + +848844B5-6103-9343-8B731B0BE4E0,Alias +{Startup Actions} + +848844B5-6103-9343-8B731B0BE4E0,Conditions +{0 conditions} + +84DA7F05-9FB7-CC36-9EC98F8A6826,CheckCondition +{Before Next Pane is Displayed} + +84DA7F05-9FB7-CC36-9EC98F8A6826,FailureMessage +<%DirectoryPermissionText%> + +84DA7F05-9FB7-CC36-9EC98F8A6826,Filename +<%InstallDir%> + +84DA7F05-9FB7-CC36-9EC98F8A6826,Permission +{can create} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Conditions +{0 conditions} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Height +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 + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,CheckCondition +{Before Action is Executed} + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,Operator +false + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,String +<%Answer%> + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Active +No + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Conditions +{1 condition} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,ExecuteAction +{After Pane is Finished} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Pane +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 + +8A761DBD-0640-D98C-9B3AD7672A8F,Conditions +{0 conditions} + +8A761DBD-0640-D98C-9B3AD7672A8F,State +disabled + +8A761DBD-0640-D98C-9B3AD7672A8F,Widget +{Back Button;Next Button} + +8C866252-8760-9B08-FE569C25B60D,CheckCondition +{Before Action is Executed} + +8C866252-8760-9B08-FE569C25B60D,Filename +<%ProgramExecutable%> + +8E095096-F018-A880-429D-A2177A9B70EA,Active +No + +8E095096-F018-A880-429D-A2177A9B70EA,Conditions +{0 conditions} + +8E095096-F018-A880-429D-A2177A9B70EA,Text,subst +1 + +8E095096-F018-A880-429D-A2177A9B70EA,X +50 + +8E095096-F018-A880-429D-A2177A9B70EA,Y +150 + +8E1A5944-5AF5-5906-16D395E386D8,Conditions +{0 conditions} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Active +Yes + +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 + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Conditions +{0 conditions} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Message,subst +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 + +905DA2E9-988C-2F27-BB1F5F274AC9,Alias +{Cancel Actions} + +905DA2E9-988C-2F27-BB1F5F274AC9,Conditions +{0 conditions} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,BackButton,subst +1 + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,CancelButton,subst +1 + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Caption,subst +1 + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Conditions +{0 conditions} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Message,subst +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 + +937C3FDD-FB28-98BD-3DAB276E59ED,Conditions +{3 conditions} + +937C3FDD-FB28-98BD-3DAB276E59ED,Text,subst +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 + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Comment +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Conditions +{0 conditions} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,ResultVirtualText +BackupLocationName + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,TclScript +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +96A68CAC-9ED7-806C-086B104720FD,CheckCondition +{Before Action is Executed} + +96A68CAC-9ED7-806C-086B104720FD,String +<%ErrorsOccurred%> + +97ACF525-C075-8635-E019202A83D8,Comment +{Ask the user if they want to proceed with the uninstall.} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Active +No + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Conditions +{0 conditions} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,ResultVirtualText +AddBackupLocation + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,TclScript +{set AddBackupLocation no} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Active +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 + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Conditions +{0 conditions} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message,subst +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 + +9A663209-495B-ED16-09BE-457B61148022,Conditions +{0 conditions} + +9A663209-495B-ED16-09BE-457B61148022,FileName +<%ShortAppName%>-program-QueryCurrent + +9A663209-495B-ED16-09BE-457B61148022,ShortcutName +{Query Current Only} + +9A663209-495B-ED16-09BE-457B61148022,TargetFileName +<%InstallDir%>/tools/QueryOutputCurrent.bat + +9A663209-495B-ED16-09BE-457B61148022,WorkingDirectory +<%InstallDir%> + +9D101299-B80C-441B-8685-6E3AC61808E8,Alias +{Remote Control} + +9D101299-B80C-441B-8685-6E3AC61808E8,Comment +{Get tech support via remote control} + +9D101299-B80C-441B-8685-6E3AC61808E8,Conditions +{0 conditions} + +9D101299-B80C-441B-8685-6E3AC61808E8,FileName +<%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%> + +A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Alias +{Startup Actions} + +A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Conditions +{0 conditions} + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,Conditions +{0 conditions} + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,ResultVirtualText +AddBackupLocation + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,TclScript +{set AddBackupLocation no } + +A6E1B027-A1B4-5848-4F868D028D00,CheckCondition +{Before Action is Executed} + +A6E1B027-A1B4-5848-4F868D028D00,String +<%ViewReadme%> + +ADA6EB2F-8820-4366-BBEF-ED1335B7F828,Conditions +{1 condition} + +AE3BD5B4-35DE-4240-B79914D43E56,Active +No + +AE3BD5B4-35DE-4240-B79914D43E56,BackButton,subst +1 + +AE3BD5B4-35DE-4240-B79914D43E56,CancelButton,subst +1 + +AE3BD5B4-35DE-4240-B79914D43E56,Caption,subst +1 + +AE3BD5B4-35DE-4240-B79914D43E56,Conditions +{0 conditions} + +AE3BD5B4-35DE-4240-B79914D43E56,Message,subst +1 + +AE3BD5B4-35DE-4240-B79914D43E56,NextButton,subst +1 + +AIX-ppc,Active +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 + +AIX-ppc,ProgramExecutable +{} + +AIX-ppc,ProgramFolderAllUsers +No + +AIX-ppc,ProgramFolderName +<%AppName%> + +AIX-ppc,ProgramLicense +<%InstallDir%>/LICENSE.txt + +AIX-ppc,ProgramName +{} + +AIX-ppc,ProgramReadme +<%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 + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Alias +{Stop Backup Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Comment +{Stop the Backup Windows Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Conditions +{0 conditions} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,FileName +<%ShortAppName%>-program-stopservice + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,ShortcutName +{Stop Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,TargetFileName +<%InstallDir%>/tools/StopService.bat + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,WorkingDirectory +<%InstallDir%> + +B0AA6839-AAB6-A602-C0E4ECA2E4FF,CheckCondition +{Before Action is Executed} + +B0AA6839-AAB6-A602-C0E4ECA2E4FF,Filename +<%ProgramExecutable%> + +B39C0455-D1B6-7DDC-E2717F83463E,CheckCondition +{Before Action is Executed} + +B39C0455-D1B6-7DDC-E2717F83463E,Operator +false + +B39C0455-D1B6-7DDC-E2717F83463E,String +<%InstallStopped%> + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Conditions +{0 conditions} + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,ExecuteAction +{Before Next Pane is Displayed} + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Password +<%InstallPassword%> + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,Conditions +{0 conditions} + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,ProgramCommandLine +{<%ServiceExeName%> -r -S <%ServiceName%>} + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,WorkingDirectory +<%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 + +B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Conditions +{0 conditions} + +B506E7DA-E7C4-4D42-8C03-FD27BA16D078,DeclineRadiobutton,subst +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 + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Alias +{Restart Backup Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Comment +{Stop and restart the Backup Windows Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Conditions +{0 conditions} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,FileName +<%ShortAppName%>-program-restartservice + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,ShortcutName +{Restart Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,TargetFileName +{<%InstallDir%>\tools\RestartService.bat} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,WorkingDirectory +<%InstallDir%> + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Conditions +{0 conditions} + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,LabelSide +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 + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Conditions +{2 conditions} + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,State +disabled + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Widget +NextButton + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,CheckCondition +{Before Next Action is Executed} + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,FailureMessage +<%DirectoryPermissionText%> + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Filename +<%InstallDir%> + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Permission +{can create} + +C0452595-F3EB-43AD-BCA2-661437584636,Alias +{Modify Backup Configuration} + +C0452595-F3EB-43AD-BCA2-661437584636,Comment +{Modify your Backup Configuration} + +C0452595-F3EB-43AD-BCA2-661437584636,Conditions +{0 conditions} + +C0452595-F3EB-43AD-BCA2-661437584636,FileName +<%ShortAppName%>-program-editconfig + +C0452595-F3EB-43AD-BCA2-661437584636,ShortcutName +{Edit Config File} + +C0452595-F3EB-43AD-BCA2-661437584636,TargetFileName +<%InstallDir%>/tools/EditConfig.bat + +C0452595-F3EB-43AD-BCA2-661437584636,WorkingDirectory +<%InstallDir%> + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,Conditions +{0 conditions} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,FileName +<%ShortAppName%>-program-QueryAll + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,ShortcutName +{Query All} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,TargetFileName +<%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 + +C105AAAE-7C16-2C9E-769FE4535B60,Conditions +{3 conditions} + +C105AAAE-7C16-2C9E-769FE4535B60,Message,subst +1 + +C105AAAE-7C16-2C9E-769FE4535B60,TextFile +<%ProgramReadme%> + +C105AAAE-7C16-2C9E-769FE4535B60,Title,subst +1 + +C33D74B2-26FA-16F5-433A10C6A747,Active +No + +C33D74B2-26FA-16F5-433A10C6A747,Conditions +{3 conditions} + +C33D74B2-26FA-16F5-433A10C6A747,ProgramCommandLine +<%ProgramExecutable%> + +C33D74B2-26FA-16F5-433A10C6A747,WaitForProgram +No + +C33D74B2-26FA-16F5-433A10C6A747,WorkingDirectory +<%InstallDir%> + +C7762473-273F-E3CA-17E3-65789B14CDB0,Conditions +{0 conditions} + +C7762473-273F-E3CA-17E3-65789B14CDB0,ExecuteAction +{Before Next Pane is Displayed} + +C7762473-273F-E3CA-17E3-65789B14CDB0,FileOpenAction +{Append to file} + +C7762473-273F-E3CA-17E3-65789B14CDB0,Files +<%ConfigFileTemplate%> + +C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite,subst +1 + +CC4337CC-F3B5-757C-DFCF5D1D365A,CheckCondition +{Before Action is Executed} + +CC4337CC-F3B5-757C-DFCF5D1D365A,Operator +false + +CC4337CC-F3B5-757C-DFCF5D1D365A,String +<%SilentMode%> + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Alias +{Start Backup Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Comment +{Start Backup Windows Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Conditions +{0 conditions} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,FileName +<%ShortAppName%>-program-startservice + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,ShortcutName +{Start Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,TargetFileName +{<%InstallDir%>\tools\StartService.bat} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,WorkingDirectory +<%InstallDir%> + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Background +white + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Conditions +{2 conditions} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text,subst +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 + +D23DD94C-E517-7F34-FD59-802CB18AB887,Comment +{Need to do before starting anything else.} + +D23DD94C-E517-7F34-FD59-802CB18AB887,Conditions +{0 conditions} + +D23DD94C-E517-7F34-FD59-802CB18AB887,Files +{*/*.conf;*/*.txt;*/*.bat} + +D23DD94C-E517-7F34-FD59-802CB18AB887,LineFeed +Windows + +D3D73C76-D9D3-07DA-63D4163A44BE,Conditions +{0 conditions} + +D3D73C76-D9D3-07DA-63D4163A44BE,ExitType +Finish + +D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B,Action +{Install Actions} + +D55BA4AF-E73B-60D1-E26F79175227,Action +{Uninstall Actions} + +D55BA4AF-E73B-60D1-E26F79175227,Conditions +{0 conditions} + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,Conditions +{0 conditions} + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,ResultVirtualText +BackupLocationNumber + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,TclScript +{incr BackupLocationNumber} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Alias +{Reload Configuration File, after editing it.} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Conditions +{0 conditions} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,FileName +<%ShortAppName%>-program-reloadconfig + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,ShortcutName +{Reload configuration file} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,TargetFileName +<%InstallDir%>/tools/ReloadConfig.bat + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,WorkingDirectory +<%InstallDir%> + +D8F0AA0F-AD79-C566-15CC508F503B,Action +{Install Actions} + +D8F0AA0F-AD79-C566-15CC508F503B,Conditions +{0 conditions} + +D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Conditions +{0 conditions} + +D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Destination +<%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 + +DA33B826-E633-A845-4646-76DFA78B907B,Conditions +{0 conditions} + +DA33B826-E633-A845-4646-76DFA78B907B,Message,subst +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 + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Conditions +{0 conditions} + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Text,subst +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 + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Alias +{Install Backup Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Comment +{Install the Backup Windows Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Conditions +{0 conditions} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,FileName +<%ShortAppName%>-program-installService + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,ShortcutName +{Install Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,TargetFileName +<%InstallDir%>/tools/InstallService.bat + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory +<%InstallDir%> + +DECC120D-6904-7F17-45A49184A5A3,Active +No + +DECC120D-6904-7F17-45A49184A5A3,Conditions +{2 conditions} + +DECC120D-6904-7F17-45A49184A5A3,ShortcutName +<%AppName%> + +DECC120D-6904-7F17-45A49184A5A3,TargetFileName +<%ProgramExecutable%> + +DECC120D-6904-7F17-45A49184A5A3,WorkingDirectory +<%InstallDir%> + +DFFF91A9-2CA5-6ABE-8474D814AF88,CheckCondition +{Before Action is Executed} + +DFFF91A9-2CA5-6ABE-8474D814AF88,Operator +false + +DFFF91A9-2CA5-6ABE-8474D814AF88,String +<%SilentMode%> + +E02368C5-95B5-03A7-3282740037B0,CheckCondition +{Before Action is Executed} + +E02368C5-95B5-03A7-3282740037B0,Operator +false + +E02368C5-95B5-03A7-3282740037B0,String +<%InstallStopped%> + +E161F216-E597-B340-C1A71C476E2C,CheckCondition +{Before Action is Executed} + +E161F216-E597-B340-C1A71C476E2C,Message,subst +1 + +E161F216-E597-B340-C1A71C476E2C,Title,subst +1 + +E23AC50D-7CFB-800E-A99C6F4068F8,Alias +{Cancel Actions} + +E23AC50D-7CFB-800E-A99C6F4068F8,Conditions +{0 conditions} + +E44CFF46-6302-C518-B9C30D2E43F7,CheckCondition +{Before Action is Executed} + +E44CFF46-6302-C518-B9C30D2E43F7,String +<%CreateDesktopShortcut%> + +E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,Conditions +{0 conditions} + +E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,ProgramCommandLine +{net stop <%ServiceName%>} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Conditions +{0 conditions} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Destination +{<%InstallDir%>\templates\NotifySysAdmin.original.vbs} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Source +{<%InstallDir%>\templates\NotifySysAdmin.template.vbs} + +EB2B31A1-C111-3582-0C8A5656692A,String +<%ErrorsOccurred%> + +EB532611-5F30-3C24-66EB-F3826D9054FD,CheckCondition +{Before Action is Executed} + +EB532611-5F30-3C24-66EB-F3826D9054FD,String +<%AddBackupLocation%> + +F4024A3E-9A6D-2726-5E0CFFA93054,Alias +{Uninstall Actions} + +F4024A3E-9A6D-2726-5E0CFFA93054,Conditions +{0 conditions} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,Active +No + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,Conditions +{0 conditions} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgramCommandLine +{cmd /k tools/7za.exe encrypted_keys.exe -p<%InstallPassword%>} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgressiveOutputWidget +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 + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Conditions +{0 conditions} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Message,subst +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 + +F9E38720-6ABA-8B99-2471-496902E4CBC2,Conditions +{0 conditions} + +F9E38720-6ABA-8B99-2471-496902E4CBC2,ResultVirtualText +BackupLocationPath + +F9E38720-6ABA-8B99-2471-496902E4CBC2,TclScript +{set BackupLocationPath "" } + +FB697A88-2842-468E-9776-85E84B009340,Active +No + +FB697A88-2842-468E-9776-85E84B009340,Conditions +{0 conditions} + +FB697A88-2842-468E-9776-85E84B009340,IgnoreErrors +Yes + +FB697A88-2842-468E-9776-85E84B009340,ProgramCommandLine +{"<%InstallDir%>\<%ServiceExeName%> -r -S <%ServiceName%>} + +FB697A88-2842-468E-9776-85E84B009340,WorkingDirectory +<%Temp%> + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Active +No + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Conditions +{0 conditions} + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,WaitTime +2000 + +FEFD090D-C133-BC95-B3564F693CD3,Alias +{Finish Actions} + +FEFD090D-C133-BC95-B3564F693CD3,Conditions +{0 conditions} + +FreeBSD-4-x86,Active +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 + +FreeBSD-4-x86,ProgramExecutable +{} + +FreeBSD-4-x86,ProgramFolderAllUsers +No + +FreeBSD-4-x86,ProgramFolderName +<%AppName%> + +FreeBSD-4-x86,ProgramLicense +<%InstallDir%>/LICENSE.txt + +FreeBSD-4-x86,ProgramName +{} + +FreeBSD-4-x86,ProgramReadme +<%InstallDir%>/README.txt + +FreeBSD-4-x86,PromptForRoot +Yes + +FreeBSD-4-x86,RequireRoot +No + +FreeBSD-4-x86,RootInstallDir +/usr/local/<%ShortAppName%> + +FreeBSD-x86,Active +No + +FreeBSD-x86,DefaultDirectoryPermission +0755 + +FreeBSD-x86,DefaultFilePermission +0755 + +FreeBSD-x86,Executable +<%AppName%>-<%Version%>-<%Platform%>-Install<%Ext%> + +FreeBSD-x86,FallBackToConsole +Yes + +FreeBSD-x86,InstallDir +<%Home%>/<%ShortAppName%> + +FreeBSD-x86,InstallMode +Standard + +FreeBSD-x86,InstallType +Typical + +FreeBSD-x86,ProgramExecutable +{} + +FreeBSD-x86,ProgramFolderAllUsers +No + +FreeBSD-x86,ProgramFolderName +<%AppName%> + +FreeBSD-x86,ProgramLicense +<%InstallDir%>/LICENSE.txt + +FreeBSD-x86,ProgramName +{} + +FreeBSD-x86,ProgramReadme +<%InstallDir%>/README.txt + +FreeBSD-x86,PromptForRoot +Yes + +FreeBSD-x86,RequireRoot +No + +FreeBSD-x86,RootInstallDir +/usr/local/<%ShortAppName%> + +HPUX-hppa,Active +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 + +HPUX-hppa,ProgramExecutable +{} + +HPUX-hppa,ProgramFolderAllUsers +No + +HPUX-hppa,ProgramFolderName +<%AppName%> + +HPUX-hppa,ProgramLicense +<%InstallDir%>/LICENSE.txt + +HPUX-hppa,ProgramName +{} + +HPUX-hppa,ProgramReadme +<%InstallDir%>/README.txt + +HPUX-hppa,PromptForRoot +Yes + +HPUX-hppa,RequireRoot +No + +HPUX-hppa,RootInstallDir +/usr/local/<%ShortAppName%> + +Linux-x86,Active +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 + +Linux-x86,ProgramName +{} + +Linux-x86,ProgramReadme +<%InstallDir%>/README.txt + +Linux-x86,PromptForRoot +Yes + +Linux-x86,RequireRoot +No + +Linux-x86,RootInstallDir +/usr/local/<%ShortAppName%> + +Solaris-sparc,Active +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 + +Solaris-sparc,ProgramExecutable +{} + +Solaris-sparc,ProgramFolderAllUsers +No + +Solaris-sparc,ProgramFolderName +<%AppName%> + +Solaris-sparc,ProgramLicense +<%InstallDir%>/LICENSE.txt + +Solaris-sparc,ProgramName +{} + +Solaris-sparc,ProgramReadme +<%InstallDir%>/README.txt + +Solaris-sparc,PromptForRoot +Yes + +Solaris-sparc,RequireRoot +No + +Solaris-sparc,RootInstallDir +/usr/local/<%ShortAppName%> + +TarArchive,Active +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 + +TarArchive,ProgramExecutable +{} + +TarArchive,ProgramFolderAllUsers +No + +TarArchive,ProgramFolderName +<%AppName%> + +TarArchive,ProgramLicense +<%InstallDir%>/LICENSE.txt + +TarArchive,ProgramName +{} + +TarArchive,ProgramReadme +<%InstallDir%>/README.txt + +TarArchive,PromptForRoot +Yes + +TarArchive,RequireRoot +No + +TarArchive,RootInstallDir +/usr/local/<%ShortAppName%> + +TarArchive,VirtualTextMap +{<%InstallDir%> <%ShortAppName%>} + +Windows,Active +Yes + +Windows,BuildType +{} + +Windows,Executable +installer.exe + +Windows,IncludeTWAPI +No + +Windows,InstallDir +{C:\Program Files\<%BrandName%>} + +Windows,InstallMode +Standard + +Windows,InstallType +Typical + +Windows,ProgramExecutable +{} + +Windows,ProgramFolderAllUsers +No + +Windows,ProgramFolderName +<%BrandName%> + +Windows,ProgramLicense +{<%InstallDir%>\LICENSE.txt} + +Windows,ProgramName +{} + +Windows,ProgramReadme +{} + +Windows,WindowsIcon +{} + +ZipArchive,Active +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 + +ZipArchive,ProgramExecutable +{} + +ZipArchive,ProgramFolderAllUsers +No + +ZipArchive,ProgramFolderName +<%AppName%> + +ZipArchive,ProgramLicense +<%InstallDir%>/LICENSE.txt + +ZipArchive,ProgramName +{} + +ZipArchive,ProgramReadme +<%InstallDir%>/README.txt + +ZipArchive,PromptForRoot +Yes + +ZipArchive,RequireRoot +No + +ZipArchive,RootInstallDir +/usr/local/<%ShortAppName%> + +ZipArchive,VirtualTextMap +{<%InstallDir%> <%ShortAppName%>} + +} + +::msgcat::mcmset de { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +} +::msgcat::mcmset en { +16D53E40-546B-54C3-088B1B5E3BBB,Text +<%CreateDesktopShortcutText%> + +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text +{No:<%BackupLocationNumber%> } + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite +BackupLocations\n\{\n + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Message +<%InstallStartupText%> + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Title +<%InstallApplicationText%> + +36FF8915-8148-0F1F-27D7239CBFA1,Text +<%ViewReadmeText%> + +3B6E2E7C-1A26-27F1-D578E383B128,Text +<%ViewReadmeText%> + +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.} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption +{} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message +{} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Subtitle +{Add a directory to backup, nickname it, and add some exclusions.} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title +{Backup Locations} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Text +{Backup Directory} + +4A9C852B-647E-EED5-5482FFBCC2AF,Description +<%ProgramFilesDescription%> + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message +<%UninstallStartupText%> + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title +<%UninstallApplicationText%> + +58E1119F-639E-17C9-5D3898F385AA,Caption +{Setup will install <%ShortAppName%> in the following folder. + +To install to this folder, click Next. To install to a different folder, click Browse and select another folder.} + +58E1119F-639E-17C9-5D3898F385AA,Subtitle +{Where should <%ShortAppName%> be installed?} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Caption +{Backup Exclusions} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Message +{} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Subtitle +{Enter your backup exclusions for this Backup Location <%BackupLocationName_ShortName%> here.} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Text +{} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Title +{Backup Exclusions} + +640DA2B2-6CF3-0873-D7AE-ABCDDE39EFCF,Text +{Put your custom text here. +<%AddBackupLocation%> +<%BackupLocationNumber%> +<%BackupLocationName%>} + +6C323815-B9AB-FA94-4F5D152EBC51,Caption +{Installation Wizard Complete} + +6C323815-B9AB-FA94-4F5D152EBC51,Message +{The Installation Wizard has successfully installed the <%BrandName%> Backup Service. Click Finish to exit the wizard.} + +6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Caption +{Installing encryption keys...} + +6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Message +{Click next to install encrypted keys and configuration file (bbackupd.conf)... + +You will be presented with the current configuration file so that you may make any last minute changes...} + +6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite +\}\n + +8202CECC-54A0-9B6C-D24D111BA52E,Description +<%TypicalInstallDescription%> + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text +{Add another backup location?} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Text +Exclusions + +8E095096-F018-A880-429D-A2177A9B70EA,Text +{AddAnother: <%AddBackupLocation%>} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Caption +{Please enter your phone number and email address.} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel +Email: + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel +Phone: + +937C3FDD-FB28-98BD-3DAB276E59ED,Text +<%CreateQuickLaunchShortcutText%> + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption +{Click Next to continue...} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message +{Building configuration file...} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Subtitle +{} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title +Continue + +9BAB328D-414B-D351-CA8D-824DF94B9DCA,Text +{Add another backup location after this one?} + +A18C2977-1409-C1FB-892415711F72,Text +<%LaunchApplicationText%> + +AAF2142A-9FC9-4664-DFF2-13B9EB7BA0E1,CompanyLabel +Company: + +AE3BD5B4-35DE-4240-B79914D43E56,Caption +{Welcome to the Installation Wizard for <%BrandName%> Backup Service!} + +AE3BD5B4-35DE-4240-B79914D43E56,Message +{Thank you for installing the <%BrandName%>(SM) Backup Service. + +If you need any assistance, please contact us at http://<%BrandName%>.com or support@<%BrandName%>.com. + +This will install <%BrandName%> version <%Version%> on your computer. + +It is recommended that you close all other applications before continuing. + +Click Next to continue or Cancel to exit Setup. +} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4404713-AF4F-4F4B-670F3115517F,Description +<%CustomInstallDescription%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Text +{Box Backup, http://www.fluffy.co.uk/boxbackup +Copyright (c) 2003-2007 Ben Summers and contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. All use of this software and associated advertising materials must display the following acknowledgement: + This product includes software developed by Ben Summers and contributors. + +4. The names of the Authors may not be used to endorse or promote products derived from this software without specific prior written permission. + +[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} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Caption +{<%BrandName%> will backup the following folder. + +To backup this folder, click Next. To backup a different folder, click Browse and select another folder.} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,DestinationLabel +{Backup This Folder} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Message +{} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Subtitle +{What directory should <%BrandName%> backup?} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Title +{Choose Backup Location} + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text +{Short Name} + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Caption +{Please enter the agreed-upon password for your encrypted key file...} + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,CompanyLabel +Password: + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Subtitle +{Please enter your encrypted key file password.} + +C105AAAE-7C16-2C9E-769FE4535B60,Caption +<%ApplicationReadmeText%> + +C105AAAE-7C16-2C9E-769FE4535B60,Message +{} + +C105AAAE-7C16-2C9E-769FE4535B60,Title +<%ApplicationReadmeText%> + +C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite +{<%BackupLocationShortName%> +{ +Path = <%BackupLocationPath%> +<%BackupLocationExclusions%> +} +} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,BrowseText +{To continue, click Next. If you would like to select a folder to backup, click Browse.} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Caption +{} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,DestinationLabel +{Backup This Folder} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Subtitle +{What directories should <%BrandName%> backup?} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Title +{Choose Backup Location} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text +<%LaunchApplicationText%> + +D4625CA6-9864-D8EF-F252D7B7DC87,Text +<%CreateDesktopShortcutText%> + +D47BE952-79F2-844E-D2E5-8F22044E7A9D,Text +{Account Number:} + +DA33B826-E633-A845-4646-76DFA78B907B,Caption +{Click Next to continue...} + +DA33B826-E633-A845-4646-76DFA78B907B,Message +{Completing configuration file...} + +DA33B826-E633-A845-4646-76DFA78B907B,Subtitle +{} + +DA33B826-E633-A845-4646-76DFA78B907B,Title +Continue + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Text +{Add another Backup Location?} + +E0CADC4E-08A6-E429-3B49-BB8CFB7B097F,Text +{Simple name for this Backup Location (short, no spaces or special characters)} + +E161F216-E597-B340-C1A71C476E2C,Message +<%UninstallLeftoverText%> + +E161F216-E597-B340-C1A71C476E2C,Title +{Uninstall <%BrandName%>} + +EA2C57E8-CCFB-CF3D-92CA-83369EFF1B08,Text +{Add (another) Backup Location after this one?} + +EDE364D6-22C7-5108-D398-26FC24E0A55A,Text +{Enter your Backup Exclusions for this Backup Location...} + +F59AF47D-4136-64F8-82C7-4506BD4327FD,Text +{Add another Backup Location after this one?} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption +{Please enter your Account Number.} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel +{Account Number (like 10005004):} + +F98784B1-1965-0F42-6BB0542AE1A9,Caption +{Installing and starting the TebucoSafe Backup Service...} + +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 +<%CreateQuickLaunchShortcutText%> + +FF4F6EEA-F4CC-428E-AF33-EB0E88E2147E,Text +{ +Copyright (c) 2003 - 2006 + Ben Summers and contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All use of this software and associated advertising materials must + display the following acknowledgment: + This product includes software developed by Ben Summers. +4. The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +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. + + + } + +} +::msgcat::mcmset es { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +} +::msgcat::mcmset fr { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +} +::msgcat::mcmset pl { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +} +::msgcat::mcmset pt_br { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +<%UninstallCompleteText%> + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +<%InstallationCompleteText%> + +B002A311-F8E7-41DE-B039-521391924E5B,Message +<%InstallingApplicationText%> + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +<%UninstallingApplicationText%> + +} + diff --git a/contrib/windows/installer/tools/InstallService.bat b/contrib/windows/installer/tools/InstallService.bat new file mode 100755 index 00000000..80a342eb --- /dev/null +++ b/contrib/windows/installer/tools/InstallService.bat @@ -0,0 +1,3 @@ +service.exe -i -S GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/KillBackupProcess.bat b/contrib/windows/installer/tools/KillBackupProcess.bat new file mode 100755 index 00000000..416d4a79 --- /dev/null +++ b/contrib/windows/installer/tools/KillBackupProcess.bat @@ -0,0 +1,3 @@ +control.exe terminate +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/QueryOutputAll.bat b/contrib/windows/installer/tools/QueryOutputAll.bat new file mode 100755 index 00000000..2ab30a72 --- /dev/null +++ b/contrib/windows/installer/tools/QueryOutputAll.bat @@ -0,0 +1,5 @@ +@ECHO OFF +: o=old, d=deleted, s=size info, t=timestamp, r=recursive +set Queryopts=-odstr +::set Queryopts=-str +query.exe "list %Queryopts%" quit > QueryOutputAllResults.txt diff --git a/contrib/windows/installer/tools/QueryOutputCurrent.bat b/contrib/windows/installer/tools/QueryOutputCurrent.bat new file mode 100755 index 00000000..d59ddbf1 --- /dev/null +++ b/contrib/windows/installer/tools/QueryOutputCurrent.bat @@ -0,0 +1,5 @@ +@ECHO OFF +: o=old, d=deleted, s=size info, t=timestamp, r=recursive +::set Queryopts=-odstr +set Queryopts=-str +query.exe "list %Queryopts%" quit > QueryOutputCurrentResults.txt diff --git a/contrib/windows/installer/tools/ReloadConfig.bat b/contrib/windows/installer/tools/ReloadConfig.bat new file mode 100755 index 00000000..5fd44e83 --- /dev/null +++ b/contrib/windows/installer/tools/ReloadConfig.bat @@ -0,0 +1,3 @@ +control.exe reload +echo off +ping 192.168.254.254 -n 8 -w 1000 > nul diff --git a/contrib/windows/installer/tools/RemoteControl.exe b/contrib/windows/installer/tools/RemoteControl.exe new file mode 100755 index 00000000..f7667421 Binary files /dev/null and b/contrib/windows/installer/tools/RemoteControl.exe differ diff --git a/contrib/windows/installer/tools/RemoveService.bat b/contrib/windows/installer/tools/RemoveService.bat new file mode 100755 index 00000000..881ec5b1 --- /dev/null +++ b/contrib/windows/installer/tools/RemoveService.bat @@ -0,0 +1,3 @@ +@@SERVICEEXENAME@ -r -S GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/RestartService.bat b/contrib/windows/installer/tools/RestartService.bat new file mode 100755 index 00000000..77092a69 --- /dev/null +++ b/contrib/windows/installer/tools/RestartService.bat @@ -0,0 +1,5 @@ +net stop GigaLock +ping 192.168.254.254 -n 2 -w 1000 > nul +net start GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/ShowUsage.bat b/contrib/windows/installer/tools/ShowUsage.bat new file mode 100755 index 00000000..e6f69e9f --- /dev/null +++ b/contrib/windows/installer/tools/ShowUsage.bat @@ -0,0 +1,3 @@ +query.exe usage quit +ping 192.168.254.254 -n 10 -w 1000 > nul + diff --git a/contrib/windows/installer/tools/StartService.bat b/contrib/windows/installer/tools/StartService.bat new file mode 100755 index 00000000..1771238f --- /dev/null +++ b/contrib/windows/installer/tools/StartService.bat @@ -0,0 +1,3 @@ +net start GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/StopService.bat b/contrib/windows/installer/tools/StopService.bat new file mode 100755 index 00000000..8e70d68d --- /dev/null +++ b/contrib/windows/installer/tools/StopService.bat @@ -0,0 +1,3 @@ +net stop GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/Sync.bat b/contrib/windows/installer/tools/Sync.bat new file mode 100755 index 00000000..30a04bec --- /dev/null +++ b/contrib/windows/installer/tools/Sync.bat @@ -0,0 +1,3 @@ +control.exe sync +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/distribution/COMMON-MANIFEST.txt b/distribution/COMMON-MANIFEST.txt new file mode 100644 index 00000000..6753c886 --- /dev/null +++ b/distribution/COMMON-MANIFEST.txt @@ -0,0 +1,47 @@ +LICENSE-DUAL.txt + +LICENSE DUAL + +RUN ./bootstrap +RUN cd docs; make + +lib/common +lib/crypto +lib/server +lib/compress +lib/win32 +test/common +test/common/testfiles +test/basicserver +test/basicserver/testfiles +test/crypto +test/compress +test/win32 +docs/api-notes +docs/api-notes/common +docs/api-notes/common/lib_common +docs/api-notes/common/lib_common.txt +docs/api-notes/common/lib_compress +docs/api-notes/common/lib_crypto +docs/api-notes/common/lib_server +MKDIR infrastructure +infrastructure/buildenv-testmain-template.cpp +infrastructure/makebuildenv.pl.in +infrastructure/makedistribution.pl.in +infrastructure/makeparcels.pl.in +infrastructure/parcelpath.pl +infrastructure/printversion.pl +infrastructure/BoxPlatform.pm.in +infrastructure/mingw +infrastructure/msvc +bootstrap +configure.ac +configure +parcels.txt +runtest.pl.in +COPYING.txt + +LICENSE none +config.sub +config.guess +infrastructure/m4 diff --git a/distribution/boxbackup/CONTACT.txt b/distribution/boxbackup/CONTACT.txt new file mode 100644 index 00000000..9f12e435 --- /dev/null +++ b/distribution/boxbackup/CONTACT.txt @@ -0,0 +1,6 @@ + +http://www.boxbackup.org/ + +Ben Summers & contributors +boxbackup@boxbackup.org + diff --git a/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt new file mode 100644 index 00000000..3677d376 --- /dev/null +++ b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt @@ -0,0 +1,95 @@ +LICENSE-GPL.txt + +LICENSE DUAL +lib/intercept +lib/raidfile +lib/httpserver +test/raidfile +test/raidfile/testfiles +test/httpserver + +LICENSE GPL +lib/backupclient +lib/backupstore +bin/bbstored +bin/bbstoreaccounts +bin/bbackupd +bin/bbackupd/win32 +bin/bbackupquery +bin/bbackupctl +bin/bbackupobjdump +bin/s3simulator +test/backupstore +test/backupstore/testfiles +test/backupstorefix +test/backupstorefix/testfiles +test/backupstorepatch +test/bbackupd +test/bbackupd/testfiles +test/backupdiff +test/httpserver/testfiles +test/httpserver/testfiles/photos +docs/Makefile +docs/tools + +LICENSE DUAL +docs/api-notes +docs/api-notes/raidfile +docs/api-notes/raidfile/lib_raidfile.txt + +LICENSE GPL +docs/api-notes/backup + +docs/images +docs/htmlguide +docs/htmlguide/adminguide +docs/htmlguide/images +docs/htmlguide/instguide +docs/htmlguide/man-html +docs/man +MKDIR docs/docbook +docs/docbook/ExceptionCodes.xml +docs/docbook/adminguide.xml +docs/docbook/bb-book.xsl +docs/docbook/bb-man.xsl +docs/docbook/bb-nochunk-book.xsl +docs/docbook/bbackupctl.xml +docs/docbook/bbackupd-config.xml +docs/docbook/bbackupd.conf.xml +docs/docbook/bbackupd.xml +docs/docbook/bbackupquery.xml +docs/docbook/bbstoreaccounts.xml +docs/docbook/bbstored-certs.xml +docs/docbook/bbstored-config.xml +docs/docbook/bbstored.conf.xml +docs/docbook/bbstored.xml +docs/docbook/instguide.xml +docs/docbook/raidfile-config.xml +docs/docbook/raidfile.conf.xml +docs/docbook/html +docs/docbook/html/images +docs/xsl-generic +docs/xsl-generic/manpages +docs/xsl-generic/lib +docs/xsl-generic/common +docs/xsl-generic/html +docs/xsl-generic/highlighting +BUGS.txt +contrib + +contrib/bbadmin +contrib/bbreporter +contrib/debian +contrib/mac_osx +contrib/redhat +contrib/rpm +REPLACE-VERSION-IN contrib/rpm/boxbackup.spec +contrib/solaris +contrib/suse +contrib/windows +contrib/windows/installer +contrib/windows/installer/tools + +infrastructure/msvc +infrastructure/msvc/2003 +infrastructure/msvc/2005 diff --git a/distribution/boxbackup/DOCUMENTATION.txt b/distribution/boxbackup/DOCUMENTATION.txt new file mode 100644 index 00000000..f45f61d5 --- /dev/null +++ b/distribution/boxbackup/DOCUMENTATION.txt @@ -0,0 +1,6 @@ + +For compilation and installation instructions, see the web site at + + http://www.boxbackup.org/ + + diff --git a/distribution/boxbackup/LINUX.txt b/distribution/boxbackup/LINUX.txt new file mode 100644 index 00000000..d7481811 --- /dev/null +++ b/distribution/boxbackup/LINUX.txt @@ -0,0 +1,29 @@ + +For instructions on building an RPM of Box Backup, see the contrib/rpm +directory. This is primarily for RedHat style systems, but notes are provided +on what needs to be modified for SUSE. + + +Requirements: + + OpenSSL 0.9.7 + +Require zlib and openssl headers for compilation -- may not be included when +installing the packages. (libssl-dev + libz-dev packages under debian) + +Bekerley DB v1 or v4 support is required. The configure script should find an +appropriate db package -- and if not, use an in-memory version of the code. +However, the in-memory version is not desirable as it will lose information +over restarts of the daemon. + +Ideally, use libeditline as a readline replacement. If not available then use +GNU readline (libreadline4-dev, probably) and pass --enable-gnu-readline to +./configure. + + + +(OpenSSL 0.9.7 is required as it implements new interfaces which make encryption +using the same key faster by avoiding key setup each time around. Configure it with +./config shared , then copy the libs and headers into the required places.) + + diff --git a/distribution/boxbackup/NETBSD.txt b/distribution/boxbackup/NETBSD.txt new file mode 100644 index 00000000..03000791 --- /dev/null +++ b/distribution/boxbackup/NETBSD.txt @@ -0,0 +1,8 @@ + +Install perl + +Install OpenSSL 0.9.7 or later. + +Not a completely working port -- symlinks don't get backed up or restored properly. +(run test/bbackupd) + diff --git a/distribution/boxbackup/THANKS.txt b/distribution/boxbackup/THANKS.txt new file mode 100644 index 00000000..09ad60e2 --- /dev/null +++ b/distribution/boxbackup/THANKS.txt @@ -0,0 +1,69 @@ + +The following developers contributed code to version 0.10: + +Nick Knight + - ported Box Backup to Windows (properly, not using Cygwin) + +Gary Niemcewicz + - added client/server (SSL) keepalives to keep the connection to the + server alive during a long diff, and saving the daemon's state across restarts + +Martin Ebourne + - ported to Solaris; wrote extended attribute support (xattr); + converted to use autoconf for automatic compiler configuration. + +Chris Wilson + - updated Nick's and Gary's work to fit in with the new trunk, + fixed some issues pointed out by Ben and Martin, made it compile + on Windows with the free MinGW compiler. + +Jonathan Morton + - vastly improved the performance and efficiency of the file-diffing code, and + obtained a free G5 PowerMac from IBM as his reward. + + +---- + +Many individuals have helped with the development of Box Backup by testing, reporting experiences, and making suggestions. In particular, thanks are due to + +Charles Lecklider + - Helped with the finer details of Win32 programming + +Pascal Lalonde + - Comprehensive and accurate bug reports, and constructive feedback + +Paul Arch + - Cygwin client port + +Ben Lovett + - Help with odd architectures, suggesting small changes + +Martin Ebourne + - RPM specification for RedHat based Linux distributions + - Patch to fix problems on 64 bit architectures + - Patch to fix compilation after RedHat Fedora's latest changes + +Per Thomsen + - Cygwin Windows service install scripts and build notes + - Answering queries on the boxbackup mailing list + +Tim Fletcher +David Harris +Richard Eigenmann + - Testing many attempts at clean compiles on various Linux distributions + +Eduardo Alvarenga + - Valuable feedback and persuasion to include new features + +Joe Gillespie + - Web site design + +Jrme Schell + - Fixes to build+config problems on Linux + +John Pybus + - Ideas and feature requests + - Useful little patches to code + +Stefan Norlin + - Help with testing and fixes on lots of different Solaris platforms diff --git a/distribution/boxbackup/VERSION.txt b/distribution/boxbackup/VERSION.txt new file mode 100644 index 00000000..129b41fa --- /dev/null +++ b/distribution/boxbackup/VERSION.txt @@ -0,0 +1,2 @@ +0.11.1 +boxbackup diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..c4a63671 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,183 @@ +# Process DocBook to HTML + +# This makefile is a bit obfuscated so that it works correctly on both +# BSD and GNU make. Some parts apply to one version of make and not the +# other; these are marked by comments. + +# The "all" target shouldn't be up here, but the trickery below defines +# what looks like a rule to GNU make, and so we need to define the actual +# default target before it. + +all: docs + +DBPROC_COMMAND = xsltproc +MKDIR_COMMAND = mkdir +CP_COMMAND = cp +PERL_COMMAND = perl +RM_COMMAND = rm -f +TAR_COMMAND = tar +GZIP_COMMAND = gzip -f +GENERATE_SCRIPT = tools/generate_except_xml.pl + +DBPROC = $(DBPROC_COMMAND) +MKDIR = $(MKDIR_COMMAND) +CP = $(CP_COMMAND) +GENERATE = $(PERL_COMMAND) $(GENERATE_SCRIPT) +RM_QUIET = $(RM_COMMAND) +TAR = $(TAR_COMMAND) +GZIP = $(GZIP_COMMAND) +PROGRESS = @ true + +# use a GNU make "define" command, that looks like a harmless dummy rule +# to BSD make, to hide parts of the Makefile from GNU make. +define IGNORED_BY_GNU_MAKE: +.if 0 +endef + + # seen by GNU make, not by BSD make + ifeq ($(V),) + DBPROC = @ echo " [XLSTPROC]" $^ && $(DBPROC_COMMAND) 2>/dev/null + GENERATE = @ echo " [GENERATE]" $@ && $(PERL_COMMAND) $(GENERATE_SCRIPT) + TAR = @ echo " [TAR] " $@ && $(TAR_COMMAND) + GZIP = @ echo " [GZIP] " $< && $(GZIP_COMMAND) + RM_QUIET = @ $(RM_COMMAND) + PROGRESS = @ echo + endif + +define IGNORED_BY_GNU_MAKE: +.endif + +.ifndef V + # seen by BSD make, not by GNU make + DBPROC = @ echo " [XSLTPROC]" $(.ALLSRC) && $(DBPROC_COMMAND) 2>/dev/null + GENERATE = @ echo " [GENERATE]" $(.TARGET) && $(PERL_COMMAND) $(GENERATE_SCRIPT) + TAR = @ echo " [TAR] " $(.TARGET) && $(TAR_COMMAND) + GZIP = @ echo " [GZIP] " $(.TARGET:.gz=) && $(GZIP_COMMAND) + RM_QUIET = @ $(RM_COMMAND) + PROGRESS = @ echo +.endif + +# neither .endif nor endef can be followed by a colon; each creates +# warnings or errors in one or other version of make. we need some +# magic to make them both work. Luckily, .endfor ignores the colon. + +.for DUMMY in $(NO_SUCH_VARIABLE) +endef +.endfor : + +PROGRESS_RM = $(PROGRESS) " [RM] " + +DOCBOOK_DIR = docbook +HTML_DIR = htmlguide +MAN_DIR = man + +BOOKXSL = $(DOCBOOK_DIR)/bb-book.xsl +NOCHUNKBOOKXSL = $(DOCBOOK_DIR)/bb-nochunk-book.xsl +MANXSL = $(DOCBOOK_DIR)/bb-man.xsl + +VPATH = $(DOCBOOK_DIR) +.SUFFIXES: .html .xml .gz .1 .5 .8 + +docs: instguide adminguide manpages + @mkdir -p $(HTML_DIR)/images + @cp $(DOCBOOK_DIR)/html/images/*.png $(HTML_DIR)/images/. + @cp $(DOCBOOK_DIR)/html/*.css $(HTML_DIR)/. + @cp $(DOCBOOK_DIR)/html/*.ico $(HTML_DIR)/. + +adminguide: $(DOCBOOK_DIR)/ExceptionCodes.xml $(HTML_DIR)/adminguide/index.html + +# $^ gives all sources on GNU make, and nothing on BSD make +# $> gives all sources on BSD make, and nothing on GNU make +$(HTML_DIR)/adminguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/adminguide.xml + $(DBPROC) -o $(HTML_DIR)/adminguide/ $^ $> + +instguide: $(HTML_DIR)/instguide/index.html + +$(HTML_DIR)/instguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/instguide.xml + $(DBPROC) -o $(HTML_DIR)/instguide/ $^ $> + +# On BSD make, $> contains all sources and $^ is empty +# On GNU make, $^ contains all sources and $> is empty +$(DOCBOOK_DIR)/ExceptionCodes.xml: ../ExceptionCodes.txt + $(GENERATE) $> $^ $@ + +manpages: man-dirs man-nroff man-html + +man-dirs: man/.there $(HTML_DIR)/man-html/.there + +$(HTML_DIR)/man-html/.there: + mkdir -p $(HTML_DIR)/man-html + touch $(HTML_DIR)/man-html/.there + +man/.there: + mkdir -p man + touch man/.there + +NROFF_PAGES = bbackupd.8 bbackupd-config.8 bbackupctl.8 bbackupquery.8 \ + bbstored.8 bbstored-config.8 bbstoreaccounts.8 bbstored-certs.8 \ + raidfile-config.8 \ + bbackupd.conf.5 bbstored.conf.5 raidfile.conf.5 + +NROFF_FILES = $(NROFF_PAGES:%=$(MAN_DIR)/%.gz) + +man-nroff: $(NROFF_FILES) + +HTML_FILES_1 = $(NROFF_PAGES:%.5=%.html) +HTML_FILES_2 = $(HTML_FILES_1:%.8=%.html) +HTML_FILES = $(HTML_FILES_2:%=$(HTML_DIR)/man-html/%) + +man-html: $(HTML_FILES) + +# $^ gives all sources on GNU make, and nothing on BSD make + +# GNU make +$(HTML_DIR)/man-html/%.html: $(NOCHUNKBOOKXSL) $(DOCBOOK_DIR)/%.xml + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.8: $(MANXSL) $(DOCBOOK_DIR)/%.xml + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.8.gz: $(MAN_DIR)/%.8 + $(GZIP) $< + +# GNU make +$(MAN_DIR)/%.5: $(MANXSL) $(DOCBOOK_DIR)/%.xml $(MANXSL) + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.5.gz: $(MAN_DIR)/%.5 + $(GZIP) $< + +# BSD make: the final colon (:) is required to make the .for and .endfor +# lines valid in GNU make. It creates (different) dummy rules in GNU and +# BSD make. Both dummy rules are harmless. + +.for MAN_PAGE in $(NROFF_PAGES) : +$(MAN_DIR)/$(MAN_PAGE).gz: $(MANXSL) $(DOCBOOK_DIR)/$(MAN_PAGE:R).xml + $(DBPROC) -o $(.TARGET:.gz=) $(.ALLSRC) + $(GZIP) $(.TARGET:.gz=) + +$(HTML_DIR)/man-html/$(MAN_PAGE:R).html: $(NOCHUNKBOOKXSL) \ +$(DOCBOOK_DIR)/$(MAN_PAGE:R).xml + $(DBPROC) -o $(.TARGET) $(.ALLSRC) +.endfor : + +dockit: clean docs documentation-kit-0.10.tar.gz + +documentation-kit-0.10.tar.gz: + $(TAR) zcf documentation-kit-0.10.tar.gz $(HTML_DIR)/ + +clean: + $(PROGRESS_RM) "$(HTML_DIR)/man-html/*.html" + $(RM_QUIET) $(HTML_FILES) + + $(PROGRESS_RM) "$(MAN_DIR)/*.[58].gz" + $(RM_QUIET) $(NROFF_FILES) + + $(PROGRESS_RM) "$(DOCBOOK_DIR)/ExceptionCodes.xml" + $(RM_QUIET) $(DOCBOOK_DIR)/ExceptionCodes.xml + + $(PROGRESS_RM) "documentation-kit-0.10.tar.gz" + $(RM_QUIET) documentation-kit-0.10.tar.gz diff --git a/docs/api-notes/INDEX.txt b/docs/api-notes/INDEX.txt new file mode 100644 index 00000000..f333ac3b --- /dev/null +++ b/docs/api-notes/INDEX.txt @@ -0,0 +1,61 @@ +TITLE Programmers Notes for Box Backup + +This directory contains the programmers notes for the Box Backup system. They will be of interest only if you want to review or modify the code. + +These notes are intended to be run through a script to produce HTML at some later stage, hence the marks such as 'TITLE'. + + +SUBTITLE Organisation + +The project is split up into several modules. The modules within 'lib' are building blocks for creation of the actual executable programs within 'bin'. 'test' contains unit tests for lib and bin modules. + +The file modules.txt lists the modules which are to be built, and their dependencies. It also allows for platform differences. + + +SUBTITLE Documentation Organisation + +In this directory, the files correspond to modules or areas of interest. Sub directories of the same name contain files documenting specific classes within that module. + + +SUBTITLE Suggested reading order + +* common/lib_common.txt +* common/lib_server.txt +* bin_bbackupd.txt +* backup_encryption.txt +* bin_bbstored.txt +* raidfile/lib_raidfile.txt + +and refer to other sections as required. + + +SUBTITLE Building + +The makefiles are generated by makebuildenv.pl. (The top level makefile is generated by makeparcels.pl, but this is for the end user to use, not a programmer.) + +To build a module, cd to it and type make. If the -DRELEASE option is specified (RELEASE=1 with GNU make) the release version will be built. The object files and exes are placed in a directory structure under 'release' or 'debug'. + +It is intended that a test will be written for everything, so in general make commands will be issued only within the test/* modules. Once it has been built, cd to debug/test/ and run the test with ./t . + + +SUBTITLE Programming style + +The code is written to be easy to write. Ease of programming is the primary concern, as this should lead to fewer bugs. Efficiency improvements can be made later when the system as a whole works. + +Much use is made of the STL. + +There is no common base class. + +All errors are reported using exceptions. + +Some of the boring code is generated by perl scripts from description files. + +There are a lot of modules and classes which can easily be used to build other projects in the future -- there is a lot of "framework" code. + + +SUBTITLE Lots more documentation + +The files are extensively commented. Consider this notes as an overview, and then read the source files for detailed and definitive information. + +Each function and class has a very brief decsription of it's purpose in a standard header, and extensive efforts have been maed to comment the code itself. + diff --git a/docs/api-notes/Win32_Clients.txt b/docs/api-notes/Win32_Clients.txt new file mode 100644 index 00000000..fad6c4af --- /dev/null +++ b/docs/api-notes/Win32_Clients.txt @@ -0,0 +1,13 @@ +The basic client tools now run on Win32 natively. +The port was done by nick@omniis.com. + +* bbackupd +* bbackupquery +* bbackupctl + +Have been ported. bbackupd runs as a NT style service. + +Known limitations: + +* File attributes and permissions are not backed up. + diff --git a/docs/api-notes/backup_encryption.txt b/docs/api-notes/backup_encryption.txt new file mode 100644 index 00000000..36580581 --- /dev/null +++ b/docs/api-notes/backup_encryption.txt @@ -0,0 +1,109 @@ +TITLE Encryption in the backup system + +This document explains how everything is encrypted in the backup system, and points to the various functions which need reviewing to ensure they do actually follow this scheme. + + +SUBTITLE Security objectives + +The crpyto system is designed to keep the following things secret from an attacker who has full access to the server. + +* The names of the files and directories +* The contents of files and directories +* The exact size of files + +Things which are not secret are + +* Directory heirarchy and number of files in each directory +* How the files change over time +* Approximate size of files + + +SUBTITLE Keys + +There are four separate keys used: + +* Filename +* File attributes +* File block index +* File data + +and an additional secret for file attribute hashes. + +The Cipher is Blowfish in CBC mode in most cases, except for the file data. All keys are maximum length 448 bit keys, since the key size only affects the setup time and this is done very infrequently. + +The file data is encrypted with AES in CBC mode, with a 256 bit key (max length). Blowfish is used elsewhere because the larger block size of AES, while more secure, would be terribly space inefficient. Note that Blowfish may also be used when older versions of OpenSSL are in use, and for backwards compatibility with older versions. + +The keys are generated using "openssl rand", and a 1k file of key material is stored in /etc/box/bbackupd. The configuration scripts make this readable only by root. + +Code for review: BackupClientCryptoKeys_Setup() +in lib/backupclient/BackupClientCryptoKeys.cpp + + +SUBTITLE Filenames + +Filenames need to be secret from the attacker, but they need to be compared on the server so it can determine whether or not is it a new version of an old file. + +So, the same Initialisation Vector is used for every single filename, so the same filename encrypted twice will have the same binary representation. + +Filenames use standard PKCS padding implemented by OpenSSL. They are proceeded by two bytes of header which describe the length, and the encoding. + +Code for review: BackupStoreFilenameClear::EncryptClear() +in lib/backupclient/BackupStoreFilenameClear.cpp + + +SUBTITLE File attributes + +These are kept secret as well, since they reveal information. Especially as they contain the target name of symbolic links. + +To encrypt, a random Initialisation Vector is choosen. This is stored first, followed by the attribute data encrypted with PKCS padding. + +Code for review: BackupClientFileAttributes::EncryptAttr() +in lib/backupclient/BackupClientFileAttributes.cpp + + +SUBTITLE File attribute hashes + +To detect and update file attributes efficiently, the file status change time is not used, as this would give suprious results and result in unnecessary updates to the server. Instead, a hash of user id, group id, and mode is used. + +To avoid revealing details about attributes + +1) The filename is added to the hash, so that an attacker cannot determine whether or not two files have identical attributes + +2) A secret is added to the hash, so that an attacker cannot compare attributes between accounts. + +The hash used is the first 64 bits of an MD5 hash. + + +SUBTITLE File block index + +Files are encoded in blocks, so that the rsync algorithm can be used on them. The data is compressed first before encryption. These small blocks don't give the best possible compression, but there is no alternative because the server can't see their contents. + +The file contains a number of blocks, which contain among other things + +* Size of the block when it's not compressed +* MD5 checksum of the block +* RollingChecksum of the block + +We don't want the attacker to know the size, so the first is bad. (Because of compression and padding, there's uncertainty on the size.) + +When the block is only a few bytes long, the latter two reveal it's contents with only a moderate amount of work. So these need to be encrypted. + +In the header of the index, a 64 bit number is chosen. The sensitive parts of the block are then encrypted, without padding, with an Initialisation Vector of this 64 bit number + the block index. + +If a block from an previous file is included in a new version of a file, the same checksum data will be encrypted again, but with a different IV. An eavesdropper will be able to easily find out which data has been re-encrypted, but the plaintext is not revealed. + +Code for review: BackupStoreFileEncodeStream::Read() (IV base choosen about half-way through) +BackupStoreFileEncodeStream::EncodeCurrentBlock() (encrypt index entry) +in lib/backupclient/BackupStoreFileEncodeStream.cpp + + +SUBTITLE File data + +As above, the first is split into chunks and compressed. + +Then, a random initialisation vector is chosen, stored first, followed by the compressed file data encrypted using PKCS padding. + +Code for review: BackupStoreFileEncodeStream::EncodeCurrentBlock() +in lib/backupclient/BackupStoreFileEncodeStream.cpp + + diff --git a/docs/api-notes/bin_bbackupd.txt b/docs/api-notes/bin_bbackupd.txt new file mode 100644 index 00000000..67ad9267 --- /dev/null +++ b/docs/api-notes/bin_bbackupd.txt @@ -0,0 +1,88 @@ +TITLE bin/bbackupd + +The backup client daemon. + +This aims to maintain as little information as possible to record which files have been uploaded to the server, while minimising the amount of queries which have to be made to the server. + + +SUBTITLE Scanning + +The daemon is given a length of time, t, which files over this age should be uploaded to the server. This is to stop recently updated files being uploaded immediately to avoid uploading something repeatedly (on the assumption that if a file has been written, it is likely to be modified again shortly). + +It will scan the files at a configured interval, and connect to the server if it needs to upload files or make queries about files and directories. + +The scan interval is actually varied slightly between each run by adding a random number up to a 64th of the configured time. This is to reduce cyclic patterns of load on the backup servers -- otherwise if all the boxes are turned on at about 9am, every morning at 9am there will be a huge spike in load on the server. + +Each scan chooses a time interval, which ends at the current time - t. This will be from 0 to current time - t on the first run, then the next run takes the start time as the end time of the previous run. The scan is only performed if the difference between the start and end times is greater or equal to t. + +For each configured location, the client scans the directories on disc recursively. + +For each directory + +* If the directory has never been scanned before (in this invocation of the daemon) or the modified time on the directory is not that recorded, the listing on the server is downloaded. + +* For each file, if it's modified time is within the time period, it is uploaded. If the directory has been downloaded, it is compared against that, and only uploaded if it's changed. + +* Find all the new files, and upload them if they lie within the time interval. + +* Recurse to sub directories, creating them on the server if necessary. + +Hence, the first time it runs, it will download and compare the entries on the disc to those on the server, but in future runs it will use the file and directory modification times to work out if there is anything which needs uploading. + +If there aren't any changes, it won't even need to connect to the server. + +There are some extra details which allow this to work reliably, but they are documented in the source. + + +SUBTITLE File attributes + +The backup client will update the file attributes on files as soon as it notices they are changed. It records most of the details from stat(), but only a few can be restored. Attributes will only be considered changed if the user id, group id or mode is changed. Detection is by a 64 bit hash, so detection is strictly speaking probablistic. + + +SUBTITLE Encryption + +All the user data is encrypted. There is a separate file, backup_encryption.txt which describes this, and where in the code to look to verify it works as described. + + +SUBTITLE Tracking files and directories + +Renaming files is a difficult problem under this minimal data scanning scheme, because you don't really know whether a file has been renamed, or another file deleted and new one created. + +The solution is to keep (on disc) a map of inode numbers to server object IDs for all directories and files over a certain user configurable threshold. Then, when a new file is discovered, it is first checked to see if it's in this map. If so, a rename is considered, which will take place if the local object corresponding to the name of the tracked object doesn't exist any more. + +Because of the renaming requirement, deletions of objects from the server are recorded and delayed until the end of the scan. + + +SUBTITLE Running out of space + +If the store server indicates on login to the backup client, it will scan, but not upload anything nor adjust it's internal stored details of the local objects. However, deletions and renames happen. + +This is to allow deletions to still work and reduce the amount of storage space used on the server, in the hope that in the future there will be enough space. + +Just not doing anything would mean that one big file created and then deleted at the wrong time would stall the whole backup process. + + +SUBTITLE BackupDaemon + +This is the daemon class for the backup daemon. It handles setting up of all the objects, and implements calulcation of the time intervals for the scanning. + + +SUBTITLE BackupClientContext + +State information for the scans, including maintaining a connection to the store server if required. + + +SUBTITLE BackupClientDirectoryRecord + +A record of state of a directory on the local filesystem. Containing the recursive scanning function, which is long and entertaining, but very necessary. It contains lots of comments which explain the exact details of what's going on. + + +SUBTITLE BackupClientInodeToIDMap + +A implementation of a map of inode number to object ID on the server. If Berkeley DB is available on the platform, it is stored on disc, otherwise there is an in memory version which isn't so good. + + + + + + diff --git a/docs/api-notes/bin_bbstored.txt b/docs/api-notes/bin_bbstored.txt new file mode 100644 index 00000000..c9c4e229 --- /dev/null +++ b/docs/api-notes/bin_bbstored.txt @@ -0,0 +1,54 @@ +TITLE bin/bbstored + +The backup store daemon. + +Maintains a store of encrypted files, and every so often goes through deleting unnecessary data. + +Uses an implementation of Protocol to communicate with the backup client daemon. See bin/bbstored/backupprotocol.txt for details. + + +SUBTITLE Data storage + +The data is arranged as a set of objects within a RaidFile disc set. Each object has a 64 bit object ID, which is turned into a filename in a mildly complex manner which ensure that directories don't have too many objects in them, but there is a minimal number of nested directories. See StoreStructure::MakeObjectFilename in lib/backupstore/StoreStructure.cpp for more details. + +An object can be a directory or a file. Directories contain files and other directories. + +Files in directories are supersceded by new versions when uploaded, but the old versions are flagged as such. A new version has a different object ID to the old version. + +Every so often, a housekeeping process works out what can be deleted, and deletes unnecessary files to take them below the storage limits set in the store info file. + + +SUBTITLE Note about file storage and downloading + +There's one slight entertainment to file storage, in that the format of the file streamed depends on whether it's being downloaded or uploaded. + +The problem is that it contains an index of all the blocks. For efficiency in managing these blocks, they all need to be in the same place. + +Files are encoded and decoded as they are streamed to and from the server. With encoding, the index is only completely known at the end of the process, so it's sent last, and lives in the filesystem last. + +When it's downloaded, it can't be decoded without knowing the index. So the index is sent first, followed by the data. + + +SUBTITLE BackupContext + +The context of the current connection, and the object which modifies the store. + +Maintains a cache of directories, to avoid reading them continuously, and keeps a track of a BackupStoreInfo object which is written back periodiocally. + + +SUBTITLE BackupStoreDaemon + +A ServerTLS daemon which forks off a separate housekeeping process as it starts up. + +Handling connections is delegated to a Protocol implementation. + + +SUBTITLE BackupCommands.cpp + +Implementation of all the commands. Work which requires writing is handled in the context, read only commands mainly in this file. + + +SUBTITLE HousekeepStoreAccount + +A class which performs housekeeping on a single account. + diff --git a/docs/api-notes/common/lib_common.txt b/docs/api-notes/common/lib_common.txt new file mode 100644 index 00000000..11d7b02d --- /dev/null +++ b/docs/api-notes/common/lib_common.txt @@ -0,0 +1,52 @@ +TITLE lib/common + +This module is the basic building block of the project. It is included implicitly as a dependency of all other modules. + +It provides basic services such as file and stream functionality, platform information, and various bits of utility code which doesn't fit naturally anywhere else but is useful to many other applications. + + +SUBTITLE Box.h + +The main include file for the project. It should be the first file included in every cpp file, followed by any system headers, then other project headers. This order has been chosen so that eventually it can be used as a target for pre-compiled headers. (This will be important for the Windows port) + + +SUBTITLE BoxPlatform.h + +This contains various bits of information on platform differences. The build scripts include a compile command line definition like PLATFORM_OPENBSD, which is then used to select the required options. + +Some of the stuff is not particularly pleasant, but it aims to produce a consistent compilation environment, and to have a abstracted way of setting other defines to tell the other files what things are available on this platform. + +GNU configure is not used, as it would be just another dependency. Although something does have to be done about compilation on Linux, which is just too different on each distribution to be caught by a common configuration here. + + +SUBTITLE Streams + +Much use is made of streams -- files, connections, buffers, all sorts of things are implemented using streams. + +The abstract base class IOStream defines the interface, see the class file for more details. + +Streams are lib/common's basic contribution to the project. A standard interface for handling data, and a number of utility classes which make performing common stream functions easy. + + +SUBTITLE Serialisation + +Note there is no "serialisable" class. Instead, objects which need to be serialised into Streams simply have two functions, ReadFromStream and WriteToStream. These perform as expected. + +As it turns out, there's no real need for a specific serialisation base class. If you do need to be able to serialise arbitary objects, there are two approaches. + +1) If you're serialising an arbitary object, you're going to know roughly what type it is. So it's likely to be a subclass of a known base class... in which case, you simply use virtual functions. + +2) If it really is an arbitary type, then you just write your function which accepts any type of object to serialise as a template. + + +SUBTITLE BoxException + +The exception base class for the project. All exceptions thrown by this code are dervied from BoxException. In general, each module which has unique errors has it's own exception class. + +CommonException errors are thrown throughout the project. + + +SUBTITLE Other bits + +There are some other extra useful bits which are documented only in the source files. + diff --git a/docs/api-notes/common/lib_common/BoxTime.txt b/docs/api-notes/common/lib_common/BoxTime.txt new file mode 100644 index 00000000..18bef5a5 --- /dev/null +++ b/docs/api-notes/common/lib_common/BoxTime.txt @@ -0,0 +1,7 @@ +TITLE BoxTime + +Not strictly a class, but time is presented in the project as 64 bit integers as microseconds since the UNIX Epoch. + +There are a number of utility functions in the BoxTime*.h files, which are useful. + +To get BoxTime values from a struct stat, use the FileModificationTime.h functions. \ No newline at end of file diff --git a/docs/api-notes/common/lib_common/CollectInBufferStream.txt b/docs/api-notes/common/lib_common/CollectInBufferStream.txt new file mode 100644 index 00000000..5f9556a3 --- /dev/null +++ b/docs/api-notes/common/lib_common/CollectInBufferStream.txt @@ -0,0 +1,26 @@ +CLASS CollectInBufferStream + +This class is essentially a buffer. Data is written to it, and stored in memory. Then when all data is written which will be written, it can be read out again. + +Useful for building streams in memory. + + +FUNCTION CollectInBufferStream::SetForReading() + +After you've written all the data, call this to set it to read mode. + + +FUNCTION CollectInBufferStream::Reset() + +Reset the stream to the state where is has no data, and is about to have data written. + + +FUNCTION CollectInBufferStream::GetSize() + +Get the size of the buffered data -- the entire data. Use the interface function BytesLeftToRead() in most cases. + + +FUNCTION CollectInBufferStream::GetBuffer() + +Get the buffer, so you can do bad things with it if you're feeling naughty. + diff --git a/docs/api-notes/common/lib_common/Configuration.txt b/docs/api-notes/common/lib_common/Configuration.txt new file mode 100644 index 00000000..ef5f38f6 --- /dev/null +++ b/docs/api-notes/common/lib_common/Configuration.txt @@ -0,0 +1,102 @@ +CLASS Configuration + +Implements the reading of multi-level configuration files. This is intended to be a generic way of representing configuration implementation. + +Basic validation is performed on files as they are read, specified by a data structure. + +test/common has some examples of usage. + + +SUBTITLE Configuration file format + +The format is simple, a list of Key = Value pairs. For example + +Key1 = Value1 +Key2 = Value2 + +Optionally, multiple values for the same key are allowed. + +Lists of configurations can be nested to any level. These are called Sub-configurations: + +SubConfig +{ + SubKey1 = ValueX + SubKey2 = ValueY +} + + +SUBTITLE Verification + +The verification structure specifies what keys are required, what are optional, and what sub-configurations are expected. Default values can be specified. + +See Configuration.h for the structures. + +RaidFileController::Initialise has a good example of a simple verification layout. + +Wildcards can be used for SubConfigurations, by specifying a name of "*". This allows you to use multiple sections to configure any number of items. + +Each item has a number of flags, which are combined to say whether an item is required, should be an integer or boolean, and rather importantly, whether it's the last item in the list. + +Verification is limited, so you may wish to do more verification the file. There are unimplemented hooks for custom verification functions to be included in the verification definitions. Should be done at some point. + +Boolean keys have possible values "true", "yes", "false", "no" (case insensitive). + + +FUNCTION Configuration::LoadAndVerify() + +Loads the configuration from disc, and verifies it. If there are problems with the verification, it returns some text which can be used to tell the user the problems with the file. These are fairly basic error messages, but do say what should be done to get it to parse properly. + +This returns a Configuration object, which can then be queries for Keys and Values. Sub-configurations are implemented through an interface which returns a reference to another Configuration object. + + + +FUNCTION Configuration::KeyExists() + +Does a specified key exist? + +Use this for optional key values only -- specify them in the verification structure if they are required. + + +FUNCTION Configuration::GetKeyValue() + +Get the value of a key as a string. + +If ConfigTest_MultiValueAllowed is set in the relevant entry in the verify structure, this string may contain multiple values, separated by a single byte with value 0x01 (use Configuration::MultiValueSeparator in code). Use SplitString() defined in Utils.h to split it into components. + +This representation was chosen as multi-values are probably rare, and unlikely to justify writing a nicer (but more memory intensive and complex) solution. + + +FUNCTION Configuration::GetKeyValueInt() + +Get the value of a key as an integer. Make sure the AsInt property is requried in the verification structure. + + +FUNCTION Configuration::GetKeyValueBool() + +Get the value of a key as a boolean value. Make sure the AsBool property is requried in the verification structure. + +Default to "false", should this verification not be performed and an unknown value is specified. + + +FUNCTION Configuration::GetKeyNames() + +Return a list of all the keys in this configuration. + + +FUNCTION Configuration::SubConfigurationExists() + +Does a specified sub-configuration exist? + + +FUNCTION Configuration::GetSubConfiguration() + +Return another Configuration object representing the sub section. + + +FUNCTION Configuration::GetSubConfigurationNames() + +Get a list of all the sub configurations. + +As there isn't a particularly neat way that configurations can be iterated over, mSubConfigurations is public. (BAD!) + + diff --git a/docs/api-notes/common/lib_common/Conversion.txt b/docs/api-notes/common/lib_common/Conversion.txt new file mode 100644 index 00000000..62d1967a --- /dev/null +++ b/docs/api-notes/common/lib_common/Conversion.txt @@ -0,0 +1,14 @@ +TITLE Generic type conversion + +Conversion.h provides generic type conversion. Within the BoxConvert namespace, it defines the templated function + + ToType Convert(FromType From) + +which converts the data type as expected. In general, from and to types have to be specified explicitly. + +Templates rather than overloaded functions are used, firstly to be absolutely explicit about the conversion required, and secondly because overloaded functions can't have differing return types for the same argument type. + +The function is specialised for various types, and the generic version uses C++ type conversion. + +Exceptions may be thrown if the conversion is not possible. These are all of the ConversionException type. + diff --git a/docs/api-notes/common/lib_common/ExcludeList.txt b/docs/api-notes/common/lib_common/ExcludeList.txt new file mode 100644 index 00000000..8a5bf36c --- /dev/null +++ b/docs/api-notes/common/lib_common/ExcludeList.txt @@ -0,0 +1,21 @@ +TITLE ExcludeList + +A class implementing a list of strings which are to be excluded in some way. Entries can be added as strings to be matched exactly, or as regular expressions. + +Multiple entries can be added in a single function call in a manner designed to work with the multi-value entries store in a Configuation object. + + +FUNCTION ExcludeList::AddDefiniteEntries() + +Definite entries are exact strings to match. + + +FUNCTION ExcludeList::AddRegexEntries() + +Regular expressions as defined by POSIX, and supported by the host platform. Will throw an exception if regular expressions are not supported by the platform. + + +FUNCTION ExcludeList::IsExcluded() + +Test a string for being excluded by definite or regex entries. + diff --git a/docs/api-notes/common/lib_common/FdGetLine.txt b/docs/api-notes/common/lib_common/FdGetLine.txt new file mode 100644 index 00000000..d92ff94d --- /dev/null +++ b/docs/api-notes/common/lib_common/FdGetLine.txt @@ -0,0 +1,11 @@ +CLASS FdGetLine + +See IOStreamGetLine, difference is that it works on basic UNIX file descriptors rather than full blown streams. Follows basic interface, except... + + +FUNCTION FdGetLine::GetLine + +Returns a string containing the optionally preprocessed line. + +Do not call if IsEOF if true. + diff --git a/docs/api-notes/common/lib_common/Guards.txt b/docs/api-notes/common/lib_common/Guards.txt new file mode 100644 index 00000000..6174fea6 --- /dev/null +++ b/docs/api-notes/common/lib_common/Guards.txt @@ -0,0 +1,5 @@ +TITLE Guards.h + +This file contains a couple of classes for using file and memory. When the class goes out of scope, the underlying object is closed or freed, respectively. + + diff --git a/docs/api-notes/common/lib_common/IOStream.txt b/docs/api-notes/common/lib_common/IOStream.txt new file mode 100644 index 00000000..09460656 --- /dev/null +++ b/docs/api-notes/common/lib_common/IOStream.txt @@ -0,0 +1,89 @@ +CLASS IOStream + +The base class for streams of data. See IOStream.h for specific details on functions, but the interface is described here. + +Most streams only implement one direction, but some both. + + +SUBTITLE Reading + + +FUNCTION IOStream::Read() + +Reads data from the stream. Returns 0 if there is no data available within the timeout requested. + +Unlike the UNIX API, a return of 0 does not mean that there is no more data to read. Use IOStream::StreamDataLeft() to find this out. + + +FUNCTION IOStream::ReadFullBuffer() + +If you want to read a specific sized block of data from a stream, it is annoying ot use the Read function, because it might return less, so you have to loop. + +This implements that loop. Note that the timeout is the timeout for each individual read -- it might take a lot longer to complete. + +You must check the return value, which is whether or not it managed to get all that data. + + +FUNCTION IOStream::BytesLeftToRead() + +How many bytes are left to read in the stream treated as a whole. May return IOStream::SizeOfStreamUnknown if it is something like a socket which doesn't know how much data is in the stream. + + +FUNCTION IOStream::StreamDataLeft() + +Returns true if more data can be read. Or more specifically, if more data might be able to be read some time in the future. + + +SUBTITLE Writing + + +FUNCTION IOStream::Write() + +Write data to a stream. Writes the entire block, or exceptions. + + +FUNCTION IOStream::WriteAllBuffered() + +Writes any buffered data to the underlying object. + +Call at the end of a series of writes to make sure the data is actually written. (Buffering is not yet implemented anywhere, but it should be soon.) + + +FUNCTION IOStream::StreamClosed() + +Is the stream closed, and writing no longer possible? + + +FUNCTION IOStream::CopyStreamTo() + +A utility function to copy the contents of one stream to another, until the reading stream has no data left. + + +SUBTITLE Other interfaces + +These are slightly nasty interfaces, because they have to match the UNIX API to some extent. + + +FUNCTION IOStream::Close() + +Close the stream -- intended to indicate that nothing more will be written. + +However, also closes reading in most cases. Stream dependent. Probably best just to delete the object and let it sort itself out. + + +FUNCTION IOStream::Seek() + +Seeks within the stream -- mainly for using files within a stream interface, although some of the utility stream classes support it too. + +This is actually a bad interface, because it does not specify whether it applies to reading or writing. This matches the interface provided by files with a single file pointer, but does not map well onto other stream types. + +Should it be changed? Perhaps. But then it means that files would either have to have an inconsitent interface, or the class keep track of two separate file pointers. Which isn't nice. + +So use carefully, and remember whether you're using a file stream, a reading stream, or a writing stream. + + +FUNCTION IOStream::GetPosition() + +Get the current file pointer. Subject to same problems as Seek with regard to semantics. + + diff --git a/docs/api-notes/common/lib_common/IOStreamGetLine.txt b/docs/api-notes/common/lib_common/IOStreamGetLine.txt new file mode 100644 index 00000000..04c56b57 --- /dev/null +++ b/docs/api-notes/common/lib_common/IOStreamGetLine.txt @@ -0,0 +1,29 @@ +CLASS IOStreamGetLine + +This class provides a convenient way to read text from a file, line by line. It also can preprocess the line to remove leading and trailing whitespace and comments. Comments are started by the character # and run to the end of the line. + +Create an instance by passing a reference to a stream into the constructor. + +Note the class does internal buffering, so you can only detach it later if the stream supports seeking backwards. + + +FUNCTION IOStreamGetLine::GetLine() + +Returns true if a line could be retreieved without a read timing out. + + +FUNCTION IOStreamGetLine::IsEOF() + +Whether the end of the stream has been reached. Do not call GetLine if this is true. + + +FUNCTION IOStreamGetLine::GetLineNumber() + +Returns the line number. + + +FUNCTION IOStreamGetLine::DetachFile() + +Detaches the stream from the GetLine class. Will seek backwards to "replace" data it's buffered back in the stream. + + diff --git a/docs/api-notes/common/lib_common/MainHelper.txt b/docs/api-notes/common/lib_common/MainHelper.txt new file mode 100644 index 00000000..eb5b07f0 --- /dev/null +++ b/docs/api-notes/common/lib_common/MainHelper.txt @@ -0,0 +1,4 @@ +TITLE MainHelper.h + +This header contains a couple of macros which add exception handling and reporting to main() functions for executable programs. + diff --git a/docs/api-notes/common/lib_common/WaitForEvent.txt b/docs/api-notes/common/lib_common/WaitForEvent.txt new file mode 100644 index 00000000..0bc55726 --- /dev/null +++ b/docs/api-notes/common/lib_common/WaitForEvent.txt @@ -0,0 +1,16 @@ +CLASS WaitForEvent + +This class implements a way to efficiently wait on one or more system objects, for example sockets and file descriptors. Where kqueue() is available, this is used, otherwise poll(). The poll() implementation is less comprehensive and rather more limited. + +To add a compatible object, call Add(). To remove it, call Remove(). To wait for an object to become ready, call Wait(), which returns a pointer to the first ready object, or 0 for a timeout. + +The timeout is set either in the constructor, or using the SetTimout() method. It is specified in milliseconds. + +The kqueue based implementation will automatically remove objects when they are closed (actually, the OS does this), but the poll implementation requires that Remove() be called. + +The flags passed to Add() or Remove() are passed to the object, and are ignored by this class. + +For an object to be able to be added to the list, it should implement FillInKEvent() and FillInPoll() for kqueue and poll implementations respectively. Use the PLATFORM_KQUEUE_NOT_SUPPORTED define to test which is necessary for the platform. + +It does not have to be derived off any particular class, as it uses templates to be compatible with any class. + diff --git a/docs/api-notes/common/lib_common/xStream.txt b/docs/api-notes/common/lib_common/xStream.txt new file mode 100644 index 00000000..91e9c0ea --- /dev/null +++ b/docs/api-notes/common/lib_common/xStream.txt @@ -0,0 +1,40 @@ +TITLE Various stream types + +Some useful streams are implemented in lib/common. + + +SUBTITLE CollectInBufferStream + +Described in it's own page. + + +SUBTITLE FileStream + +Implements a stream from a file, allowing both reading and writing. + + +SUBTITLE MemBlockStream + +Turns a memory block into a readable stream. + +Can also be constructed using StreamableMemBlock, CollectInBufferStream and MemBlockStream as sources of the buffer. + + +SUBTITLE PartialReadStream + +Create a readable stream which will read a set number of bytes from another stream, and then declare itself as closed. + +Useful for extracting a small chunk of another stream to present to a function which expects to consume all of a stream. + + +SUBTITLE ReadGatherStream + +Create a readable stream out of blocks from many streams -- so various sections of any number of streams are composed into a single stream. + +To use, register each stream with AddComponent, then use the returned 'handle' with AddBlock to add blocks to the composed stream. + +Optionally, the object will take ownership of the streams added, and delete them when itself is deleted. + +See the comments in the function blocks in the cpp file for more info. + + diff --git a/docs/api-notes/common/lib_compress.txt b/docs/api-notes/common/lib_compress.txt new file mode 100644 index 00000000..f3e26f20 --- /dev/null +++ b/docs/api-notes/common/lib_compress.txt @@ -0,0 +1,8 @@ +TITLE lib/compress + +This is a horrid C++ interface to zlib. It is written this way to avoid having to buffer any data, and so be unnecessarily inefficient. But this does end up just being a wrapper to the zlib interface. + +See test/compress for an example of how to use it. But it should be very familiar. + +For an easier interface, use CompressStream. + diff --git a/docs/api-notes/common/lib_compress/CompressStream.txt b/docs/api-notes/common/lib_compress/CompressStream.txt new file mode 100644 index 00000000..eca43eb6 --- /dev/null +++ b/docs/api-notes/common/lib_compress/CompressStream.txt @@ -0,0 +1,27 @@ +TITLE CompressStream + +This implements a compressing stream class, which compresses and decompresses data sent to an underlying stream object. Data is likely to be buffered. + + +WARNING: Use WriteAllBuffered() (after objects which need to be sent in their entirity) and Close() (after you have finished writing to the stream) otherwise the compression buffering will lose the last bit of data for you. + + +The class works as a filter to an existing stream. Ownership can optionally be taken so that the source stream is deleted when this object is deleted. + +It can operate in one or two directions at any time. Data written to the stream is compressed, data read from the stream is decompressed. If a direction is not being (de)compressed, then it can act as a pass-through without touching the data. + +The constructor specifies the actions to be taken: + +CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, + bool PassThroughWhenNotCompressed = false); + +pStream - stream to filter +TakeOwnership - if true, delete the stream when this object is deleted. +DecompressRead - reads pass through a decompress filter +CompressWrite - writes pass through a compression filter +PassThroughWhenNotCompressed - if not filtering a direction, pass through the data unaltered, otherwise exception if an attempt is made on that direction. + + +Note that the buffering and compression will result in different behaviour than the underlying stream: data may not be written in one go, and data being read may come out in different sized chunks. + diff --git a/docs/api-notes/common/lib_crypto.txt b/docs/api-notes/common/lib_crypto.txt new file mode 100644 index 00000000..9ddafe69 --- /dev/null +++ b/docs/api-notes/common/lib_crypto.txt @@ -0,0 +1,28 @@ +TITLE lib/crypto + +Provides cryptographic primatives using the OpenSSL implementation. + + +SUBTITLE CipherContexts and CipherDescriptions + +A CipherContext is the interface to encryption and decryption, providing a generic interface. + +It is constructed with a decrypt or encrypt flag, and a CipherDescription. The CipherDescription specifies which cipher is to be used, the key, and all other cipher specific paramteres. + +The CipherContext has support for padding and initialisation vectors. + + +SUBTITLE Random numbers + +An interface to the OpenSSL psuedo random number generater is provided. + + +SUBTITLE Digests + +An interface to the MD5 digest is provided. + + +SUBTITLE RollingChecksum + +The rsync rolling checksum is implemented. The interface is a little tricky to be efficient as the caller must track the position and provide relevant bytes to advance the checksum. + diff --git a/docs/api-notes/common/lib_crypto/CipherContext.txt b/docs/api-notes/common/lib_crypto/CipherContext.txt new file mode 100644 index 00000000..30fb4608 --- /dev/null +++ b/docs/api-notes/common/lib_crypto/CipherContext.txt @@ -0,0 +1,28 @@ +CLASS CipherContext + +Encryption and decryption using OpenSSL EVP interface. + +See the cpp file for more documentation in the function headers, and test/crypto for examples. + +General notes below. + + +SUBTITLE Construction + +Construct with the encryption direction, and a CipherDescription of the cipher and key required. + + +SUBTITLE Encrypting or decrypting + +Begin() and Transform() allow piece by piece transformation of a block. + +TransformBlock() transforms an entire block. + + +SUBTITLE Buffering + +All transforms expect to have enough space in the buffers for their entire output. Because of block boundaries and padding, it is necessary that the output buffer should be bigger than the input buffer. The amount of space depends on the cipher. + +InSizeForOutBufferSize() and MaxOutSizeForInBufferSize() perform these space calculations, returning the maximuim in size for a specified out size, and the reverse, respectively. + + diff --git a/docs/api-notes/common/lib_crypto/RollingChecksum.txt b/docs/api-notes/common/lib_crypto/RollingChecksum.txt new file mode 100644 index 00000000..d871b3f2 --- /dev/null +++ b/docs/api-notes/common/lib_crypto/RollingChecksum.txt @@ -0,0 +1,36 @@ +CLASS RollingChecksum + +Implementing the rsync rolling checksum algorithm. Read it's description first: + +http://samba.anu.edu.au/rsync/tech_report/node3.html + + +SUBTITLE Construction and initial checksum calculation + +The constructor takes a pointer to a block of data and a size, and calculates the checksum of this block. It can now be "rolled forward" to find the checksum of the block of the same size, one byte forward, with minimal calculation. + + +FUNCTION RollingChecksum::GetChecksum() + +Returns the checksum for the current block. + + +FUNCTION RollingChecksum::RollForward() + +This function takes the byte at the start of the current block, and the last byte of the block it's rolling forward to, and moves the checksum on. + +If the block is + + char *pBlock = ; + +with size s, then it should be called with + + RollForward(pBlock[0], pBlock[s]) + +and now GetChecksum will return the checksum of the block (pBlock+1) of size s. + + +FUNCTION RollingChecksum::RollForwardSeveral() + +Similar to RollForward(), but is more efficient for skipping several bytes at once. Takes pointers to the data buffer rather than the actual data values. + diff --git a/docs/api-notes/common/lib_server.txt b/docs/api-notes/common/lib_server.txt new file mode 100644 index 00000000..392f331d --- /dev/null +++ b/docs/api-notes/common/lib_server.txt @@ -0,0 +1,9 @@ +TITLE lib/server + +This provides various classes for implementing client/server applications (and UNIX daemons). + +The stars of the show are ServerTLS and Protocol, which allow client server applications which communicate using TLS (SSL successor) to be built with surprisingly few lines of code. + +All details in the respective class files. + +Look at test/basicserver for examples. diff --git a/docs/api-notes/common/lib_server/Daemon.txt b/docs/api-notes/common/lib_server/Daemon.txt new file mode 100644 index 00000000..6956ec2b --- /dev/null +++ b/docs/api-notes/common/lib_server/Daemon.txt @@ -0,0 +1,96 @@ +CLASS Daemon + +Implement a UNIX daemon which + +* Daemonises (detach from console, etc) +* Sets up signal handlers, and does useful things with them +* Reads configuration files +* Writes PID file +* Opens syslog + +all the usual UNIX basic daemon behaviour. + +The daemon exe takes optional arguments: The first is a configuration file filename which overrides the default. If the second argument is 'SINGLEPROCESS' the daemon will not daemonise. + +The configuration file must have a section like this + +Server +{ + PidFile = /var/run/daemon.pid + User = username +} + +Use DAEMON_VERIFY_SERVER_KEYS (defined in Daemon.h) to include the necessary keys in your configuration file's verify structure. + +The "User" line is optional, and if it is present the Daemon will change user to this username just before it daemonises. Note that unless the directory the PID file is written to has write permission for this user, the PID file will not be deleted on exit. + + +To implement a daemon, derive a class from Daemon, and override Run(). + +Then in your main file, do something like + +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + BackupDaemon daemon; + return daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); + + MAINHELPER_END +} + +and that's it. Obviously it's worth doing a few more things as well, but that's it. + + +FUNCTION Daemon::Run() + +Override with the main daemon code. It should behave like this + +void SomethingDaemon::Run() +{ + // Read configuration file + + // Do lots of work until StopRun() returns true + while(!StopRun()) + { + ::sleep(10); + } + + // Clean up nicely +} + +which allows the base class to implement the standard start, terminate and -HUP behaviours correctly. + + +FUNCTION Daemon::DaemonName() + +Returns the name of the daemon, for use in syslog. + + +FUNCTION Daemon::DaemonBanner() + +Returns the banner to be displayed on startup, or 0 for no banner. + + +FUNCTION Daemon::GetConfigVerify() + +Returns a configuration verify structure for verifying the config file. Note that this configuration structure should include a sub-configuration called "Server, and have entries defined by DAEMON_VERIFY_SERVER_KEYS. See one of the bin/bbackupd for an example. + + +FUNCTION Daemon::StopRun() + +Returns true when the daemon needs to be terminated or restarted. Use IsReloadConfigWanted() and IsTerminateWanted() to find out which one, if you need to know. + + +FUNCTION Daemon::SetupInInitialProcess() + +Override to perform additional functions in the initial process, before forking and detachment happens. + + +FUNCTION Daemon::EnterChild() + +Called when a child is entered. If you override it, remember to call the base class. + + + + diff --git a/docs/api-notes/common/lib_server/Protocol.txt b/docs/api-notes/common/lib_server/Protocol.txt new file mode 100644 index 00000000..09d3c1f1 --- /dev/null +++ b/docs/api-notes/common/lib_server/Protocol.txt @@ -0,0 +1,120 @@ +CLASS Protocol + +Protocol + +* serialises and deserialises data objects +* sends arbitary streams + +through a bi-directional IOStream object, usually a socket. + +These data objects are auto-generated by a perl script, along with the logic to implement a simple protocol where one end is a client, and the other a server. The client sends a command object (and optional streams) and the server returns a reply object (and optional streams). + +The perl script uses a description file which specifies the data inside these objects (which can be extended to include any type which implements the standard serialisation functions), the required responses to the commands, and whether streams are involved. + +It then implements a server object, which given a stream and a user defined context object, waits for commands, processes them, and sends the results back. All you need to do is implement a DoCommand() function for each command object you define. + +On the client side, a Query() function is implemented which takes objects as parameters, and returns the right type of reply object (or throws an exception if it doesn't get it.) Short cut Query() functions are also implemented which don't require objects to be created manually. + +Thus, implementing a server is as simple as deriving a daemon off ServerStream or ServerTLS, and implementing the Connection() function as + + void TestProtocolServer::Connection(SocketStream &rStream) + { + TestProtocolServer server(rStream); + TestContext context; + server.DoServer(context); + } + +and that's it. TestContext is a user defined class which keeps track of all the state of the connection, and is passed to all the DoCommand() functions, which look like this: + + std::auto_ptr + TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, + TestContext &rContext) + { + return std::auto_ptr + (new TestProtocolServerSimpleReply(mValue+1)); + } + +(taken from test/basicserver) + +The client code looks like this + + SocketStream conn; + conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock"); + + TestProtocolClient protocol(conn); + + // Query + { + std::auto_ptr + reply(protocol.QuerySimple(41)); + TEST_THAT(reply->GetValuePlusOne() == 42); + } + + +Finally, debug logging can be generated which allows a list of all commands and their parameters to be logged to syslog or a file. + + +SUBTITLE Protocol Description File + +This file is passed to the lib/server/makeprotocol.pl script, which generates a h and cpp file to implement the protocol. + +It is in two sections, separated by a 'BEGIN_OBJECTS' on a line of it's own. + +In the top half, the following statements must be made. + +Name + The name of the protocol, used in naming classes. + +IdentString + The idenfitifaction string sent over the IOStream to confirm it it + is talking to another Protocol object speaking the same Protocol. + +ServerContextClass + The user defined context class used for the server, and the header + file it is defined in. + +Additionally, the following optional commands can be made. + +ClientType +ServerType (similarly) + Extends the types used in the objects below. Server and client + can use different types for the same object type. + +ImplementLog (Client|Server) (syslog|file) + Implement command logging for client or server into syslog or a file. + +LogTypeToText (Client|Server) + + For extended types, optionally define how to convert them into printf + elements and the code to run to get the argument. Within the evaluate + parameter, VAR is replaced by the name of the variable to display. + If this is not specified for a given type, OPAQUE is output instead. + + +In the object section, an object is defined by a line + + + +followed by lines beginning with whitespace defining the data transmitted in the object. The type may be list, which specifies a list (implemented as a std::vector) of entries of that type. + +The attributes specify exactly how that object is used in the defined protocol. + +Reply + The object is a reply object, sent from the server to the client. + +Command(Reply-Type) + The object is a command, send from the client to the server, and the server + will send back an object of type Reply-Type. + +IsError(Type-Field,SubType-Field) + The command is an error object, and the two files specify the data member + which describes the error type and sub type. + +EndsConversation + When this command is received, the connection is to be terminated. + (ie a logout command) + +StreamWithCommand + When this command is sent as a command, a stream follows it. + + diff --git a/docs/api-notes/common/lib_server/ServerStream.txt b/docs/api-notes/common/lib_server/ServerStream.txt new file mode 100644 index 00000000..6c5932a0 --- /dev/null +++ b/docs/api-notes/common/lib_server/ServerStream.txt @@ -0,0 +1,29 @@ +CLASS ServerStream + +ServerStream implementes a Daemon which accepts stream connections over sockets, and forks into a child process to handle them. + +To implement a daemon, derive from + + ServerStream + +The type SocketStream specifies that incoming connections should be treated as normal sockets, and SERVER_LISTEN_PORT is the port to listen to (if it's a inet socket, and if not overridden in the config file). The actual addresses (or names) to bind to are specified in the configuration file by the user. + +Make sure SERVERSTREAM_VERIFY_SERVER_KEYS(0) is included in the configuration verification structure in the "Server" sub configuration. 0 could be replaced with a default address, for example "unix:/var/run/server.sock" to specific a default UNIX socket in the filesystem. + +See test/basicserver for a simple example. + +The ListenAddresses key in the Server subconfiguration is a comma separated list of addresses, specified as family:name. Internet sockets are family 'inet', for example 'inet:localhost' (or 'inet:localhost:1080' to specify a port number as well), and unix domain sockets are 'unix', example above. + + +Override Connection to handle the connection. + +Remember to override Daemon functions like the server name, and start it up, just like a generic Daemon. + + +FUNCTION ServerStream::Connection + +This function takes a connected stream as it's argument. It should then proceed to do whatever it needs to do to talk to the client. + +Using IOStreamGetLine or a Protocol class to communicate may be quick ways of implementing this functionality. + + diff --git a/docs/api-notes/common/lib_server/ServerTLS.txt b/docs/api-notes/common/lib_server/ServerTLS.txt new file mode 100644 index 00000000..dbde500f --- /dev/null +++ b/docs/api-notes/common/lib_server/ServerTLS.txt @@ -0,0 +1,6 @@ +CLASS ServerTLS + +Implements a server which uses TLS (SSL) to encrypt and authenticate connections. + +Very similar to ServerStream, except it reads the certificate files for the TLSContext out of the Server sub-configuration to set up a TLSContext ("CertificateFile", "PrivateKeyFile" and "TrustedCAsFile"). Otherwise works exactly the same. + diff --git a/docs/api-notes/common/lib_server/SocketStream.txt b/docs/api-notes/common/lib_server/SocketStream.txt new file mode 100644 index 00000000..82813279 --- /dev/null +++ b/docs/api-notes/common/lib_server/SocketStream.txt @@ -0,0 +1,8 @@ +CLASS SocketStream + +A implementation of IOStream which wraps a socket connection. + +It can either be created by attaching to an existing object, or use the Open() function to open a connection to a named host on a specific port (or a local UNIX socket in the filesystem). + +Follows stream interface. + diff --git a/docs/api-notes/common/lib_server/SocketStreamTLS.txt b/docs/api-notes/common/lib_server/SocketStreamTLS.txt new file mode 100644 index 00000000..ebb3f233 --- /dev/null +++ b/docs/api-notes/common/lib_server/SocketStreamTLS.txt @@ -0,0 +1,11 @@ +CLASS SocketStreamTLS + +An implementation of IOStream which wraps a TLS (SSL) connection over a socket. + +The Open function takes a TLSContext reference which specifies the parameters for the connection. + + +FUNCTION GetPeerCommonName() + +Returns the common name of the certificate presented by the remote end. + diff --git a/docs/api-notes/common/lib_server/TLSContext.txt b/docs/api-notes/common/lib_server/TLSContext.txt new file mode 100644 index 00000000..ff50d3e6 --- /dev/null +++ b/docs/api-notes/common/lib_server/TLSContext.txt @@ -0,0 +1,16 @@ +CLASS TLSContext + +A wrapper over the OpenSSL context object. + +Note: you need to call SSLLib::Initialise at the beginning of your program to use these functions. + + +SUBTITLE Construction + +The constuctor takes the following parameters + +* Boolean for whether this is acting as a server or a client +* The .pem file containing the certificate which will be used +* The .pem file containing the private key for this certificate +* The .pem file containing the certificates which will certify the other end of the connection. + diff --git a/docs/api-notes/common/memory_leaks.txt b/docs/api-notes/common/memory_leaks.txt new file mode 100644 index 00000000..9a9764ea --- /dev/null +++ b/docs/api-notes/common/memory_leaks.txt @@ -0,0 +1,44 @@ +TITLE Memory leak detection + +The build system contains a primative memory leak detection system in debug builds. + +It works by using #defines to replace calls to malloc, free, realloc, new and delete with debug versions which record their use. When a process ends, it dumps a list of all the blocks or objects which were allocated by not freed, and the file and line of the source where they are originally allocated. + +It's not perfect, but should catch most things and work on most platforms. + +If it doesn't work on your platform, define PLATFORM_DISABLE_MEM_LEAK_TESTING in BoxPlatform.h within the relevant section. + + +SUBTITLE Requirements in source + +It does require some extra lines in the source file. The last included file in each .cpp file must be + + #include "MemLeakFindOn.h" + +and if a .h file uses any of these functions, the contents of the .h file should be bounded with + + #include "MemLeakFindOn.h" + + // ... some code, but absolutely no #includes + + #include "MemLeakFindOff.h" + +The cleanupforcvs.pl script checks for correct usage. + + +SUBTITLE Use in tests + +Tests will automatically dump memory leaks and regard them as a failure for anything executing in the main test process. + +To test for memory leaks in programs run from the test, or daemons, use something like + + TestRemoteProcessMemLeaks("bbackupd.memleaks"); + +If memory leak detection is being used, it will check the specified file for memory leak reports (deleting it afterwards). Any leak is an error. + +The Daemon class automactically arranges for the memory leak reports to be written. Other exes should set this up themselves, preferably using the define in MainHelper.h, as the first thing in their main() function. + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, name) + +File is the filename to write the report to, conventionally called ".memleaks", and name is the name of the exe. + diff --git a/docs/api-notes/encrypt_rsync.txt b/docs/api-notes/encrypt_rsync.txt new file mode 100644 index 00000000..a5db2df2 --- /dev/null +++ b/docs/api-notes/encrypt_rsync.txt @@ -0,0 +1,66 @@ +TITLE Encrypted rsync algorithm + +The backup system uses a modified version of the rsync algorithm. A description of the plain algorithm can be found here: + + http://samba.anu.edu.au/rsync/tech_report/ + +The algorithm is modified to allow the server side to be encrypted, yet still benefit from the reduced bandwidth usage. For a single file transfer, the result will be only slightly less efficient than plain rsync. For a backup of a large directory, the overall bandwidth may be less due to the way the backup client daemon detects changes. + +This document assumes you have read the rsync document. + +The code is in lib/backupclient/BackupStoreFile*.*. + + +SUBTITLE Blocks + +Each file is broken up into small blocks. These are individually compressed and encrypted, and have an entry in an index which contains, encrypted, it's weak and strong checksums and decoded plaintext size. This is all done on the client. + +Why not just encrypt the file, and use the standard rsync algorithm? + +1) Compression cannot be used, since encryption turns the file into essentially random data. This is not very compressible. + +2) Any modification to the file will result in all data after that in the file having different ciphertext (in any cipher mode we might want to use). Therefore the rsync algorithm will only be able to detect "same" blocks up until the first modification. This significantly reduces the effectiveness of the process. + +Note that blocks are not all the same size. The last block in the file is unlikely to be a full block, and if data is inserted which is not a integral multiple of the block size, odd sized blocks need to be created. This is because the server cannot reassemble the blocks, because the contents are opaque to the server. + + +SUBTITLE Modifed algorithm + +To produce a list of the changes to send the new version, the client requests the block index of the file. This is the same step as requesting the weak and strong checksums from the remote side with rsync. + +The client then decrypts the index, and builds a list of the 8 most used block sizes above a certain threshold size. + +The new version of the file is then scanned in exactly the same way as rsync for these 8 block sizes. If a block is found, then it is added to a list of found blocks, sorted by position in the file. If a block has already been found at that position, then the old entry is only replaced by the new entry if the new entry is a "better" (bigger) match. + +The block size covering the biggest file area is searched first, so that most of the file can be skipped over after the first pass without expensive checksumming. + +A "recipe" is then built from the found list, by trivially discarding overlapping blocks. Each entry consists of a number of bytes of "new" data, a block start number, and a number of blocks from the old file. The data is stored like this as a memory optimisation, assuming that files mostly stay the same rather than having all their blocks reordered. + +The file is then encoded, with new data being sent as blocks of data, and references to blocks in the old file. The new index is built completely, as the checksums and size need to be rencrypted to match their position in the index. + + +SUBTITLE Combination on server + +The "diff" which is sent from the client is assembled into a full file on the server, simply by adding in blocks from the old file where they are specified in the block index. + + +SUBTITLE Storage on server + +Given that the server will in general store several versions of a file, combining old and new files to form a new file is not terribly efficient on storage space. Particularly for large multi-Gb database files. + +An alternative scheme is outlined below, however, it is significantly more complex to implement, and so is not implemented in this version. + +1) In the block index of the files, store the file ID of the file which each block is source from. This allows a single file to reference blocks from many files. + +2) When the file is downloaded, the server combines the blocks from all the files into a new file as it is streamed to the client. (This is not particuarly complicated to do.) + +This all sounds fine, until housekeeping is considered. Old versions need to be deleted, without losing any blocks necessary for future versions. + +Instead of just deleting a file, the server works out which blocks are still required, and rebuilds the file omitting those blocks which aren't required. + +This complicates working out how much space a file will release when it is "deleted", and indeed, adds a whole new level of complexity to the housekeeping process. (And the tests!) + +The directory structure will need an additional flag, "Partial file", which specifies that the entry cannot be built as previous blocks are no longer available. Entries with this flag should never be sent to the client. + + + diff --git a/docs/api-notes/lib_backupclient.txt b/docs/api-notes/lib_backupclient.txt new file mode 100644 index 00000000..3e4a079b --- /dev/null +++ b/docs/api-notes/lib_backupclient.txt @@ -0,0 +1,46 @@ +TITLE lib/backupclient + +Classes used on the store and on the server. + +See documentation in the files for more details. + + +SUBTITLE BackupStoreDirectory + +The directory listing class, containing a number of entries, representing files. + + +SUBTITLE BackupStoreFile + +Handles compressing and encrypting files, and decoding files downloaded from the server. + + +SUBTITLE BackupStoreFilename + +An encrypted filename. + + +SUBTITLE BackupStoreFilenameClear + +Derived from BackupStoreFilename, but with the ability to encrypt and decrypt filenames. Client side only. + + +SUBTITLE BackupClientFileAttributes + +Only used on the client -- the server treats attributes as blocks of opaque data. + +This reads attributes from files on discs, stores them, encrypts them, and applies them to new files. + +Also has a static function to generate filename attribute hashes given a struct stat and the filename. + + +SUBTITLE BackupClientRestore + +Routines to restore files from the server onto the client filesystem. + + +SUBTITLE BackupClientCryptoKeys + +This reads the key material from disc, and sets up the crypto for storing files, attributes and directories. + + diff --git a/docs/api-notes/lib_backupstore.txt b/docs/api-notes/lib_backupstore.txt new file mode 100644 index 00000000..8f24eb7b --- /dev/null +++ b/docs/api-notes/lib_backupstore.txt @@ -0,0 +1,30 @@ +TITLE lib/backupstore + +Classes which are shared amongst the server side store utilities, bbstored and bbstoreaccounts. Note also depends on lib/backupclient, as a lot of code is shared between the client and server. + + +SUBTITLE BackupStoreAccountDatabase + +A simple implementation of an account database. This will be replaced with a more suitable implementation. + + +SUBTITLE BackupStoreAccounts + +An interface to the account database, and knowledge of how to initialise an account on disc. + + +SUBTITLE BackupStoreConfigVerify + +The same configuration file is used by all the utilities. This is the Configuration verification structure for this file. + + +SUBTITLE BackupStoreInfo + +The "header" information about an account, specifying current disc usage, space limits, etc. + + +SUBTITLE StoreStructure + +Functions specifying how the files are laid out on disc in the store. + + diff --git a/docs/api-notes/raidfile/RaidFileRead.txt b/docs/api-notes/raidfile/RaidFileRead.txt new file mode 100644 index 00000000..0a5efbf7 --- /dev/null +++ b/docs/api-notes/raidfile/RaidFileRead.txt @@ -0,0 +1,14 @@ +CLASS RaidFileRead + +Read a raid file. + +IOStream interface, plus a few extras, including reading directories and checking that files exist. + + +FUNCTION RaidFileRead::Open + +Open a given raid file -- returns a pointer to a new RaidFileRead object. + +Note that one of two types could be returned, depending on the representation of the file. + + diff --git a/docs/api-notes/raidfile/RaidFileWrite.txt b/docs/api-notes/raidfile/RaidFileWrite.txt new file mode 100644 index 00000000..89500c37 --- /dev/null +++ b/docs/api-notes/raidfile/RaidFileWrite.txt @@ -0,0 +1,36 @@ +CLASS RaidFileWrite + +Interface to writing raidfiles. + +See IOStream interface. + +Some other useful functions are available, see h and cpp files. + + +FUNCTION RaidFileWrite::RaidFileWrite() + +The constructor takes the disc set number and filename of the file you're interested. + + +FUNCTION RaidFileWrite::Open() + +Open() opens the file for writing, and takes an "allow overwrite" flag. + + +FUNCTION RaidFileWrite::Commit() + +Commmit the file, and make it visible to RaidFileRead. If ConvertToRaidNow == true, it will be converted to raid file representation immediately. + +Setting it to false is not a good idea. Later on, it will tell a daemon to convert it in the background, but for now it simply won't be converted. + + +FUNCTION RaidFileWrite::Discard() + +Abort the creation/update. Equivalent to just deleting the object without calling Commit(). + + +FUNCTION RaidFileWrite::Delete() + +Delete a file -- don't need to Open() it first. + + diff --git a/docs/api-notes/raidfile/lib_raidfile.txt b/docs/api-notes/raidfile/lib_raidfile.txt new file mode 100644 index 00000000..0c4244ce --- /dev/null +++ b/docs/api-notes/raidfile/lib_raidfile.txt @@ -0,0 +1,73 @@ +TITLE lib/raidfile + +Implements RAID type abilities in files in userland, along with simple transaction like semantics for writing and reading files. + +IOStream interfaces are used to read and write files. + +Eventually a raid file daemon will be written to "raidify" files in the background. This will allow fast streaming of data to a single disc, followed by splitting into separate raid files later. + +There is an option to disable raidification for use on systems with only one hard disc, or a hardware RAID array. + + +SUBTITLE Controller + +The raid disc sets are managed by RaidFileController. This reads the configuration file, /etc/box/raidfile.conf, and then stores the sets of discs for use by the other classes. + +In the code, files are referenced using an integer set number, and a filename within that set. For example, (0, "dir/subdir/filename.ext"). The RAID file controller turns this into actual physcial filenames transparently. + +The files are devided into blocks, which should match the fragment/block size of the underlying filesystem for maximum space efficiency. + +If the directories specified in the configuration files are all equal, then userland RAID is disabled. The files will not be processed into the redundant parts. + + +SUBTITLE Writing + +Files are written (and modified) using the RaidFileWrite class. This behaves as a seekable IOStream for writing. + +When all data has been written, the file is committed. This makes it available to be read. Processing to split it into the three files can be delayed. + +If the data is not commited, the temporary write file will be deleted. + +RaidFileWrite will optionally refuse to overwrite another file. If it is being overwritten, a read to that file will get the old file until it has been committed. + + +SUBTITLE Reading + +File are opened and read with RaidFileRead::Open. This returns an IOStream (with some extras) which reads a file. + +If the file has been turned into the RAID representation, then if an EIO error occurs on any operation, it will switch transparently into recovery mode where the file will be regenerated from the remaining two files using the parity file. (and log an error to syslog) + + +SUBTITLE Layout on disc + +The directory structure is replicated onto each of the three directories within the disc set. + +Given a filename x, within the disc set, one of the three discs is chosen as the 0th disc for this file. This is done by hashing the filename. This ensures that an equal load is placed on each disc -- otherwise one disc would only be accessed when a RAID file was being written. + +When the file is being written, the actual physical file is "x.rfwX". This just a striaght normal file -- nothing is done to the data. + +When it is commited, it is renamed to "x.rfw". + +Then, when it is turned into the raid representation, it is split across three discs, each file named "x.rf". + +When trying to open a file, the first attempt is for "x.rfw". If this exists, it is used as is. + +If this fails, two of the "x.rf" files are attempted to be opened. If this succeeds, the file is opened in RAID mode. + +This procedure guarenettes that a file will be opened either as a normal file, or as the correct three RAID file elements, even if a new version is being commited at the same time, or another process is working to turn it into a raid file. + + +SUBTITLE Raid file representation + +The file is split into three files, stripe 1, stripe 2 and parity. + +Considering the file as a set of blocks of the size specified in the configuration, odd numbered blocks go in stripe 1, even blocks in stripe 2, and a XOR of the corresponding blocks in parity. + +Thus only two files are needed to recreate the original. + +The complexity comes because the size of the file is not stored inside the files at any point. This means if stripe 2 is missing, it is ambiguous as to how big the file is. + +Size is stored as a 64 bit integer. This is appended to the parity file, unless it can be stored by XORing it into the last 8 bytes of the parity file. + +This scheme is complex to implement (see the RaidFileRead.cpp!) but minimises the disc spaced wasted. + diff --git a/docs/api-notes/win32_build_on_cygwin_using_mingw.txt b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt new file mode 100644 index 00000000..e93af6cd --- /dev/null +++ b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt @@ -0,0 +1,113 @@ +How to build Box Backup on Win32 using Cygwin and MinGW +By Chris Wilson, 2009-03-31 + +(To read this document online with better formatting, browse to: +[http://www.boxbackup.org/trac/wiki/CompileWithMinGW]) + +== MinGW C++ == + +Start by installing Cygwin on your Windows machine from [http://www.cygwin.org/cygwin/]. + +Make sure to select the following packages during installation: + + * 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 + * 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. + +== 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: + + C:\Cygwin\Home\YourUsername + +Make sure you know the full path to this directory. + +If your user name has spaces in it, which is quite common on Windows, +please rename your home directory to one without any spaces, and change +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] + +Open a Cygwin shell, go to the base directory, and unpack OpenSSL: + + tar xzvf openssl-1.0.0a.tar.gz + +Configure OpenSSL for MinGW compilation, and build and install it: + + cd openssl-1.0.0a + ./Configure --prefix=/usr/i686-pc-mingw32/ mingw + make + make install + +== 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: + + tar xjvf pcre-8.10.tar.bz2 + +Configure PCRE for MinGW compilation, and build and install it: + + cd pcre-8.10 + export CFLAGS="-mno-cygwin" + export CXXFLAGS="-mno-cygwin" + ./configure --prefix=/usr/i686-pc-mingw32 + 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: + + find -type f -not \( -wholename .*svn*. \) -exec dos2unix {} \; + +== Compile Box Backup == + +Enter the source directory and configure like this: + + cd trunk + ./infrastructure/mingw/configure.sh + 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'': + + * boxbackup\release\bin\bbackupd\bbackupd.exe + * boxbackup\release\bin\bbackupquery\bbackupquery.exe + * boxbackup\release\bin\bbackupctl\bbackupctl.exe + * openssl\out32dll\libeay32.dll + * 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. + +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/api-notes/win32_build_on_linux_using_mingw.txt b/docs/api-notes/win32_build_on_linux_using_mingw.txt new file mode 100644 index 00000000..2c3e4fe9 --- /dev/null +++ b/docs/api-notes/win32_build_on_linux_using_mingw.txt @@ -0,0 +1,108 @@ +How to build Box Backup for Windows (Native) on Linux using MinGW +By Chris Wilson, 2005-12-07 + +Install the MinGW cross-compiler for Windows: + +- Debian and Ubuntu users can "apt-get install mingw32" +- Fedora and SuSE users can download RPM packages from + [http://mirzam.it.vu.nl/mingw/] + +You will need to know the prefix used by the cross-compiler executables. +It will usually be something like "ix86-mingw32*-". All the binaries in the +cross-compiler package will start with this prefix. The documentation below +assumes that it is "i386-mingw32-". Adjust to taste. + +You will also need to install Wine and the Linux kernel "binary formats" +(binfmt) support, so that you can run Windows executables on Linux, +otherwise the configure scripts will not work properly with a cross-compiler. +On Ubuntu, run: + + apt-get install wine binfmt-support + /etc/init.d/binfmt-support start + +Start by downloading Zlib from [http://www.zlib.net/], unpack and enter +source directory: + + export CC=i386-mingw32-gcc + export AR="i386-mingw32-ar rc" + export RANLIB="i386-mingw32-ranlib" + ./configure + make + make install prefix=/usr/local/i386-mingw32 + +Download OpenSSL 0.9.8b from +[http://www.openssl.org/source/openssl-0.9.8b.tar.gz] + +Unpack and configure: + + tar xzvf openssl-0.9.8b.tar.gz + cd openssl-0.9.8b + ./Configure --prefix=/usr/local/i386-mingw32 mingw + make makefile.one + wget http://www.boxbackup.org/svn/box/chris/win32/support/openssl-0.9.8b-mingw-cross.patch + patch -p1 < openssl-0.9.8b-mingw-cross.patch + make -f makefile.one + make -f makefile.one install + +Download PCRE from +[http://prdownloads.sourceforge.net/pcre/pcre-6.3.tar.bz2?download] + +Unpack: + + tar xjvf pcre-6.3.tar.bz2 + cd pcre-6.3 + +Configure and make: + + export AR=i386-mingw32-ar + ./configure --host=i386-mingw32 --prefix=/usr/local/i386-mingw32/ + make winshared + +If you get this error: + + ./dftables.exe pcre_chartables.c + /bin/bash: ./dftables.exe: cannot execute binary file + make: *** [pcre_chartables.c] Error 126 + +then run: + + wine ./dftables.exe pcre_chartables.c + make winshared + +to complete the build. Finally: + + cp .libs/libpcre.a /usr/local/i386-pc-mingw32/lib + cp .libs/libpcreposix.a /usr/local/i386-pc-mingw32/lib + cp pcreposix.h /usr/local/i386-pc-mingw32/include + +You will need to find a copy of mingwm10.dll that matches your cross-compiler. +Most MinGW distributions should come with it. On Debian and Ubuntu, for some +bizarre reason, you'll find it compressed as +/usr/share/doc/mingw32-runtime/mingwm10.dll.gz, in which case you'll +have to un-gzip it with "gzip -d". Copy it to a known location, e.g. +/usr/local/i386-mingw32/bin. + +Download and extract Box Backup, and change into the base directory, +e.g. boxbackup-0.11rc2. Change the path to mingwm10.dll in parcels.txt to +match where you found or installed it. + +Now configure Box with: + + ./configure --host=i386-mingw32 \ + CXXFLAGS="-mthreads -I/usr/local/i386-mingw32/include" \ + LDFLAGS=" -mthreads -L/usr/local/i386-mingw32/lib" \ + LIBS="-lcrypto -lws2_32 -lgdi32" + make + +or, if that fails, try this: + + export CXX="i386-mingw32-g++" + export AR=i386-mingw32-ar + export RANLIB=i386-mingw32-ranlib + export CFLAGS="-mthreads" + export CXXFLAGS="-mthreads" + export LDFLAGS="-mthreads" + export LIBS="-lcrypto -lws2_32 -lgdi32" + (if you don't have a "configure" file, run "./bootstrap") + ./configure --target=i386-mingw32 + make CXX="$CXX" AR="$AR" RANLIB="$RANLIB" WINDRES="i386-mingw32-windres" diff --git a/docs/api-notes/windows_porting.txt b/docs/api-notes/windows_porting.txt new file mode 100644 index 00000000..ada3b857 --- /dev/null +++ b/docs/api-notes/windows_porting.txt @@ -0,0 +1,100 @@ +TITLE Notes on porting the backup client to Windows + +It should be relatively easy to port the backup client (bbackupd) to Windows. However, the server relies on unlink() behaviour of the UNIX filesystem which is different on the Windows platform, so it will not be so easy to port. + +An installation of perl is required to build the system. The ActiveState port is the easiest to install. + + +SUBTITLE Build environment + +The build environment generation script, makebuildenv.pl, uses perl to scan all the files and generate makefiles. It's rather orientated towards UNIX systems with gcc. + +Probably the easiest way to handle this is to detect the Windows platform, set up a few variables appropriately (in BoxPlatform.pm) and then post-process the generated Makefiles to mould them into something more handy for the MS Windows compiler and toolchain. + +The script itself is a bit messy. It was fine at first, but then the multi-platform thing got in the way. I do intend to rewrite it at some point in the future. + +Make sure your new version defines PLATFORM_WIN32 on the compile line. + +All files #include "Box.h" as the first include file. Use this for pre-compiled headers. Edit BoxPlatform.h to include the Windows headers required, and include a PLATFORM_WIN32 section. The easiest start will be to leave this blank, apart from typedefs for the basic types and any "not supported" #defines you can find. + +Boring bits of the code, such as exceptions and protocol definitions, are autogenerated using perl scripts. This code should be portable without modification. + +I have tried to avoid the things I know won't work with the MS compiler, so hopefully the code will be fairly clean. However, it might be a little easier to use the MinGW compiler [ http://www.mingw.org/ ] just to be consistent with the UNIX version. But I hope this won't be necessary. + +You'll need the latest version of OpenSSL. This was slightly difficult to get to compile last time I tried -- especially if you're determined to use the optimised assembler version. The main difficulty was getting a version which would link properly with the options used in my project, the default libraries selected got in the way. + + +SUBTITLE Porting as UNIX emulation + +Since the daemon uses so few UNIX system calls and with a limited set of options, it seems to make sense to port it by writing emulations of these functions. It's probably nicest to create a lib/win32 directory, and populate this with header files corresponding to the UNIX header files used. These just contain inline functions which map the UNIX calls to Win32 calls. + +File/socket handles may have to be translated. -1 is used as a failure return value and by the code internally to mark an invalid socket handle. (0 is a valid socket handle) + +Of course, some bits of code aren't relevant, so will just be #ifdefed out, or replaced. But this should be minimal. (Only perhaps the small bit relating to filesystem structure -- there aren't really mount points as such.) + + +SUBTITLE File tracking + +The daemon uses the inode number of a file to keep track of files and directories, so when they're renamed they can be moved efficiently on the store. Some unique (per filesystem) number will have to be found and used instead. + +It uses the Berkeley DB to store these on disc. It's likely another storage system will need to be used. (It just has to map the file's unique number into to a 8 byte struct.) + +There is a in-memory implementation for platforms which don't support Berkeley DB, but this isn't so good when the daemon has to be restarted as all the tracking is lost. But it's an easy start. + + +SUBTITLE Expected filesystem behaviour + +File and directories have (at least) two modification times, for contents and attributes. + +For files, the contents modification time must change when the contents change, and the attributes time when the attributes change (and may change when the contents change too.) + +For directories, the contents modification time must change when files or directories are deleted or added. If it changes any more frequently than this, then the client will be slightly less efficient -- it will download the store's directory listing whenever this time changes. The attributes modification time is less important, as the actual attributes are compared and only uploaded if different. + + +SUBTITLE Attributes + +Attributes means file modification times, flags, and filesystem permissions. + +The BackupClientFileAttribute class will need to be extended. Allocate another "attribute type" for the Win32 attributes, and then serialise it in a compatible way -- put your new attribute type in the header, and then a serialised network byte order structure in the rest. The different size of block is handled for you, and the server never looks inside. + +Add code so that under UNIX, Win32 attributes are ignored, and UNIX attributes under Win32. + +It's probably not necessary to worry too much about these for the first version. Not many people seem to use these attributes anyway. + + +SUBTITLE Times + +The system uses it's own 64 bit time type -- see BoxTime.h. Everything is translated to this from the various different system time types, and calculated and stored internally in this form. + + +SUBTITLE Daemon as a Service + +The client is derived from the Daemon class, which implements a daemon. The interface is simple, and it shouldn't be hard to write a compatible class which implements a Windows Service instead. + +Or cheat and run it as a Win32 application. + +Note that the daemon expects to be able to read every file it wants, and will abort a scan and upload run if it gets an error. The daemon must therefore be run with sufficient privileges. It runs as root under UNIX. + + +SUBTITLE Command Socket + +The backup daemon accepts commands from bbackupctl through a UNIX domain socket. When a connection is made, the user ID of the connecting process is checked to see if it's the same user ID as the daemon is running under. + +This may not have any exact analogue under Win32, so another communications scheme may have to be devised. + +This is only actually necessary if the client is to be run in snapshot mode. It can be safely left unimplemented if snapshot mode is not required, or the prompts for it to sync with the server are implemented some other way. + + +SUBTITLE NTFS streams + +If you want to back up NTFS streams, then a generic solution should probably be defined, so that the Mac OS X resource forks can be backed up with the same mechanism. + + +SUBTITLE Source code + +I work on a slightly different version of the source files. A make distribution script adds the license header and removes private sections of code. This means submitted diffs need a slight bit of translation. + + + + + diff --git a/docs/docbook/adminguide.xml b/docs/docbook/adminguide.xml new file mode 100644 index 00000000..edb0a58c --- /dev/null +++ b/docs/docbook/adminguide.xml @@ -0,0 +1,1981 @@ + + +]> + + Box Backup administrator's guide + + + License + + Copyright © 2003 - 2007, Ben Summers and contributors. All rights + reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + + + All use of this software and associated advertising materials + must display the following acknowledgement: This product includes + software developed by Ben Summers and contributors. + + + + The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + + + [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. + + + + Configuration + +
+ System configuration + +
+ Server + + After you've downloaded and compiled the programs you need to + install the programs on your server. As root do the following: + + make install-backup-server + + This assumes that you are installing on the same server that you + compiled the software on. If not, copy the + boxbackup-x.xx-backup-server-OSNAME.tgz file to the server you want to + run on, and install there. For example (on Mac OS X): + + tar zxvf boxbackup-0.10-server-darwin8.5.0.tgz +cd boxbackup-0.10-server-darwin8.5.0 +./install-backup-server + + Then create the user for the backup daemon on the server: + + useradd _bbstored + + Box Backup has a built-in software RAID facility (redundant + array of inexpensive disks) for the backup store. This allows you to + spread the store data over three disks, and recover from the loss of + any one disk without losing data. However, this is now deprecated, and + you are recommended to use the software or hardware RAID facilities of + your operating system instead. Use the following command if you want + to create a simple server without Box Backup RAID: + + mkdir /tmp/boxbackupRepository # Create the directory +chown _bbstored /tmp/boxbackupRepository/ # Change the owner to the new boxbackup daemon user + +/usr/local/sbin/raidfile-config /etc/box/ 1024 /tmp/boxbackupRepository + +#substitute 1024 with the desired blocksize +#substitute /tmp/boxbackupRepository with a directory that exists where you want the backup store located +#/usr/local/sbin/raidfile-config --help shows you the options + + Then create the configuration file /etc/box/bbstored.conf The + hostname is tricky as it is used for two things: The name of the + server in the certificate and the address the server is listening on. + Since you might be using NAT, might move the server around or the + domain name might change, choose a name that describes the server. + When the network address of the server changes, you need to update the + ListenAddresses directive in the + /etc/box/bbstored.conf file. + + /usr/local/sbin/bbstored-config /etc/box hostname _bbstored + + This last step outputs 5 instructions that you must execute to + the letter. A lot of questions are raised on the mailing list because + these steps have not been followed properly. + + TODO: Expand on this. Explain the 5 steps in detail. + + If you want to run the server as a non-root user, look here. +
+ +
+ Certificate Management + + There are two steps involved to create an account. You need to + create the account on the server, and sign a certificate to give the + client permission to connect to the server. + + Running a Certification Authority for TLS (SSL) connections is + not trivial. However, a script to does most of the work in a way which + should be good enough for most deployments. + + + The certificate authority directory is intended to be stored + on another server. It should not be kept on the backup server, in + order to limit the impact of a server compromise. The instructions + and the script assume that it will be kept elsewhere, so will ask + you to copy files to and from the CA. + + + + SSL certificates contain validity dates, including a "valid + from" time. If the clock on the machine which signs the certificates + is not syncronised to the clocks of the machines using these + certificates, you will probably get strange errors until the start + time is reached on all machines. If you get strange errors when + attempting to use new certificates, check the clocks on all machines + (client, store and CA). You will probably just need to wait a while + until the certificates become valid, rather than having to + regenerate them. + + +
+ Set up a Certificate Authority + + It is recommended that you keep your Certificate Authority on + a separate machine than either the client or the server, preferably + without direct network access. The contents of this directory + control who can access your backup store server. + + To setup the basic key structure, do the following: + + /usr/local/sbin/bbstored-certs ca init + + (See OpenSSL notes if you + get an OpenSSL error) + + This creates the directory called ca in + the current directory, and initialises it with basic keys. +
+ +
+ Sign a server certificate + + When you use the bbstored-config script to + set up a config file for a server, it will generate a certificate + request (CSR) for you. Transfer it to the machine with your CA, then + do: + + /usr/local/sbin/bbstored-certs ca sign-server hostname-csr.pem + + This signs the certificate for the server. Follow the + instructions in the output on which files to install on the server. + The CSR file is now no longer needed. Make sure you run this command + from the directory above the directory 'ca'. + + TODO: Explain instructions in output. +
+ +
+ Set up an account + + Choose an account number for the user. This must be unique on + the server, and is presented as a 31 bit number in hex greater than + 0, for example, 1 or 75AB23C. Then on the backup store server, + create the account with: + + /usr/local/sbin/bbstoreaccounts create 75AB23C 0 4096M 4505M + + This looks complicated. The numbers are, in order: + + + + The account number allocated (hex) + + + + The RAID disc set (0 if you use raidfile-config and don't + add a new set) + + + + Soft limit (size) + + + + Hard limit (size) + + + + The sizes are are specified in Mb, Gb, or blocks, depending on + the suffix. 1M specifies 1 Mb, 1G specifies 1 Gb, and 1B specifies 1 + block, the size of which depends on how you have configured the + raidfile system with raidfile-config. + + In this example, I have allocated 4Gb (assuming you use 2048 + byte blocks as per my example) as the soft limit, and 4Gb + 10% as + the hard limit. + + NOTE The sizes specified here are pre-RAID. So if you are + using userland RAID, you are actually allocating two-thirds of this + amount. This means that, when you take compression into account, + that if you allocate 2Gb on the server, it'll probably hold about + 2Gb of backed up files (depending on the compressability of those + files). + + The backup client will (voluntarily) try not to upload more + data than is allowed by the soft limit. The store server will refuse + to accept a file if it would take it over the hard limit, and when + doing housekeeping for this account, try and delete old versions and + deleted files to reduce the space taken to below the soft + limit. + + This command will create some files on disc in the raid file + directories (if you run as root, the utility will change to the user + specified in the bbstored.conf file to write them) and update the + accounts file. A server restart is not required. + + NOTE If you get a message saying 'Exception: RaidFile (2/8)', + the directories you specified in the raidfile.conf are not writable + by the _bbstored user -- fix it, and try again. + + Finally, tell the user their account number, and the hostname + of your server. They will use this to set up the backup client, and + send you a CSR. This has the account number embedded in it, and you + should be sure that it has the right account number in it. + + Sign this CSR with this command: + + /usr/local/sbin/bbstored-certs ca sign 75AB23C-csr.pem + + Don't forget to check that the embedded account number is + correct! Then send the two files back to the user, as instructed by + the script. + + Please read the Troubleshooting page if you have + problems. + + TODO: Link to troubleshooting... +
+
+ +
+ Log Files + + You may wish to see what's going on with the server. Edit + /etc/syslog.conf, and add: + + local6.info /var/log/box +local5.info /var/log/raidfile + + Note: Separators must be tabs, + otherwise these entries will be ignored. + + touch /var/log/box +touch /var/log/raidfile + + Set up log rotation for these new log files. For example, if you + have /etc/newsyslog.conf, add the following lines + to it: + + /var/log/box 644 7 2000 * Z +/var/log/raidfile 644 7 2000 * Z + + If you have /etc/logrotate.d, create a new + file in there (for example + /etc/logrotate.d/boxbackup) containing the + following: + + /var/log/box /var/log/raidfile { + weekly + create + compress + rotate 52 +} + + Then restart syslogd, for example: + + /etc/init.d/syslogd restart +
+ +
+ Configuring a client + + Before you can do any configuration, you need to know the + hostname of the server you will be using, and your account number on + that server. + + Later in the process, you will need to send a certificate + request to the administrator of that server for it to be + signed. + + Installation is covered in the compiling and installing section. + You only need the backup-client parcel. + + It is important that you read all the output of the config + scripts. See the end of this page for an example. + + The backup client has to be run as root, because it needs to + read all your files to back them up, although it is possible to back + up a single user's files by running it as that user. (Tip: specify a + directory other than /etc/box, and then give the + alternate config file as the first argument to + bbackupd). However, it will fall over if you don't + give yourself read access to one of your files. + +
+ Basic configuration + + Run the bbackupd-config script to generate + the configuration files and generate a private key and certificate + request. + + /usr/local/sbin/bbackupd-config /etc/box lazy 999 hostname /var/bbackupd /home + + (See OpenSSL notes if you + get an OpenSSL error) + + The items in bold need to be changed. In order, they are the + account number, the hostname of the server you're using, and + finally, the directories you want backed up. You can include as many + you want here. + + However, the directories you specify must not contain other + mounted file systems within them at any depth. Specify them + separately, one per mount point. No checks are currently made to + catch bad configuration of this nature! + + You may also want to consider changing the mode from lazy to + snapshot, depending on what your system is used for: + + + + Lazy Mode + + + This mode regularly scans the files, with only a rough + schedule. It uploads files as and when they are changed, if + the latest version is more than a set age. This is good for + backing up user's documents stored on a server, and spreads + the load out over the day. + + + + + Snapshot Mode + + + This mode emulates the traditional backup behaviour of + taking a snapshot of the filesystem. The backup daemon does + absolutely nothing until it is instructed to make a backup + using the bbackupctl utility (probably as a cron job), at + which point it uploads all files which have been changed since + the last time it uploaded. + + + + + When you run the config script, it will tell you what you need + to do next. Don't forget to read all the output. An example is shown + at the end of this page, but the instructions for your installation + may be different. +
+ +
+ Certificates + + After you have sent your certificate request off to the server + administrator and received your certificate and CA root back, + install them where instructed by the bbackupd-config script during + basic bbackupd configuration. + + You can then run the daemon (as root) by running + /usr/local/sbin/bbackupd, and of course, adding it + to your system's startup scripts. The first time it's run it will + upload everything. Interrupting it and restarting it will only + upload files which were not uploaded before - it's very + tolerant. + + If you run in snapshot mode, you will need to add a cron job + to schedule backups. The config script will tell you the exact + command to use for your system. + + Please read the Troubleshooting page if you have + problems. + + Remember to make a traditional backup of the keys file, as + instructed. You cannot restore files without it. + + It is recommended that you backup up all of /etc/box as it + will make things easier if you need to restore files. But only the + keys are absolutely essential. + + If you want to see what it's doing in more detail (probably a + good idea), follow the instructions in the server setup to create + new log files with syslog. +
+ +
+ Adding and removing backed up locations + + By editing the /etc/box/bbackupd.conf file, you can add and + remove directories to back up - see comments in this file for help. + Send bbackupd a HUP signal after you modify it. + + When you remove a location, it will not be marked as deleted + immediately. Instead, bbackupd waits about two days before doing so, + just in case you change your mind. After this, it will be eventually + removed from the store by the housekeeping process. Run as + root. + + The backup client is designed to be run as root. It is + possible to run without root, but this is not recommended. Clock + synchronisation for file servers. + + If you are using the backup client to backup a filesystem + served from a fileserver, you should ideally ensure that the + fileserver clocks are synchronised with the fileserver. + + bbackupd will cope perfectly well if the clocks are not + synchronised. Errors up to about half an hour cause no problems. + Larger discrepancies cause a loss of efficiency and the potential to + back up a file during a write process. + + There is a configuration parameter MaxFileTimeInFuture, which + specifies how far in the future a file must be for it to be uploaded + as soon as it is seen. You should not need to adjust this (default + is 2 days). Instead, get those clocks synchronised. Excluding files + and directories from the backup. + + Within the bbackupd.conf file, there is a section named + BackupLocations which specifies which locations on disc should be + backed up. It has subsections, each of which 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. + + 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. +
+ +
+ Example configuration output + + This is an example of output from the bbstored-config + script. + + + Follow the instructions output by your script, not the ones + here -- they may be different for your system. + + + /usr/local/sbin/bbackupd-config /etc/box lazy 51 server.example.com /var/bbackupd /home /etc/samba + +Setup bbackupd config utility. + +Configuration: + Writing configuration file: /etc/box/bbackupd.conf + Account: 51 + Server hostname: server.example.com + Directories to back up: + /home + /etc/samba + +Note: If other file systems are mounted inside these directories, then problems may occur +with files on the store server being renamed incorrectly. This will cause efficiency +problems, but not affect the integrity of the backups. + +WARNING: Directories not checked against mountpoints. Check mounted filesystems manually. + +Creating /etc/box... +Creating /etc/box/bbackupd +Generating private key... + [OpenSSL output omitted] + +Generating keys for file backup +Writing notify script /etc/box/bbackupd/NotifyStoreFull.sh +Writing configuration file /etc/box/bbackupd.conf + +=================================================================== + +bbackupd basic configuration complete. + +What you need to do now... + +1) Make a backup of /etc/box/bbackupd/51-FileEncKeys.raw + This should be a secure offsite backup. + Without it, you cannot restore backups. Everything else can + be replaced. But this cannot. + KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. + +2) Send /etc/box/bbackupd/51-csr.pem + to the administrator of the backup server, and ask for it to + be signed. + +3) The administrator will send you two files. Install them as + /etc/box/bbackupd/51-cert.pem + /etc/box/bbackupd/serverCA.pem + after checking their authenticity. + +4) You may wish to read the configuration file + /etc/box/bbackupd.conf + and adjust as appropraite. + + There are some notes in it on excluding files you do not + wish to be backed up. + +5) Review the script + /etc/box/bbackupd/NotifyStoreFull.sh + and check that it will email the right person when the store + becomes full. This is important -- when the store is full, no + more files will be backed up. You want to know about this. + +6) Start the backup daemon with the command + /usr/local/sbin/bbackupd + in /etc/rc.local, or your local equivalent. + Note that bbackupd must run as root. + +=================================================================== + + Remember to make a secure, offsite backup of your backup keys, + as described in Basic + configuration above. If you do not, and that key is lost, you + have no backups. +
+
+ +
+ Configuration Options + + Box Backup has many options in its configuration file. We will + try to list them all here. + + First of all, here is an example configuration file, for + reference: + + + Example Configuration File + + StoreHostname = localhost +AccountNumber = 0x2 + +KeysFile = /etc/box/2-FileEncKeys.raw +CertificateFile = /etc/box/2-cert.pem +PrivateKeyFile = /etc/box/2-key.pem +TrustedCAsFile = /etc/box/serverCA.pem +DataDirectory = /var/run/boxbackup +NotifyScript = /etc/box/NotifySysadmin.sh +CommandSocket = /var/run/box/bbackupd.sock + +UpdateStoreInterval = 86400 +MinimumFileAge = 3600 +MaxUploadWait = 7200 +FileTrackingSizeThreshold = 65536 +DiffingUploadSizeThreshold = 65536 +MaximumDiffingTime = 20 +ExtendedLogging = no +LogAllFileAccess = yes + +Server +{ + PidFile = /var/run/bbackupd.pid +} +BackupLocations +{ + etc + { + Path = /etc + } + home + { + Path = /home + ExcludeDir = /home/shared + ExcludeDir = /home/chris/.ccache + ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache + } +} + + + As you can see from the example above, the configuration file + has a number of subsections, enclosed in curly braces {}. Some options + appear outside of any subsection, and we will refer to these as root options. The available options in + each section are described below. + + Every option has the form name = value. Names are + not case-sensitive, but values are. Depending on the option, the value + may be: + + + + a path (to a file or directory); + + + + a number (usually in seconds or bytes); + + + + a boolean (the word Yes or No); + + + + a hostname (or IP address). + + + + Paths are specified in native format, i.e. a full Windows path + with drive letter on Windows clients, or a full Unix path on Unix + clients. + + + Example: + + StoreObjectInfoFile = + /var/state/boxbackup/bbackupd.dat + + StoreObjectInfoFile = C:\Program Files\Box + Backup\data\bbackupd.dat + The use of relative paths (which do not start with a + forward slash on Unix, or a drive specification on Windows) is + possible but not recommended, since they are interpreted relative to + the current working directory when bbackupd was started, which is + liable to change unexpectedly over time. + + Numbers which start with "0x" are interpreted as hexadecimal. + Numbers which do not start with "0x" are interpreted as + decimal. + +
+ Root Options + + These options appear outside of any subsection. By convention + they are at the beginning of the configuration file. + + Some options are required, and some are optional. + + + + StoreHostname (required) + + + The Internet host name (DNS name) or IP address of the + server. This is only used to connect to the server. + + + + + AccountNumber (required) + + + The number of the client's account on the server. This + must be provided by the server operator, and must match the + account number in the client's certificate, otherwise the + client will not be able to log into the server. + + The account number may be specified in hexadecimal + (starting with 0x, as in the example above) or in decimal, but + since the server operator works in hexadecimal, that format is + highly recommended and is the default. + + + + + KeysFile (required) + + + The path to the file containing the encryption key used + for data encryption of client file data and filenames. This is + the most important file to keep safe, since without it your + backups cannot be decrypted and are useless. Likewise, if an + attacker gets access to this key and to your encrypted + backups, he can decrypt them and read all your data. + + Do not change the encryption key without deleting all + files from the account on the server first. None of your old + files on the store will be readable if you do so, and if you + change it back, none of the files uploaded with the new key + will be readable. + + + + + CertificateFile (required) + + + The path to the OpenSSL client certificate in PEM + format. This is supplied by the server operator in response to + the certificate request which you send to them. Together with + the PrivateKeyFile, this provides access to the store server + and the encrypted data stored there. + + It is not critical to protect this file or to back it up + safely, since it can be regenerated by creating a new + certificate request, and asking the server operator to sign + it. You may wish to back it up, together with the + PrivateKeyFile, to avoid this inconvenience if you lose all + your data and need quick access to your backups. + + If you do back them up, you should keep them in a + separate location to the KeysFile, since any person holding + the KeysFile and the PrivateKeyFile can gain access to your + encrypted data and decrypt it. + + + + + PrivateKeyFile (required) + + + The path to the OpenSSL private key in PEM format. This + is generated at the same time as the certificate request, but + there is no need to send it to the server operator, and you + should not do so, in case the communication is intercepted by + an attacker. Together with the CertificateFile, this provides + access to the store server and the encrypted data stored + there. + + See the notes under CertificateFile for information + about backing up this file. + + + + + TrustedCAsFile (required) + + + The path to the OpenSSL certificate of the Client + Certificate Authority (CCA), in PEM format. This is supplied + by the server operator along with your account details, or + along with your signed client certificate. This is used to + verify that the server which you are connecting to is + authorised by the person who signed your certificate. It + protects you against DNS and ARP poisoning and IP spoofing + attacks. + + + + + DataDirectory (required) + + + The path to a directory where bbackupd will keep local + state information. This consists of timestamp files which + identify the last backup start and end times, used by + bbackupquery to determine whether files + have changed, and optionally a database of inode numbers, + which are used to check for files being renamed. The database + is only saved if Box Backup is built with Berkeley Database + (BDB) support. + + + + + NotifyScript (optional) + + + The path to the script or command to run when the Box + Backup client detects an error during the backup process. This + is normally used to notify the client system administrator by + e-mail when a backup fails for any reason. + + The script or command is called with one of the + following additional arguments to identify the cause of the + problem: + + + + store-full + + + The backup store is full. No new files are being + uploaded. If some files are marked as deleted, they + should be removed in due course by the server's + housekeeping process. Otherwise, you need to remove some + files from your backup set, or ask the store operator + for more space. + + + + + read-error + + + One or more files which were supposed to be backed + up could not be read. This could be due to: + + running the server as a non-root user; + + + + backing up a mounted filesystem such as + NFS; + + + + access control lists being applied to some + files; + + + + SELinux being enabled; + + + + trying to back up open files under + Windows; + + + + strange directory permissions such as 0000 or + 0400. + + Check the client logs, e.g. + /var/log/bbackupd on Unix, or the Windows Event Viewer + in Control Panel > Administrative Tools, for more + information about which files are not being backed up + and why. + + + + + backup-error + + + There was a communications error with the server, + or an unexpected exception was encountered during a + backup run. Check the client logs, e.g. + /var/log/box on Unix, or the + Windows Event Viewer in Control Panel > + Administrative Tools, for more information about the + problem. + + You may wish to check your Internet access to the + server, check that the server is running, and ask your + server operator to check your account on the + server. + + + + + + + + CommandSocket (optional) + + + The path to the Unix socket which + bbackupd creates when running, and which + bbackupctl uses to communicate with it, for + example to force a sync or a configuration reload. If this + option is omitted, no socket will be created, and + bbackupctl will not function. + + Unix sockets appear within the filesystem on Unix, as a + special type of file, and must be created in a directory which + exists and to which bbackupd has write access, and bbackupctl + has read access. + + On Windows, the path is ignored, and a named + pipe is created instead. This does not currently + have any security attached, so it can be accessed by any user. + Unlike a Unix socket it can also be accessed remotely. Please + use this option with extreme caution on Windows, and only on + fully trusted networks. + + + + + AutomaticBackup (optional) + + + Enable or disable the client from connecting + automatically to the store every + UpdateStoreInterval seconds. When + enabled (set to Yes), the client is in + Lazy Mode. When disabled (set to + No), it is in Snapshot + Mode. This setting is optional, and the default + value is Yes. + + + + + UpdateStoreInterval (required) + + + The approximate time between successive connections to + the server, in seconds, when the client is in Lazy + Mode. The actual time is randomised slightly to + prevent "rush hour" traffic jams on the server, where many + clients try to connect at the same time. + + This value is ignored if the client is in + Snapshot Mode. However, it is still + required. It can be set to zero in this case. + + You will probably need to experiment with the value of + this option. A good value to start with is probably 86400 + seconds, which is one day. + + + + + MinimumFileAge (required) + + + The number of seconds since a file was last modified + before it will be backed up. The reason for this is to avoid + repeatedly backing up files which are repeatedly changing. A + good value is about 3600 seconds (one hour). If set to zero, + files which have changed will always be backed up on the next + backup run. + + The MaxUploadWait option + overrides this option in some circumstances. + + + + + MaxUploadWait (required) + + + The number of seconds since a file was last uploaded + before it will be uploaded again, even if it keeps changing. + The reason for this is to ensure that files which are + continuously modified are eventually uploaded anyway. This + should be no less than the value of + MinimumFileAge. A good value is about + 14400 seconds (4 hours). + + + + + MaxFileTimeInFuture (optional) + + + The maximum time that a file's timestamp can be in the + future, before it will be backed up anyway. Due to clock + synchronisation problems, it is inevitable that you will + occasionally see files timestamped in the future. Normally, + for files which are dated only slightly in the future, you + will want to wait until after the file's date before backing + it up. However, for files whose dates are very wrong (more + than a few hours) you will normally prefer to back them up + immediately. + + A good value is about 7200 seconds (2 hours) to cope + with potential problems when moving in and out of daylight + saving time, if applicable in your timezone. The default + value, if this setting is not provided, is 172800 seconds (2 + days). + + + + + FileTrackingSizeThreshold (required) + + + The minimum size of files which will be tracked by inode + number to detect renames. It is not worth detecting renames of + small files, since they are quick to upload again in full, and + keeping their inode numbers in memory increases the client's + memory usage and slows down searches. Larger files should be + tracked to avoid wasting space on the store and long + uploads. + + A good value is about 65536 bytes (64 kilobytes). + + + + + DiffingUploadSizeThreshold (required) + + + The minimum size of files which will be compared to the + old file on the server, and for which only changes will be + uploaded. It is not worth comparing small files, since they + are quick to upload again in full, and sending the entire file + reduces the risk of data loss if the store is accidentally + corrupted. Larger files should have only their differences + uploaded to avoid wasting space on the store and long + uploads. + + A good value is about 65536 bytes (64 kilobytes). + + + + + MaximumDiffingTime (optional) + + + The maximum time for which the client will attempt to + find differences between the current version and the old + version in the store, before giving up and uploading the + entire file again. Very large files (several gigabytes) may + take a very long time to scan for changes, but would also take + a very long time to upload again and use a lot of space on the + store, so it is normally worth omitting this value. + + Use this option only if, for some bizarre reason, you + prefer to upload really large files in full rather than spend + a long time scanning them for changes. + + + + + KeepAliveTime (optional) + + + The interval (in seconds) between sending Keep-Alive + messages to the server while performing long operations such + as finding differences in large files, or scanning large + directories. + + These messages ensure that the SSL connection is not + closed by the server, or an intervening firewall, due to lack + of activity. + + The server will normally wait up to 15 minutes (900 + seconds) before disconnecting the client, so the value should + be given and should be less than 900. Some firewalls may time + out inactive connections after 10 or 5 minutes. + + A good value is 300 seconds (5 minutes). You may need to + reduce this if you frequently see TLSReadFailed or + TLSWriteFailed errors on the client. + + + + + StoreObjectInfoFile (optional) + + + Enables the use of a state file, which stores the + client's internal state when the client is not running. This + is useful on clients machines which are frequently shut down, + for example desktop and laptop computers, because it removes + the need for the client to recontact the store and rescan all + directories on the first backup run, which may take some time. + This feature is somewhat experimental and not well tested. + + + This is option is disabled by default, in which case the + state is stored in memory only. The value is the path to the + state file. + + + + + ExtendedLogging (optional) + + + Enables the connection debugging mode of the client, + which writes all commands sent to or received from the server + to the system logs. This generates a lot + of output, so it should only be used when instructed, or when + you suspect a connection problem or client-server protocol + error (and you know how to interpret the output). + + This is a boolean value, which may be set to + Yes or No. The default is of + course No. + + + + + ExtendedLogFile (optional, new in 0.11) + + + Enables the same debugging output as + ExtendedLogging, but written to a file + instead of the system logs. This is useful if you need + extended logging, but do not have access to the system logs, + for example if you are not the administrator of the + computer. + + The value is the path to the file where these logs will + be written. If omitted, extended logs will not be written to a + file. This is entirely independent of the + ExtendedLogging option. It does not + make much sense to use both at the same time. + + + + + LogAllFileAccess (optional, new in 0.11) + + + Enables logging of all local file and directory access, + file uploads (full and differential), and excluded files. This + may be useful if the client is failing to upload a particular + file, or crashing while trying to upload it. The logs will be + sent to the system log or Windows Event Viewer. + + This generates a lot + of output, so it should only be used when instructed, or when + you suspect that bbackupd is skipping some files and want to + know why. Because it is verbose, the messages are hidden by + default even if the option is enabled. To see them, you must + run bbackupd with at least one -v option. + + This is a boolean value, which may be set to + Yes or No. The default is of + course No. + + + + + SyncAllowScript (optional) + + + The path to the script or command to run when the client + is about to start an automatic backup run, and wishes to know + whether it is safe to do so. This is useful for clients which + do not always have access to the server, for example laptops + and computers on dial-up Internet connections. + + The script should either output the word + now if the backup should proceed, or else a + number, in seconds, which indicates how long the client should + wait before trying to connect again. Any other output will + result in an error on the client, and the backup will not + run. + + This value is optional, and by default no such script is + used. + + + +
+ +
+ Server Section + + These options appear within the Server subsection, which is at + the root level. + + + + PidFile + + + This option enables the client to write its processs + identifier (PID) to the specified file after starting. The + file will be deleted when the client daemon exits for any + reason. This is disabled by default, but is recommended + whenever you run the client daemon as a daemon (in the + background), which is usually the case. This file can be used + by scripts to determine whether the daemon is still running, + and to send it messages to reload its configuration or to + terminate. + + + Example Server Section + + Server +{ + PidFile = /var/state/boxbackup/bbackupd.pid +} + + + + +
+ +
+ Backup Locations Section + + This section serves only as a container for all defined backup + locations. + + + Example Backup Locations Section + + BackupLocations +{ + etc + { + Path = /etc + } + home + { + Path = /home + ExcludeDir = /home/shared + ExcludeDir = /home/chris/.ccache + ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache + } +} + + + Each subsection is a backup location. The name of the + subsection is the name that will be used on the server. The root + directory of the account on the server contains one subdirectory per + location. The name should be simple, not containing any spaces or + special characters. + + If you do not define any locations, the client will not back + up any files! + + It is currently not recommended to back up the root directory + of the filesystem on Unix. Box Backup is designed to back up + important data and configuration files, not full systems. + Nevertheless, nothing prevents you from doing so if you + desire. + + On Windows, it is currently not possible to back up files + which are open (currently in use), such as open documents in + Microsoft Office, and system files such as the registry and the + paging file. You will get an error for each open file which the + client attempts to back up. Once the file has been closed, it will + be backed up normally. System files will always be open, and should + be excluded from your backups. +
+
+
+
+ + + Administration + + This chapter deals with the dauily running and management of the Box + Backup system. It explains most day-to-day tasks. + +
+ Regular Maintenance + + The steps involved in maintaining and keeping the backup sets + healthy are outlined in this section. + +
+ Controlling a backup client + + The bbackupctl program sends control commands to the bbackupd + daemon. It must be run as the same user as the daemon, and there is no + exception for root. + + The command line syntax is: + + /usr/local/sbin/bbackupctl [-q] [-c config-file] command + + The -q option reduces the amount of output the program emits, + and -c allows an alternative configuration file to be + specified. + + Valid commands are: + + + + terminate + + Stop the bbackupd daemon now (equivalent to kill) + + + + reload + + Reload the configuration file (equivalent to kill + -HUP) + + + + sync + + Connect to the server and synchronise files now + + + + bbackupctl communicates with + the server via a UNIX domain socket, specified in bbackupd.conf with + the CommandSocket directive. This does not need to be specified, and + bbackupd will run without the command + socket, but in this case bbackupctl will not be able to communicate + with the daemon. + + Some platforms cannot check the user id of the connecting + process, so this command socket becomes a denial of service security + risk. bbackupd will warn you when it + starts up if this is the case on your platform, and you should + consider removing the CommandSocket directive on these + platforms. +
+ +
+ Using bbackupctl to perform snapshots + + bbackupctl's main purpose is to + implement snapshot based backups, emulating the behaviour of + traditional backup software. + + Use bbackupd-config to write a configuration file in snapshot + mode, and then run the following command as a cron job. + + /usr/local/sbin/bbackupctl -q sync + + This will cause the backup daemon to upload all changed files + immediately. bbackupctl will exit + almost immediately, and will not output anything unless there is an + error. +
+ +
+ Checking storage space used on the server + +
+ From the client machine + + bbackupquery can tell you how much space is used on the server + for this account. Either use the usage command in interactive mode, + or type: + + /usr/local/sbin/bbackupquery -q usage quit + + to show the space used as a single command. +
+ +
+ On the server + + bbstoreaccounts allows you to query the space used, and change + the limits. To display the space used on the server for an account, + use: + + /usr/local/sbin/bbstoreaccounts info 75AB23C + + To adjust the soft and hard limits on an account, use: + + /usr/local/sbin/bbstoreaccounts setlimit 75AB23C new-soft-limit new-hard-limit + + You do not need to restart the server. +
+
+ +
+ Verify and restore files + + Backups are no use unless you can restore them. The bbackupquery + utility does this and more. + + You don't provide any login information to it, as it just picks + up the data it needs from /etc/box/bbackupd.conf. You should run it as + root so it can find everything it needs. + + Full documentation can be found in the bbackupquery manual page. It follows + the model of a command line sftp client quite closely. + + TODO: Link to bbackupquery man-page here. + + On systems where GNU readline is available (by default) it uses + that for command line history and editing. Otherwise it falls back to + very basic UNIX text entry. + + TODO: Did the readline dependency change to editline? + +
+ Using bbackupquery + + bbackupquery is the tool you use to verify, restore and + investigate your backup files with. When invoked, it simply logs + into the server using the certificates you have listed in + bbackupd.conf. + + After you run bbackupquery, you will see a prompt, allowing + you to execute commands. The list (or ls) command lets you view + files in the store. It works much like unix ls, but with different + options. An example: + + [pthomsen@host bbackupquery]$ bbackupquery +Box Backup Query Tool v0.10, (c) Ben Summers and contributors 2003-2006 +Using configuration file /etc/box/bbackupd.conf +Connecting to store... +Handshake with store... +Login to store... +Login complete. + +Type "help" for a list of commands. + +query > ls +00000002 -d---- mp3 +00000003 -d---- video +00000004 -d---- home-pthomsen +00000005 -d---- root +query > + + The ls commands shows the directories that are backed up. Now + we'll take a closer look at the home-pthomsen directory: + + query > cd home-pthomsen +query > ls +00002809 f----- sample.tiff +0000280a f----- s3.tiff +0000280b f----- s4.tiff +0000280d f----- s2.tiff +0000280e f----- foo.pdf +0000286c f----- core.28720 +0000339a -d---- .emacs.d +0000339d -d---- bbackup-contrib +00003437 f----- calnut.compare.txt +0000345d f----- DSCN1783.jpg +0000345e f----- DSCN1782.jpg +query > + + The ls command takes the following options; + + + + -r -- recursively list + all files + + + + -d -- list deleted + files/directories + + + + -o -- list old versions + of files/directories + + + + -I -- don't display + object ID + + + + -F -- don't display + flags + + + + -t -- show file + modification time (and attr mod time if has the object has + attributes, ~ separated) + + + + -s -- show file size in + blocks used on server (only very approximate indication of size + locally) + + + + The flags displayed from the ls command are as follows: + + + f = file + + d = directory + + X = deleted + + o = old version + + R = remove from server as soon as marked deleted or + old + + a = has attributes stored in directory record which + override attributes in backup file + +
+ +
+ Verify backups + + As with any backup system, you should frequently check that + your backups are working properly by comparing them. Box Backup + makes this very easy and completely automatic. All you have to do is + schedule the bbackupquery compare command to run + regularly, and check its output. You can run the command manually as + follows: + + /usr/local/sbin/bbackupquery "compare -a" quit + + This command will report all the differences found between the + store and the files on disc. It will download everything, so may + take a while. You should expect to see some differences on a typical + compare, because files which have recently changed are unlikely to + have been uploaded yet. It will also tell you how many files have + been modified since the last backup run, since these will normally + have changed, and such failures are expected. + + You are strongly recommended to add this command as a + cron job, at least once a month, and to check the + output for anything suspicious, particularly a large number of + compare failures, failures on files that have not been modified, or + any error (anything except a compare mismatch) that occurs during + the compare operation. + + Consider keeping a record of these messages and comparing them + with a future verification. + + If you would like to do a "quick" check which just downloads + file checksums and compares against that, then run: + + /usr/local/sbin/bbackupquery "compare -aq" quit + + However, this does not check that the file attributes are + correct, and since the checksums are generated on the client they + may not reflect the data on the server if there is a problem -- the + server cannot check the encrypted contents. View this as a quick + indication, rather than a definite check that your backup verifies + correctly. +
+ +
+ Restore backups + + You will need the keys file created when you configured the + server. Without it, you cannot restore the files; this is the + downside of encrypted backups. However, by keeping the small keys + file safe, you indirectly keep your entire backup safe. + + The first step is to recreate the configuration of the backup + client. It's probably best to have stored the /etc/box directory + with your keys. But if you're recreating it, all you really need is + to have got the login infomation correct (ie the certs and + keys). + + Don't run bbackupd yet! It will mark all your files as deleted + if you do, which is not hugely bad in terms of losing data, just a + major inconvenience. (This assumes that you are working from a blank + slate. If you want to restore some files to a different location, + it's fine to restore while bbackupd is running, just do it outside a + backed up directory to make sure it doesn't start uploading the + restored files.) + + Type: + + /usr/local/sbin/bbackupquery + + to run it in interactive mode. + + Type: + + list + + to see a list of the locations stored on the server. + + For each location you want to restore, type: + + restore name-on-server local-dir-name + + The directory specified by local-dir-name must not exist yet. + If the restore is interrupted for any reason, repeat the above + steps, but add the -r flag to the + restore command to tell it to resume. +
+ +
+ Retrieving deleted and old files + + Box Backup makes old versions of files and files you have + deleted available, subject to there being enough disc space on the + server to hold them. + + This is how to retrieve them using bbackupquery. Future + versions will make this far more user-friendly. + + Firstly, run bbackupquery in interactive mode. It behaves in a + similar manner to a command line sftp client. + + /usr/local/sbin/bbackupquery + + Then navigate to the directory containing the file you want, + using list, cd and pwd. + + query > cd home/profiles/USERNAME + + List the directory, using the "o" option to list the files + available without filtering out everything apart from the current + version. (if you want to see deleted files as well, use list + -odt) + + query > list -ot +00000078 f--o- 2004-01-21T20:17:48 NTUSER.DAT +00000079 f--o- 2004-01-21T20:17:48 ntuser.dat.LOG +0000007a f--o- 2004-01-21T17:55:12 ntuser.ini +0000007b f---- 2004-01-12T15:32:00 ntuser.pol +0000007c -d--- 1970-01-01T00:00:00 Templates +00000089 -d--- 1970-01-01T00:00:00 Start Menu +000000a0 -d--- 1970-01-01T00:00:00 SendTo +000000a6 -d--- 1970-01-01T00:00:00 Recent +00000151 -d--- 1970-01-01T00:00:00 PrintHood +00000152 -d--- 1970-01-01T00:00:00 NetHood +00000156 -d--- 1970-01-01T00:00:00 My Documents +0000018d -d--- 1970-01-01T00:00:00 Favorites +00000215 -d--- 1970-01-01T00:00:00 Desktop +00000219 -d--- 1970-01-01T00:00:00 Cookies +0000048b -d--- 1970-01-01T00:00:00 Application Data +000005da -d--- 1970-01-01T00:00:00 UserData +0000437e f--o- 2004-01-24T02:45:43 NTUSER.DAT +0000437f f--o- 2004-01-24T02:45:43 ntuser.dat.LOG +00004380 f--o- 2004-01-23T17:01:29 ntuser.ini +00004446 f--o- 2004-01-24T02:45:43 NTUSER.DAT +00004447 f--o- 2004-01-24T02:45:43 ntuser.dat.LOG +000045f4 f---- 2004-01-26T15:54:16 NTUSER.DAT +000045f5 f---- 2004-01-26T15:54:16 ntuser.dat.LOG +000045f6 f---- 2004-01-26T16:54:31 ntuser.ini + + (this is a listing from a server which is used as a Samba + server for a network of Windows clients.) You now need to fetch the + file using it's ID, rather than it's name. The ID is the hex number + in the first column. Fetch it like this: + + query > get -i 0000437e NTUSER.DAT +Object ID 0000437e fetched successfully. + + The object is now available on your local machine. You can use + lcd to move around, and sh ls to list directories on your local + machine. +
+
+
+ +
+ Fixing corruptions of store data + + This section gives help on what to do if your server has suffered + corruption, for example, after an unclean shutdown or other operating + system or hardware problem. + + In general, as updates to the store are made in an atomic manner, + the most likely result is wasted disc space. However, if really bad + things happen, or you believe that there is a lot of wasted space, then + these instructions will help to restore your data. + + You know you will need to do something if you get strange errors, + and bbackupd attempts to contact the server every 100 seconds or so. Or + if one of the discs in your RAID disc set has failed. + + After following these instructions, the end result will be that + bbackupquery will be able to see all the files which were stored on your + server, and retrieve them. Some of them may be in lost+found directories + in the root of the store (or in their original position if they have + been moved) but they will all be able to be retrieved. + + After you have retrieved the files you want, bbackupd will upload + new versions where necessary, and after about two days, mark any + lost+found directories as deleted. Finally, those directories will be + removed by the housekeeping process on the server. + + These instructions assume you're working on account 1234. Replace + this with the account number that you actually want to check (the one + that is experiencing errors). These steps will need to be repeated for + all affected accounts. + +
+ Stop bbackupd + + First, make sure that bbackupd is not running on the client + machine for the account you are going to recover. Use + bbackupctl terminate to stop it. This step is not + strictly necessary, but is recommended. During any checks on the + account, bbackupd will be unable to log in, and after they are + complete, the account is marked as changed on the server so bbackupd + will perform a complete scan. +
+ +
+ Are you using RAID on the server? + + The raidfile recovery tools have not been written, and probably + will not be, since Box Backup RAID is deprecated. However, when two + out of three files are available, the server will successfully allow + access to your data, even if it complains a lot in the logs. The best + thing to do is to fix the accounts, if necessary, and retrieve any + files you need. Then move the old store directories aside (in case you + need them) and start afresh with new accounts, and let the clients + upload all their data again. +
+ +
+ Check and fix the account + + First, run the check utility, and see what errors it + reports. + + /usr/local/sbin/bbstoreaccounts check 1234 + + This will take some time, and use a fair bit of memory (about 16 + bytes per file and directory). If the output looks plausible and + reports errors which need fixing, run it again but with the fix + flag: + + /usr/local/sbin/bbstoreaccounts check 1234 fix + + This will fix any errors, and remove unrecoverable files. + Directories will be recreated if necessary. + + NOTE: The utility may adjust + the soft and hard limits on the account to make sure that housekeeping + will not remove anything -- check these afterwards. +
+ +
+ Grab any files you need with bbackupquery + + At this point, you will have a working store. Every file which + was on the server, and wasn't corrupt, will be available. + + On the client, use bbackupquery to log in and examine the store. + (type help at the prompt for instructions). Retrieve any files you + need, paying attention to any lost+found directories in the root + directory of the store. + + You can skip this step if you are sure that the client machine + is fine -- in this case, bbackupd will bring the store up to + date. +
+ +
+ Restart bbackupd + + Restart bbackupd on the client machine. The store account will + be brought up to date, and files in the wrong place will be marked for + eventual deletion. +
+
+ +
+ Troubleshooting + + If you are trying to fix a store after your disc has been + corrupted, see Fixing corruptions of + store data. + + Unfortunately, the error messages are not particularly helpful at + the moment. This page lists some of the common errors, and the most + likely causes of them. + + When an error occurs, you will see a message like 'Exception: + RaidFile/OSFileError (2/8)' either on the screen or in your log files. + (it is recommended you set up another log file as recommended in the + server setup instructions.) + + This error may not be particularly helpful, although some do have + extra information about probable causes. To get further information, + check the ExceptionCodes.txt file in the root of the distribution. This + file is generated by the ./configure script, so you will need to have + run that first. + + Some common causes of exceptions are listed below. + + Please email me with any other codes you get, and I will let you + know what they mean, and add notes here. + +
+ RaidFile (2/8) + + This is found either when running bbstoreaccounts or in the + bbstored logs. + + Problem: The directories you + specified in the raidfile.conf are not writable by the _bbstored + user. + + Resolution: Change permissions + appropriately. +
+ +
+ Common (1/2) + + This usually occurs when the configuration files can't be + opened. + + Problem: You created your + configurations in non-standard locations, and the programs cannot find + them. + + Resolution: Explicitly specify + configuration file locations to daemons and programs. For + example + + /usr/local/sbin/bbstored /some/other/dir/bbstored.config /usr/local/sbin/bbackupquery -c /some/other/dir/bbackupd.config + + (daemons specify the name as the first argument, utility + programs with the -c option). + + Problem: bbstored can't find + the raidfile.conf file specified in bbstored.conf. + + Resolution: Edit bbstored.conf + to point to the correct location of this additional configuration + file. +
+ +
+ Server (3/16) + + The server can't listen for connections on the IP address + specified when you configured it. + + Problem: This probably means + you've specified the wrong hostname to bbstored-config -- maybe your + server is behind a NAT firewall? + + Resolution: Edit bbstored.conf + and correct the ListenAddresses line. You should replace the server + address with the IP address of your machine. +
+ +
+ Connection (7/x) + + These errors all relate to connections failing -- you may see + them during operation if there are network failures or other problems + between the client and server. The backup system will recover from + them automatically. + +
+ Connection (7/30) - SSL problems + + Log snippet from client side: + + bbackupd[1904]: Opening connection to server xxxx.xxx... +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01 +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:asn1 encoding routines:ASN1_verify:EVP lib +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed +bbackupd[1904]: TRACE: Exception thrown: ConnectionException(Conn_TLSHandshakeFailed) at SocketStreamTLS.cpp(237) +bbackupd[1904]: Exception caught (7/30), reset state and waiting to retry... + + And from the server: + + bbstored[19291]: Incoming connection from xx.xxx.xx.xxx port xxxxx (handling in child xxxxx) +bbstored[21588]: SSL err during Accept: error:xxxxxxxx:SSL routines:SSL3_READ_BYTES:tlsv1 alert decrypt error +bbstored[21588]: in server child, exception Connection TLSHandshakeFailed (7/30) -- terminating child + + Solution: Create a new CA on + the server side and re-generate the client certificate. Re-creating + the client certificate request is not necessary. +
+
+ +
+ Advanced troubleshooting + + If this really doesn't help, then using the DEBUG builds of the + system will give you much more information -- a more descriptive + exception message and the file and line number where the error + occurred. + + For example, if you are having problems with bbstoreaccounts, + build the debug version with: + + cd boxbackup-0.0 +cd bin/bbstoreaccounts +make + + Within the module directories, make defaults to building the + debug version. At the top level, it defaults to release. + + This will build an executable in debug/bin/bbstoreaccounts which + you can then use instead of the release version. It will give far more + useful error messages. + + When you get an error message, use the file and line number to + locate where the error occurs in the code. There will be comments + around that line to explain why the exception happened. + + If you are using a debug version of a daemon, these extended + messages are found in the log files. +
+
+
+ + &__ExceptionCodes__elfjz3fu; + + + Running without root + + It is possible to run both the server and client without root + privileges. + +
+ Server + + The server, by default, runs as a non-root user. However, it + expects to be run as root and changes user to a specified user as soon + as it can, simply for administrative convenience. The server uses a port + greater than 1024, so it doesn't need root to start. + + To run it entirely as a non-root user, edit the bbstored.conf + file, and remove the User directive from the Server section. Then simply + run the server as your desired user. +
+ +
+ Client + + The client requires root for normal operation, since it must be + able to access all files to back them up. However, it is possible to run + the client as a non-root user, with certain limitations. + + Follow the installation instructions, but install the executable + files manually to somewhere in your home directory. Then use + bbackupd-config to configure the daemon, but use a directory other than + /etc/box, probably somewhere in your home directory. + + All directories you specify to be backed up must be readable, and + all files must be owned by the user and readable to that user. + + Important: If any file or directory is not readable by this user, + the backup process will skip that file or directory. Keep an eye on the + logs for reports of this failure. + + Non-root operation of the backup client is recommended only for + testing, and should not be relied on in a production environment. +
+
+
diff --git a/docs/docbook/bb-book.xsl b/docs/docbook/bb-book.xsl new file mode 100644 index 00000000..a4f05fdb --- /dev/null +++ b/docs/docbook/bb-book.xsl @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/docs/docbook/bb-man.xsl b/docs/docbook/bb-man.xsl new file mode 100644 index 00000000..0f9e5c75 --- /dev/null +++ b/docs/docbook/bb-man.xsl @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/docs/docbook/bb-nochunk-book.xsl b/docs/docbook/bb-nochunk-book.xsl new file mode 100644 index 00000000..6099256c --- /dev/null +++ b/docs/docbook/bb-nochunk-book.xsl @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/docs/docbook/bbackupctl.xml b/docs/docbook/bbackupctl.xml new file mode 100644 index 00000000..b4880bfd --- /dev/null +++ b/docs/docbook/bbackupctl.xml @@ -0,0 +1,205 @@ + + + + bbackupctl + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupctl + + Control the Box Backup client daemon + + + + + bbackupctl + + -q + + -c config-file + + command + + + + + Description + + bbackupctl sends commands to a running + bbackupd daemon on a client machine. It can be used to + force an immediate backup, tell the daemon to reload its configuration + files or stop the daemon. If bbackupd is configured in + snapshot mode, it will not back up automatically, and the + bbackupctl must be used to tell it when to start a + backup. + + Communication with the bbackupd daemon takes place over a local + socket (not over the network). Some platforms (notably Windows) can't + determine if the user connecting on this socket has the correct + credentials to execute the commands. On these platforms, ANY local user + can interfere with bbackupd. To avoid this, remove the CommandSocket + option from bbackupd.conf, which will also disable bbackupctl. See the + Client Configuration page for more information. + + bbackupctl needs to read the + bbackupd configuration file to find out the name of the + CommandSocket. If you have to tell bbackupd where to + find the configuration file, you will have to tell + bbackupctl as well. The default on Unix systems is + usually /etc/box/bbackupd.conf. On Windows systems, + it is bbackupd.conf in the same directory where + bbackupd.exe is located. If + bbackupctl cannot find or read the configuration file, + it will log an error message and exit. + + bbackupctl usually writes error messages to the + console and the system logs. If it is not doing what you expect, please + check these outputs first of all. + + + + + + + Run in quiet mode. + + + + + config-file + + + Specify configuration file. + + + + + + Commands + + The following commands are available in bbackupctl: + + + + terminate + + + This command cleanly shuts down bbackupd. + This is better than killing or terminating it any other + way. + + + + + reload + + + Causes the bbackupd daemon to re-read all + its configuration files. Equivalent to kill + -HUP. + + + + + sync + + + Initiates a backup. If no files need to be backed up, no + connection will be made to the server. + + + + + force-sync + + + Initiates a backup, even if the + SyncAllowScript says that no backup should run + now. + + + + + wait-for-sync + + + Passively waits until the next backup starts of its own + accord, and then terminates. + + + + + wait-for-end + + + Passively waits until the next backup starts of its own + accord and finishes, and then terminates. + + + + + sync-and-wait + + + Initiates a backup, waits for it to finish, and then + terminates. + + + + + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd-config.xml b/docs/docbook/bbackupd-config.xml new file mode 100644 index 00000000..41bb6587 --- /dev/null +++ b/docs/docbook/bbackupd-config.xml @@ -0,0 +1,153 @@ + + + + bbackupd-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd-config + + Box Backup client daemon configuration file + generator + + + + + bbackupd-config + + config-dir + + backup-mode + + account-num + + server-hostname + + working-dir + + backup-dir + + backup-dir ... + + + + + Description + + The bbackupd-config script creates configuration files and client + certificates. It takes at least six parameters: + + + + config-dir + + + Configuration directory. Usually + /etc/box. + + + + + backup-mode + + + Either lazy or snapshot. + + + + + account-num + + + The client account number. This is set by the bbstored + administrator. + + + + + server-hostname + + + The hostname or IP address of the bbstored server. + + + + + working-dir + + + A directory to keep temporary state files. This is usually + something like /var/bbackupd. This can be + changed in bbackupd.conf later on if + required. + + + + + backup-dir + + + A space-separated list of directories to be backed up. Note + that this does not traverse mount + points. + + + + + + + Files + + /etc/box/bbackupd.conf + + /etc/box/bbackupd/NotifySysAdmin.sh + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd.conf.xml b/docs/docbook/bbackupd.conf.xml new file mode 100644 index 00000000..43ae2bed --- /dev/null +++ b/docs/docbook/bbackupd.conf.xml @@ -0,0 +1,479 @@ + + + + bbackupd.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd.conf + + Box Backup client daemon configuration file + + + + + /etc/box/bbackupd.conf + + + + + Description + + + + AccountNumber + + + The account number of this client. This is set by the admin of + the store server. + + + + + UpdateStoreInterval + + + Specifies the interval between scanning of the local discs. To + avoid cycles of load on the server, this time is randomly adjusted + by a small percentage as the daemon runs. Defaults to 1 hour. + + + + + MinimumFileAge + + + Specifies how long since a file was last modified before it + will be uploaded. Defaults to 6 hours. + + + + + MaxUploadWait + + + If a file is repeatedly modified it won't be uploaded + immediately in case it's modified again. However it should be + uploaded eventually. This is how long we should wait after first + noticing a change. Defaults to 1 day. + + + + + MaxFileTimeInFuture + + + + + + + + AutomaticBackup + + + + + + + + SyncAllowScript + + + Use this to temporarily stop bbackupd from syncronising or + connecting to the store. 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 prints 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 the script will be asked + again in that number of seconds. + + For example, you could use this on a laptop to only backup + when on a specific network. + + + + + MaximumDiffingTime + + + How much time should be spent on diffing files. + + + + + DeleteRedundantLocationsAfter + + + + + + + + FileTrackingSizeThreshold + + + + + + + + DiffingUploadSizeThreshold + + + + + + + + StoreHostname + + + The hostname or IP address of the + bbstored + + 8 + server. + + + + + StorePort + + + The port used by the server. Defaults to 2201. + + + + + ExtendedLogging + + + Logs everything that happens between the client and server. + The + bbackupd + + 8 + client must also be started with + . + + + + + ExtendedLogFile + + + + + + + + LogAllFileAccess + + + + + + + + LogFile + + + + + + + + LogFileLevel + + + + + + + + CommandSocket + + + Where the command socket is created in the filesystem. + + + + + KeepAliveTime + + + + + + + + StoreObjectInfoFile + + + + + + + + NotifyScript + + + The location of the script which runs at certain events. This + script is generated by + bbackupd-config + + 8 + . Defaults to + /etc/box/bbackupd/NotifySysAdmin.sh. + + + + + NotifyAlways + + + + + + + + CertificateFile + + + The path to the client's public certificate. + + + + + PrivateKeyFile + + + The path to the client's private key. This should only be + readable by root. + + + + + TrustedCAsFile + + + The Certificate Authority created by + bbstored-certs + + 8 + . + + + + + KeysFile + + + The data encryption key. This must be kept safe at all costs, your data is + useless without it! + + + + + DataDirectory + + + A directory to keep temporary state files. This is usually + something like /var/bbackupd. + + + + + Server + + + This section relates to the running daemon. + + + + PidFile + + + The location of the process ID file. Defaults to + /var/run/bbackupd.pid. + + + + + + + + BackupLocations + + + This section defines each directory to be backed up. Each + entry must have at least a Path entry and, optionally, include and + exclude directives. + + Multiple include and exclude directives may appear. + + + + Path + + + The path to back up. + + + + + ExcludeFile + + + Exclude a single file. + + + + + ExcludeFilesRegex + + + Exclude multiple files based on a regular expression. + See + re_format + + 7 + . + + + + + ExcludeDir + + + Exclude a single directory. + + + + + ExcludeDirsRegex + + + Exclude multiple directories based on a regular + expression. See + re_format + + 7 + . + + + + + AlwaysIncludeFile + + + Include a single file from a directory which has been + excluded. + + + + + AlwaysIncludeFilesRegex + + + Include multiple files from an excluded directory, + based on a regular expression. + + + + + AlwaysIncludeDir + + + Include a single directory from a directory which has + been excluded. + + + + + AlwaysIncludeDirsRegex + + + Include multiple directories from an excluded + directory, based on a regular expression. + + + + + + + + + + Examples + + The following is an example of a backup location: + + home +{ + Path = /home + ExcludeDir = /home/guest + ExcludeDir = /home/[^/]+/tmp + ExcludeFilesRegex = .*\.(mp3|MP3)$ + AlwaysIncludeFile = /home/someuser/importantspeech.mp3 +} + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd + + 8 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd.xml b/docs/docbook/bbackupd.xml new file mode 100644 index 00000000..11de0f3f --- /dev/null +++ b/docs/docbook/bbackupd.xml @@ -0,0 +1,209 @@ + + + + bbackupd + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd + + Box Backup client daemon + + + + + bbackupd + + -DFkqvVT + + -c config-file + + -t tag + + + + + Description + + bbackupd runs on client computers in the + background, finding new files to back up. When it is time for a backup, + bbackupd will connect to the server + (bbstored) to upload the files. + + A running bbackupd daemon can be controlled with + the bbackupctl command, to make it shut down, reload + its configuration, or start an immediate backup. + + bbackupd needs to be configured to tell it which + files to back up, how often, and to which server (running + bbstored). See the Client Configuration page for more + information. For this, you must write a configuration file. You must + either place it in the default location, or tell + bbackupd where to find it. + + You can check the default location with the + option. The default on Unix systems is usually + /etc/box/bbackupd.conf. On Windows systems, it is + bbackupd.conf in the same directory where + bbackupd.exe is located. If bbackupd cannot find or + read the configuration file, it will log an error message and exit. + + bbackupd usually writes log messages to the + system logs, using the facility local5, which you can + use to filter them to send them to a separate file. It can also write them + to the console, see options below. If bbackupd is not + doing what you expect, please check the logs first of all. + + + Options + + + + config-file + + + Use the specified configuration file. If + is omitted, the last argument is the configuration file. If none + is specified, the default is used (see above). + + + + + + + + Debugging mode. Do not fork into the background (do not run + as a daemon). Not available on Windows. + + + + + + + + No-fork mode. Same as for bbackupd. Not + available on Windows. + + + + + + + + Keep console open after fork, keep writing log messages to + it. Not available on Windows. + + + + + + + + Run more quietly. Reduce verbosity level by one. Available + levels are NOTHING, FATAL, + ERROR, WARNING, + NOTICE, INFO, + TRACE, EVERYTHING. Default + level is NOTICE in non-debugging builds. Use + once to drop to WARNING level, twice for + ERROR level, four times for no logging at + all. + + + + + -v + + + Run more verbosely. Increase verbosity level by one. Use + once to raise to INFO level, twice for + TRACE level, three times for + EVERYTHING (currently the same as + TRACE). + + + + + + + + Run at maximum verbosity (EVERYTHING + level). + + + + + tag + + + Tag each console message with specified marker. Mainly + useful in testing when running multiple daemons on the same + console. + + + + + + + + Timestamp each line of console output. + + + + + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupquery.xml b/docs/docbook/bbackupquery.xml new file mode 100644 index 00000000..016d5c7d --- /dev/null +++ b/docs/docbook/bbackupquery.xml @@ -0,0 +1,506 @@ + + + + bbackupquery + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupquery + + Box Backup store query and file retrieval + + + + + bbackupquery + + -q + + -c configfile + + command ... + + + + + Description + + bbackupquery is the main way of interacting with + the backup store from a Box Backup client machine. It supports both + interactive and batch modes of operation. + + It can be used to reviewing the status of a client machine's backup + store, getting status from the store server. The main use is to retrieve + files and directories when needed. + + bbackupquery supports interactive and batch modes + of operation. Interactive mode allows for interaction with the server much + like an interactive FTP client. + + Batch mode is invoked by putting commands into the invocation of + bbackupquery. Example: + + bbackupquery "list home-dirs" quit + + Note that commands that contain spaces are enclosed in double + quotes. If the quit command is omitted, after the + preceding commands are completed, bbackupquery will + enter interactive mode. + + + + Options + + + + + + + Quiet. Suppresses status output while running. + + + + + + + + Use configfile instead of the default bbackupd.conf file. + Can be a relative or full path. + + + + + + + Commands + + The commands that can be used in bbackupquery are listed + below. + + + + help + + + Displays the basic help message, which gives information about + the commands available in bbackupquery. Use the + form help command to get help on a specific + command. + + + + + quit + + + End the session with the store server, and quit + bbackupquery. + + + + + cd options + directory-name + + + Change directory. Options: + + + + + consider deleted directories for traversal + + + + + + + + consider old versions of directories for traversal. + This option should never be useful in a correctly formed + store. + + + + + + + + lcd + local-directory-name + + + Change directory on the client machine. To list the contents + of the local directory, type sh ls (on Unix-like + machines). + + + + + list options + directory-name + + + The list (or its synonym ls) command lists + the content of the current, or specified, directory. The options are + as follows: + + + + + + + recursively list all files + + + + + + + + list deleted files and directories + + + + + + + + list old versions of files and directories + + + + + + + + don't display object IDs + + + + + + + + don't display flags + + + + + + + + show file modification time (and attr mod time, if the + object has attributes). + + + + + + + + show file size in blocks used on server. Note that + this is only a very approximate indication of local file + size. + + + + + + + + ls options + directory-name + + + Synonym for list. + + + + + pwd + + + Print current directory, always relative to the backup store + root. + + + + + sh shell-command + + + Everything after the sh is passed to a shell and run. All + output from the command is displayed in the client. + + Example: to list the contents of the current directory on the + client machine type sh ls. + + + + + compare -a + + + + + + + + compare -l + location-name + + + + + + + + compare store-dir-name + local-dir-name + + + Compare the current data in the store with the data on the + disc. Please note that all the data will be downloaded from the + store, so this can be a very lengthy process depending on the size + of the store, and the size of the part you are comparing. + + Options: + + + + + + + compare all locations. + + + + + + + + compare one backup location as specified in the + configuration file. This compares one of the top level store + directories. + + + + + + + + set return code. The return code is set to the + following values, if quit is the next command. So, if + another command is run after the compare, the return code + will not refer to the compare. This option is very useful + for automating compares. Return code values: + + -- no differences were + found + + + + -- differences were + found + + + + -- an error occured + + + + + + + + + + get object-filename + local-filename + + + + + + + + get -i object-id + local-filename + + + Gets a file from the store. Object is specified as the + filename within the current directory. Local filename is optional. + Ignores old and deleted files when searching the directory for the + file to retrieve. + + To get an old or deleted file, use the + option and select the object as a hex object ID (first column in + listing). The local filename must be specified. + + + + + getobject object-id + local-filename + + + Gets the object specified by the object id (in hex) and stores + the raw contents in the local file specified. Note: This is only + useful for debugging as it does not decode files from the stored + format, which is encrypted and compressed. + + + + + restore -d + directory-name + local-directory-name + + + + + + + + restore -r + + + Restores a directory to the local disc. The local directory + specified must not exist (unless a previous restore is being + restarted). The root cannot be restored -- restore locations + individually. + + Options: + + + + + + + restore a deleted directory + + + + + + + + resume an interrupted restore + + + If a restore operation is interrupted for any + reason, it can be restarted using the switch. + Restore progress information is saved in a file at regular intervals + during the restore operation to allow restarts. + + + + + usage -m + + + Show space used on the server for this account. Display + fields: + + Used: Total amount of space used on + the server + + + + Old files: Space used by old + files + + + + Deleted files: Space used by + deleted files + + + + Directories: Space used by the + directory structure + + + + When Used exceeds the soft limit, the + server will start to remove old and deleted files until the usage + drops below the soft limit. After a while, you should expect to see + the usage stay at just below the soft limit. You only need more + space if the space used by old and deleted files is near + zero. + + The option displays output in + machine-readable form. + + + + + + + Bugs + + If you find a bug in Box Backup and you want to let us know about + it, join the mailing + list and send us a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstoreaccounts.xml b/docs/docbook/bbstoreaccounts.xml new file mode 100644 index 00000000..08092acf --- /dev/null +++ b/docs/docbook/bbstoreaccounts.xml @@ -0,0 +1,386 @@ + + + + bbstoreaccounts + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstoreaccounts + + Box Backup store accounts manager + + + + + bbstoreaccounts + + -c config-file + + command + + account-id + + command-specific arguments + + + + + Description + + bbstoreaccounts is the tool for managing accounts + on the store server. It can be used to view information related to + accounts, as well as create, change and delete accounts on the store + server. + + bbstoreaccounts always takes at least 2 + parameters: the command name and the account ID. Some commands require + additional parameters, and some commands have optional parameters. + + + Options + + + + + + + The configfile to use for connecting to the store. Default + is /etc/box/bbstored.conf. + + + + + + + Commands + + The commands tells bbstoreaccounts what action to perform. + + + + check account-id + fix + + + The check command verifies the + integrity of the store account given, and optionally fixes any + corruptions. Note: It is + recommended to run the 'simple' check command (without + fix) before using the fix + option. This gives an overview of the extent of any problems, + before attempting to fix them. + + + + + create account-id + disc-set soft-limit + hard-limit + + + Creates a new store account with the parameters given. The + parameters are as follows: + + + + + + + The ID of the new account to be created. A 32-bit + hexadecimal number. Cannot already exist on the + server. + + + + + + + + The disc set from + raidfile.conf + + 5 + where the backups for this client will + be stored. A number. Each RAID-file set has a number in + raidfile.conf. This number is what's used. + + + + + + + + The soft limit is the amount of storage that the + server will guarantee to be available for + storage. + + + + + + + + The amount of storage that the the server will + allow, before rejecting uploads, and starting to + eliminate old and deleted files to get back down to + soft-limit. + + + + + + + + delete account-id + yes + + + Deletes the account from the store server completely. + Removes all backups and deletes all references to the account in + the config files. + + delete will ask for confirmation from + the user, when called. Using the flag, + eliminates that need. This is useful when deleting accounts from + within a script or some other automated means. 0 + + + + + info account-id + + + Display information about the given account. + Example:[root]# bbstoreaccounts info 1 + Account ID: 00000001 + Last object ID: 58757 + Blocks used: 9864063 (38531.50Mb) + Blocks used by old files: 62058 (242.41Mb) +Blocks used by deleted files: 34025 (132.91Mb) + Blocks used by directories: 6679 (26.09Mb) + Block soft limit: 11796480 (46080.00Mb) + Block hard limit: 13107200 (51200.00Mb) + Client store marker: 1139559852000000 + + Explanation: + + + + Account ID + + + The account ID being displayed. + + + + + Last Object ID + + + A counter that keeps track of the objects that + have been backed up. This number refers to the last file + that was written to the store. The ID is displayed as a + decimal number, and the object ID can be converted to a + path name to a file as follows: convert the number to + hex (e.g.: 58757 => 0xE585); The last backed up file + will be (relative from the client's store root): + e5/o85.rfw. Longer numbers infer + more directories in the structure, so as an example + 3952697264 as the last object ID gives 0xEB995FB0, which + translates to a backup pathname of + eb/99/5f/ob0.rfw. + + + + + Blocks used + + + The number of blocks used by the store. The size + in Mb depends on the number of blocks, as well as the + block size for the disc set given in + raidfile.conf + + 5 + . In this case the block size is + 4096. + + + + + Blocks used by old files + + + The number of blocks occupied by files that have + newer versions in the store. This data is at risk for + being removed during housekeeping. + + + + + Blocks used by deleted files + + + The number of blocks used by files that have been + deleted on the client. This data is at risk for being + removed during housekeeping. + + + + + Blocks used by directories + + + The number of blocks used by directories in the + store. + + + + + Block soft limit + + + The soft limit in blocks. The soft limit is the + maximum guaranteed storage space available to the + account. When housekeeping starts, and the old and + deleted files are removed, they are removed in + chronological order (oldest first), until the data used + is less than the soft limit. + + + + + Block hard limit + + + The hard limit in blocks. The hard limit is the + most amount of storage the server will allow in an + account. Any data above this amount will be rejected. + Housekeeping will reduce the storage use, so more data + can be uploaded. + + + + + Client store marker + + + + bbstored + + 8 + uses this number to determine if it + needs to rescan the entire store. If this number is + different from the last time it checked, a rescan will + take place. + + + + + + + + setlimit account-id + soft-limit hard-limit + + + Changes the storage space allocation for the given + account. No server restart is needed. + + Parameters: + + + + + + + The ID of the account to be modified. + + + + + + + + The soft limit is the amount of storage that the + server will guarantee to be available for + storage. + + + + + + + + The amount of storage that the the server will + allow before rejecting uploads and starting to eliminate + old and deleted files to get back down to + . + + + + + + + + + + + Examples + + Create an account with ID 3af on disc set 0, with a 20GB soft-limit + and a 22GB hard-limit:bbstoreaccounts create 3af 0 20G 22GAlter + existing account ID 20 to have a 50GB soft-limit and a 55GB + hard-limit:bbstoreaccounts setlimit 20 50G 55G + + + + Files + + /etc/box/bbstored/accounts.txt + + + + See Also + + + bbstored + + 8 + , + bbstored-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored-certs.xml b/docs/docbook/bbstored-certs.xml new file mode 100644 index 00000000..79308089 --- /dev/null +++ b/docs/docbook/bbstored-certs.xml @@ -0,0 +1,180 @@ + + + + bbstored-certs + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored-certs + + Manage certificates for the Box Backup system + + + + + bbstored-certs + + certs-dir + + command + + arguments + + + + + Description + + bbstored-certs creates and signs certificates for + use in Box Backup. It allows the user to create and sign the server keys, + as well as signing client keys. + + All commands must be followed by the certs-dir, + which is the directory in which the certificates are stored. + + + Commands + + There are 3 commands: + + + + init + + + Create the certs-dir, and generate the + server keys for bbstored. certs-dir cannot + exist before running the command. + + + + + sign-server + servercsrfile + + + Sign the server certificate. The + servercsrfile is the file generated by the + init command. + + + + + sign + clientcsrfile + + + Sign a client certificate. The + clientcsrfile is generated during client setup. + See + bbackupd-config + + 8 + . Send the signed certificate back to the client, + and install according to the instructions given by + bbackupd-config. + + + + + + + + Files + + + raidfile-config + + 8 + generates the + raidfile.conf + + 5 + file. + + + + Bugs + + If you find a bug in Box Backup, and you want to let us know about + it, join the mailing + list, and send a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + See Also + + + bbstored-config + + 8 + , + bbstored.conf + + 5 + , + bbstoreaccounts + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored-config.xml b/docs/docbook/bbstored-config.xml new file mode 100644 index 00000000..6d0113c1 --- /dev/null +++ b/docs/docbook/bbstored-config.xml @@ -0,0 +1,148 @@ + + + + bbstored-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored-config + + Box Backup store daemon configuration file + generator + + + + + bbstored-config + + configdir + + servername + + username + + + + + Description + + The bbstored-config script creates configuration files and server + certificates for a bbstored instance. It takes three parameters: + + + + configdir + + + The directory where config files will reside. A + bbstored subdirectory will be created where + several config files will reside. The + bbstored.conf file will be created in + configdir. + + + + + servername + + + The name of the server that is being configured. Usually the + fully qualified domain name of the machine in question. + + + + + username + + + The name of the user that should be running the + bbstored process. Recommended name: + _bbstored. + + + + + A valid + raidfile.conf + + 5 + must be found in configdir. Several steps are taken + during the run of bbstored-config: + + + + Server certificates are created. This requires interaction from + the operator. + + + + The RAID volumes are checked to ensure that the configuration is + consistent and will work. + + + + Instructions for next steps to take are shown. These steps may + be different for different OS platforms, so pay close attention to + these instructions. + + + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored.conf + + 5 + , + bbstored + + 8 + , + bbstored-certs + + 8 + , + raidfile-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored.conf.xml b/docs/docbook/bbstored.conf.xml new file mode 100644 index 00000000..136b1bd0 --- /dev/null +++ b/docs/docbook/bbstored.conf.xml @@ -0,0 +1,211 @@ + + + + bbstored.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored.conf + + Box Backup store daemon configuration file + + + + + /etc/box/bbstored.conf + + + + + Description + + The following configuration options are valid: + + + + RaidFileConf + + + Specifies the path to the + raidfile.conf + + 5 + . This is normally + /etc/box/raidfile.conf. + + + + + AccountDatabase + + + Specifies the path to the account database created by + + bbstoreaccounts + + 8 + . This is usually + /etc/box/bbstored/accounts.txt. + + + + + ExtendedLogging + + + Specifies whether extended logging should be enabled to show + what commands are being received from clients. + + + + + TimeBetweenHousekeeping + + + How long between scanning for files which need + deleting. + + + + + Server + + + These options relate to the actual daemon. + + PidFile + + + The location of the pidfile, where the daemon's + process ID is kept. + + + + + User + + + The user to run as. + + + + + ListenAddresses + + + The interface addresses to listen on. Hostnames may be + used instead of IP addresses. The format is: + or + . + + + + + CertificateFile + + + The path to the server's public certificate. + + + + + PrivateKeyFile + + + The path to the server's private key. This should only + be readable by root and/or the . + + + + + TrustedCAsFile + + + The Certificate Authority created by + bbstored-certs + + 8 + . + + + + + + + + + + Examples + + The following is an example bbstored.conf: + + RaidFileConf = /etc/box/raidfile.conf +AccountDatabase = /etc/box/bbstored/accounts.txt + +TimeBetweenHousekeeping = 900 + +Server +{ + PidFile = /var/run/bbstored.pid + User = _bbstored + ListenAddresses = inet:server.example.com + CertificateFile = /etc/box/bbstored/server.example.com-cert.pem + PrivateKeyFile = /etc/box/bbstored/server.example.com-key.pem + TrustedCAsFile = /etc/box/bbstored/clientCA.pem +} + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored + + 8 + , + bbstored-config + + 8 + , + raidfile-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored.xml b/docs/docbook/bbstored.xml new file mode 100644 index 00000000..81891a8d --- /dev/null +++ b/docs/docbook/bbstored.xml @@ -0,0 +1,90 @@ + + + + bbstored + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored + + Box Backup store daemon + + + + + bbstored + + config-file + + + + + Description + + bbstored runs on a central server. Clients + running bbackupd connect to the server and upload + files. + + The only argument is optional and specifies a non-default + configuration file. By default it will look for the configuration file as + /etc/box/bbstored.conf. + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored.conf + + 5 + , + bbstored-config + + 8 + , + raidfile-config + + 8 + , + raidfile.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/html/bbdoc-man.css b/docs/docbook/html/bbdoc-man.css new file mode 100644 index 00000000..0345560e --- /dev/null +++ b/docs/docbook/html/bbdoc-man.css @@ -0,0 +1,104 @@ +body { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 20px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto; } + +table { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: 10pt; + line-height: 100%; } + + +code { + font-size: 11pt; } + + + +div.navheader { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + line-height: 100%; } + +#header { + background-color: #e4e6ed; + text-align: left; + padding-top: 10px; + margin-right: -100px; + margin-left: -250px; + top: 20px; + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid white } + +#logo { + position: relative; + margin-left: 200px } + + +#page { + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 50px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto } + +#disc { } + +.informaltable td,tr {font-size: 1em; + line-height: 140%; + text-align: left; + background-color: #e4e6ed; + padding: 4px } + +tr,td {font-size: 1em; + line-height: 100%; + background-color: #edeef3; } + +pre, tt { font-size: 1.3em; + color: #088; + letter-spacing: 1px; + word-spacing: 2px} + +h1 { + color: #c00; + font-size: 16pt; + margin-bottom: 2em; + margin-left: -50px } + +h2 { + color: #324e95; + font-size: 12pt; + margin-top: 2em; + margin-left: -50px } + +h3 { + color: #324e95; + font-size: 10pt; + margin-top: 2em; + margin-left: -50px } + +dt { font-weight: bold } + +a:link { + color: #324e95; + text-decoration: none; + background-color: transparent } + +a:visited { + color: #90c; + text-decoration: none } + +a:hover { + color: #c00; + text-decoration: underline; + background-color: transparent } diff --git a/docs/docbook/html/bbdoc.css b/docs/docbook/html/bbdoc.css new file mode 100644 index 00000000..d3b4a1c2 --- /dev/null +++ b/docs/docbook/html/bbdoc.css @@ -0,0 +1,112 @@ +body { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 20px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto; } + +table { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: 10pt; + line-height: 100%; } + + + + +div.navheader { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + line-height: 100%; } + +#header { + background-color: #e4e6ed; + text-align: left; + padding-top: 10px; + margin-right: -100px; + margin-left: -250px; + top: 20px; + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid white } + +#logo { + position: relative; + margin-left: 200px } + + +#page { + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 50px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto } + +#disc { } + +.informaltable td,tr {font-size: 1em; + line-height: 140%; + text-align: left; + background-color: #e4e6ed; + padding: 4px } + +tr,td {font-size: 1em; + line-height: 100%; + background-color: #edeef3; } + +pre, tt { font-size: 1.3em; + color: #088; + letter-spacing: 1px; + word-spacing: 2px} + +h1 { + color: #c00; + font-size: 16pt; + margin-bottom: 2em; + margin-left: -50px } + +h2 { + color: #324e95; + font-size: 12pt; + margin-top: 2em; + margin-left: -50px } + +h3 { + color: #324e95; + font-size: 10pt; + margin-top: 2em; + margin-left: -50px } + +dt { font-weight: bold } + +ul { + list-style-image: url(images/arrow.png) } + +ul li { + background-color: #e4e6ed; + margin: 1em 6em 1em -2em; + padding: 0.2em 0.5em 0.2em 1em; + border-style: solid; + border-width: 1px; + border-color: #c4c4d5 #fff #fff #c4c4d5 } + +a:link { + color: #324e95; + text-decoration: none; + background-color: transparent } + +a:visited { + color: #90c; + text-decoration: none } + +a:hover { + color: #c00; + text-decoration: underline; + background-color: transparent } diff --git a/docs/docbook/html/favicon.ico b/docs/docbook/html/favicon.ico new file mode 100644 index 00000000..f8cbb3b3 Binary files /dev/null and b/docs/docbook/html/favicon.ico differ diff --git a/docs/docbook/html/images/arrow.png b/docs/docbook/html/images/arrow.png new file mode 100644 index 00000000..4c60805d Binary files /dev/null and b/docs/docbook/html/images/arrow.png differ diff --git a/docs/docbook/html/images/bblogo.png b/docs/docbook/html/images/bblogo.png new file mode 100644 index 00000000..b33230b4 Binary files /dev/null and b/docs/docbook/html/images/bblogo.png differ diff --git a/docs/docbook/html/images/stepahead.png b/docs/docbook/html/images/stepahead.png new file mode 100644 index 00000000..9ac05b8c Binary files /dev/null and b/docs/docbook/html/images/stepahead.png differ diff --git a/docs/docbook/instguide.xml b/docs/docbook/instguide.xml new file mode 100644 index 00000000..addef297 --- /dev/null +++ b/docs/docbook/instguide.xml @@ -0,0 +1,766 @@ + + + + Box Backup Build and Installation Guide + + + License + + Copyright © 2003 - 2007, Ben Summers and contributors. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + + + All use of this software and associated advertising materials + must display the following acknowledgement: This product includes + software developed by Ben Summers. + + + + The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + + + + [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. + + + + Introduction + + The backup daemon, bbackupd, runs on all machines to be backed up. + The store server daemon, bbstored runs on a central server. Data is sent + to the store server, which stores all data on local filesystems, that is, + only on local hard drives. Tape or other archive media is not used. + + The system is designed to be easy to set up and run, and cheap to + use. Once set up, there should be no need for user or administrative + intervention, apart from usual system maintenance. + +
+ Client daemon + + bbackupd is configured with a list of directories to back up. It + has a lazy approach to backing up data. Every so often, the directories + are scanned, and new data is uploaded to the server. This new data must + be over a set age before it is uploaded. This prevents rapid revisions + of a file resulting in many uploads of the same file in a short period + of time. + + It can also operate in a snapshot mode, which behaves like + traditional backup software. When instructed by an external bbackupctl + program, it will upload all changed files to the server. + + The daemon is always running, although sleeping most of the time. + In lazy mode, it is completely self contained -- scripts running under + cron jobs are not used. The objective is to keep files backed up, not to + make snapshots of the filesystem at particular points in time + available. + + If an old version of the file is present on the server, a modified + version of the rsync algorithm is used to upload only the changed + portions of the file. + + After a new version is uploaded, the old version is still + available (subject to disc space on the server). Similarly, a deleted + file is still available. The only limit to their availability is space + allocated to this account on the server + + Future versions will add the ability to mark the current state of + files on the server, and restore from this mark. This will emulate the + changing of tapes in a tape backup system. + +
+ Restoration + + Restoring files is performed using a query tool, bbackupquery. + This can be used to restore entire directories, or as an 'FTP-like' + tool to list and retrieve individual files. Old versions and deleted + files can be retrieved using this tool for as long as they are kept on + the server. +
+ +
+ Client Resource Usage + + bbackupd uses only a minimal amount of disc space to store + records on uploaded files -- less than 32 bytes per directory and file + over a set size threshold. However, it minimises the amount of queries + it must make to the server by storing, in memory, a data structure + which allows it to determine what data is new. It does not need to + store a record of all files, essentially just the directory names and + last modification times. This is not a huge amount of memory. + + If there are no changes to the directories, then the client will + not even connect to the server. +
+
+ +
+ Security + + Box Backup is designed to be secure in several ways. The data + stored on the backup store server is encrypted using secret-key + cryptography. Additionally, the transport layer is encrypted using TLS, + to ensure that the communications can't be snooped. + +
+ Encryption + + The files, directories, filenames and file attributes are all + encrypted. By examining the stored files on the server, it is only + possible to determine the approximate sizes of a files and the tree + structure of the disc (not names, just number of files and + subdirectories in a directory). By monitoring the actions performed by + a client, it is possible to determine the frequency and approximate + scope of changes to files and directories. + + The connections between the server and client are encrypted + using TLS (latest version of SSL). Traffic analysis is possible to + some degree, but limited in usefulness. + + An attacker will not be able to recover the backed up data + without the encryption keys. Of course, you won't be able to recover + your files without the keys either, so you must make a conventional, + secure, backup of these keys. +
+ +
+ Authentication + + SSL certificates are used to authenticate clients. UNIX user + accounts are not used to minimise the dependence on the configuration + of the operating system hosting the server. + + A script is provided to run the necessary certification + authority with minimal effort. +
+
+ +
+ Server daemon + + The server daemon is designed to be simple to deploy, and run on + the cheapest hardware possible. To avoid the necessity to use expensive + hardware RAID or software RAID with complex setup, it (optionally) + stores files using RAID techniques. + + It does not need to run as a privileged user. + + Each account has a set amount of disc space allocated, with a soft + and a hard limit. If the account exceeds the soft limit, a housekeeping + process will start deleting old versions and deleted files to reduce the + space used to below the soft limit. If the backup client attempts to + upload a file which causes the store to exceed the hard limit, the + upload will be refused. +
+
+ + + Building and installing + +
+ Before you start + + Firstly, check that all the clocks on your clients, servers and + signing machines are accurate and in sync. A disagreement in time + between a client and a server is the biggest cause of installation + difficulties, as the times in the generated certificates will cause + login failures if the start date is in the future. +
+ +
+ Box Backup compile + + In the following instructions, replace 0.00 with the actual + version number of the archive you have downloaded. + + For help building on Windows, see the Windows + Compile Appendix. And if you want to build a Linux RPM, look here. + + You need the latest version of OpenSSL, as some of the extra APIs + it provides are required. You should have this anyway, as earlier + versions have security flaws. (If you have an earlier version installed, + the configuration script will give you instructions on enabling + experimental support for older versions.) + + See OpenSSL notes for more information + on OpenSSL issues. + + There are some notes in the archive about compiling on various + platforms within the boxbackup-0.00 directory -- read them first. For + example, if you are compiling under Linux, look for LINUX.txt as + boxbackup-0.00/LINUX.txt after untaring the archive. + + Download the archive, then in that directory type + + tar xvzf boxbackup-0.00.tgz +cd boxbackup-0.00 +./configure +make + + The server and client will be built and packaged up for + installation on this machine, or ready to be transferred as tar files to + another machine for installation. + + This builds two parcels of binaries and scripts, 'backup-client' + and 'backup-server'. The generated installation scripts assumes you want + everything installed in /usr/local/bin + + Optionally, type make test to run + all the tests. +
+ +
+ Local installation + + Type make install-backup-client + to install the backup client. + + Type make install-backup-server + to install the backup server. +
+ +
+ Remote installation + + In the parcels directory, there are tar files for each parcel. The + name reflects the version and platform you have built it for. + + Transfer this tar file to the remote server, and unpack it, then + run the install script. For example: + + tar xvzf boxbackup-0.00-backup-server-OpenBSD.tgz +cd boxbackup-0.00-backup-server-OpenBSD +./install-backup-server +
+ +
+ Configure options + + You can use arguments to the configure script to adjust the + compile and link lines in the generated Makefiles, should this be + necessary for your platform. The configure script takes the usual GNU + autoconf arguments, a full list of which can be obtained with --help. Additional options for Box Backup + include: + + + + + + --enable-gnu-readline + + Use GNU readline if present. Linking Box Backup against + GNU readline may create licence implications if you then + distribute the binaries. libeditline is also supported as a safe + alternative, and is used by default if available. + + + + --disable-largefile + + Omit support for large files + + + + --with-bdb-lib=DIR + + Specify Berkeley DB library location + + + + --with-bdb-headers=DIR + + Specify Berkeley DB headers location + + + + --with-random=FILE + + Use FILE as random number seed (normally + auto-detected) + + + + --with-tmp-dir=DIR + + Directory for temporary files (normally /tmp) + + + + + + See OpenSSL notes for the OpenSSL + specific options. +
+ +
+ Tests + + There are a number of unit tests provided. To compile and run one + type: + + ./runtest.pl bbackupd release +./runtest.pl common debug +./runtest.pl ALL + + The runtest.pl script will compile and run the test. The first + argument is the test name, and the second the type of build. Use ALL as + a test name to run all the tests. + + The output from the tests is slightly muddled using this script. + If you're developing, porting or trying out new things, it might be + better to use the following scheme: + + cd test/bbackupd +make +cd ../../debug/test/bbackupd +./t + + or in release mode... + + cd test/bbackupd +make -D RELEASE +cd ../../release/test/bbackupd +./t + + (use RELEASE=1 with GNU make) + + I tend to use two windows, one for compilation, and one for + running tests. +
+
+ + + Box Backup and SSL + +
+ General notes + + Ideally, you need to use version 0.9.7 or later of OpenSSL. If + this is installed on your system by default (and it is on most recent + releases of UNIX like OSes) then everything should just work. + + However, if it isn't, you have a few options. + +
+ Upgrade your installation + + The best option is to upgrade your installation to use 0.9.7. + Hopefully your package manager will make this easy for you. This may + require reinstallation of lots of software which depends on OpenSSL, + so may not be ideal. + + (But as there have been a few security flaws in OpenSSL + recently, you probably want to upgrade it anyway.) +
+ +
+ Install another OpenSSL + + The second best option is to install another copy. If you + download and install from source, it will probably install into + /usr/local/ssl. You can then configure Box Backup to use it + using: + + ./configure --with-ssl-headers=/usr/local/ssl/include --with-ssl-lib=/usr/local/ssl/lib + + which will set up the various includes and libraries for + you. + + The configuration scripts may be a problem, depending on your + installation. See below for more information. +
+ +
+ Use the old version of OpenSSL + + If you have an old version installed, the configuration script + will give you instructions on how to enable support for older + versions. Read the warnings, and please, whatever you do, don't + release binary packages or ports which enable this option. + + You may have issues with the configuration scripts, see + below. +
+
+ +
+ If you have problems with the config scripts + + If you get OpenSSL related errors with the configuration scripts, + there are two things to check: + + + + The bin directory within your OpenSSL directory is in the path + (if you have installed another version) + + + + You have an openssl.cnf file which works and can be + found. + + + +
+ OpenSSL config file + + You need to have an openssl.cnf file. The default will generally + work well (see example at end). Make sure the openssl utility can find + it, either set the OPENSSL_CONF environment variable, or install it + into the location that is mentioned in the error messages. + + Example OpenSSL config file: + + # +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +RANDFILE = /dev/arandom + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +#countryName_default = AU +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +#stateOrProvinceName_default = Some-State + +localityName = Locality Name (eg, city) + +0.organizationName = Organization Name (eg, company) +#0.organizationName_default = Internet Widgits Pty Ltd + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = CryptSoft Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = + +commonName = Common Name (eg, fully qualified host name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ x509v3_extensions ] + +nsCaRevocationUrl = http://www.cryptsoft.com/ca-crl.pem +nsComment = "This is a comment" + +# under ASN.1, the 0 bit would be encoded as 80 +nsCertType = 0x40 +
+
+
+ + + Compiling bbackupd on Windows using Visual C++ + + This Appendix explains how to build the bbackupd daemon for Windows + using the Visual C++ compiler. + + If you have any problems following these instructions, please sign + up to the mailing + list and report them to us, or send an email to Chris Wilson. Thanks! + + Note: bbstored will not be built + with this process. bbstored is not currently supported on Windows. There + are no plans for bbstored support on Windows. + +
+ Tools + + You will need quite a bit of software to make this work. All of it + is available for free on the Internet, although Visual C++ Express has + license restrictions and a time limit. + +
+ Visual C++ + + Microsoft's Visual C++ compiler and development environment are + part of their commercial product Visual Studio. Visual + Studio 2005 is supported, and 2003 should work as well. + + You can also download + a free copy of Visual C++ 2005 Express. This copy must be registered + (activated) within 30 days, and is free for one year. + + You will need the Platform + SDK to allow you to compile Windows applications. +
+ +
+ Perl + + Download and install ActivePerl for + Windows, which you can probably find here. +
+ +
+ Libraries + + You will need to download and install several libraries. They + must all be built in the same directory, to be able to link + properly. + + 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:\Documents and Settings\Your Username\Desktop\Box + + Make sure you know the full path to this directory. + +
+ OpenSSL + + You will need to compile OpenSSL using Visual C++. The latest + release at this time, OpenSSL 0.9.8a, does not compile with Visual + C++ 2005 out of the box, so you need a + patched version. The original + source and patch + are also available. + + To compile OpenSSL: + + + + Use a Windows unzipper such as WinZip (free trial) to + extract the openssl-0.9.8a-vc2005.tar.gz archive, + which you just downloaded, into the base directory. + + + + Rename the folder from openssl-0.9.8a-vc2005 to openssl + + + + Open a command shell (run cmd.exe) and cd to the openssl directory + + + + Run the following commands: + + perl Configure VC-WIN32 +ms\do_ms +"c:\program files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat" +nmake -f ms\ntdll.mak + + +
+ +
+ Zlib + + You will need to download the Zlib compiled DLL. + Create a directory called zlib in + the base directory, and unzip the file you just downloaded into that + directory. You don't need to compile anything. +
+
+ +
+ Download Box Backup + + The first version of Box Backup that's known to compile and with + Visual C++ 2005 is available on the Subversion + server. However, this version has not been extensively tested + and may be out of date. + + The changes are expected to be merged into the Subversion trunk + at some point, and this page should then be updated. If in doubt, + please sign up to the mailing + list and ask us. + + To get the source code out of Subversion you will need a Subversion + client for Windows. After installing it, open a new command + prompt, go to the base directory, and type: + + svn co http://www.boxbackup.org/svn/box/chris/win32/vc2005-compile-fixes/ boxbackup + + This should create a directory called boxbackup inside the base directory. +
+ +
+ Configure Box Backup + + Open a command prompt, change to the base directory then + boxbackup, and run win32.bat to configure the sources. Otherwise, + Visual C++ will complain about missing files whose names start with + autogen, and missing config.h. +
+ +
+ Compile Box Backup + + Open Visual C++. Choose "File/Open/Project", navigate to the + base directory, then to boxbackup\infrastructure\msvc\2005 (or + 2003 if using Visual Studio 2003), + and open any project or solution file in that directory. + + Press F7 to compile Box Backup. If the compilation is + successful, boxbackup\Debug\bbackupd.exe will be + created. +
+ +
+ Install Box Backup + + 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: + + + + boxbackup\Debug\bbackupd.exe + + + + openssl\out32dll\libeay32.dll + + + + 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. + + 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. +
+ +
+ Windows Service + + Box Backup can also run as a Windows service, in which case it + will be automatically started at boot time in the background. To + install this, open a command prompt, and run: + + cd "C:\Program Files\Box Backup" +bbackupd.exe -i + + This should output Box Backup service installed. +
+
+
+ + + Compilation and installation by building an RPM on + Linux + + It is very easy to build an RPM of Box Backup on Linux platforms. + This should work on all Red Hat distributions (including Fedora), SuSE, + and probably others too. + + Given that you have the correct development packages installed + simply execute this command (replacing the version number): + + rpmbuild -ta boxbackup-0.00.tgz + + rpmbuild will report where the packages have been written to, and + these can be installed in the normal manner. + + If you have never built an RPM before you should set up a convenient + build area as described in the RPM + book. + + If you wish to customise the package you can find the spec file in + the contrib/rpm directory. + +
diff --git a/docs/docbook/raidfile-config.xml b/docs/docbook/raidfile-config.xml new file mode 100644 index 00000000..78a3e94c --- /dev/null +++ b/docs/docbook/raidfile-config.xml @@ -0,0 +1,198 @@ + + + + raidfile-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + raidfile-config + + Configure Box Backup's RAID files + + + + + raidfile-config + + config-dir + + blocksize + + dir1 dir2 dir3 + + + + + Description + + raidfile-config creates a raidfile.conf file for Box Backup. This + file holds information about the directories used to store backups in. Box + Backup supports userland RAID, in a restricted RAID5 configuration, where + 3 and only 3 'drives' are supported. You can read more about RAID5 (and + other RAID-levels) here. + + + Parameters + + The parameters are as follows: + + + + config-dir + + + The directory path where configuration files are located. + Usually this is /etc/box. + raidfile.conf will be written in this + directory. + + + + + blocksize + + + The block size used for file storage in the system, in + bytes. Using a multiple of the file system block size is a good + strategy. Depending on the size of the files you will be backing + up, this multiple varies. Of course it also depends on the native + block size of your file system. + + + + + dir1 + + + The first directory in the built-in RAID array. + + + + + dir2 + + + The second directory in the built-in RAID array. If you are + not using the built-in RAID functionality, this field should be + ignored. You should not use the built-in RAID if you have a + hardware RAID solution or if you're using another type of software + RAID (like md on Linux). + + + + + dir3 + + + The third directory in the built-in RAID array. The same + notes that apply to dir2 also apply to + dir3. + + + + + Note that there are currently no way to add multiple disk sets to + the raidfile.conf file using command line tools, etc. See + raidfile.conf + + 5 + for details on adding more disks. + + + + + Bugs + + If you find a bug in Box Backup, and you want to let us know about + it, join the mailing + list, and send a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + Files + + raidfile-config generates the + raidfile.conf + + 5 + file. + + + + See Also + + + bbstored-config + + 8 + , + bbstored.conf + + 5 + , + raidfile.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/raidfile.conf.xml b/docs/docbook/raidfile.conf.xml new file mode 100644 index 00000000..7a8b6410 --- /dev/null +++ b/docs/docbook/raidfile.conf.xml @@ -0,0 +1,143 @@ + + + + raidfile.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + raidfile.conf + + Userland RAID for Box Backup + + + + + /etc/box/raidfile.conf + + + + + Description + + The raidfile.conf is usually generated by + raidfile-config + + 8 + but may be manually edited if the store locations move + or if more than one disc set is required. + + + + discX + + + Specifies a set of discs. + + + + SetNumber + + + The set number of the RAID disc, referenced by each + account. + + + + + BlockSize + + + The block size of the file system (usually 2048). + Under BSD with FFS, set this to your file system's + fragment size (most likely an 8th of the block + size). + + + + + Dir0 + + + The first directory in the RAID array. + + + + + Dir1 + + + The second directory in the RAID array. If you do + not wish to use the built-in RAID functionality, this + field should be set to the same as + Dir0. You should not use the built-in + RAID if you have a hardware RAID solution or if you're + using another type of software RAID (like md on + Linux). + + + + + Dir2 + + + The third directory in the RAID array. The same + notes that apply to Dir2 also apply to + Dir3. + + + + + + + + + + Files + + /etc/box/raidfile.conf + + + + See Also + + + raidfile-config + + 8 + , + bbstored.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/images/bblogo-alpha.xcf b/docs/images/bblogo-alpha.xcf new file mode 100644 index 00000000..18f494f9 Binary files /dev/null and b/docs/images/bblogo-alpha.xcf differ diff --git a/docs/tools/generate_except_xml.pl b/docs/tools/generate_except_xml.pl new file mode 100644 index 00000000..38ee9074 --- /dev/null +++ b/docs/tools/generate_except_xml.pl @@ -0,0 +1,74 @@ +#!/usr/bin/perl -w +use strict; + +open (EXCEPT, "< $ARGV[0]") or die "Can't open $ARGV[0]: $!\n"; +open (DOCBOOK, "> $ARGV[1]") or die "Can't open $ARGV[1] for writing: $!\n"; + +print DOCBOOK < + + + Exception codes + +EOD +my $sectionName; +my $sectionNum; +my $sectionDesc; +my $exceptionCode; +my $exceptionShortDesc; +my $exceptionLongDesc; +while() +{ + next if(m/^#/); + chomp; + if(m/^EXCEPTION TYPE (\w+) (\d+)/) + { + $sectionName = ucfirst(lc($1)); + $sectionNum = $2; + if($sectionName ne "Common") + { + $sectionDesc = "the " . $sectionName; + } + else + { + $sectionDesc = "any"; + } + print DOCBOOK < + $sectionName Exceptions ($sectionNum) + + These are exceptions that can occur in $sectionDesc module + of the system. + + +EOD + } + + # The END TYPE line + if(m/^END TYPE$/) + { + print DOCBOOK " \n \n"; + } + + # The actual exceptions + if(m/(\(\d+\/\d+\)) - (\w+ \w+)(?: - )?(.*)$/) + { + $exceptionCode = $1; + $exceptionShortDesc = $2; + $exceptionLongDesc = $3; + + print DOCBOOK " \n "; + print DOCBOOK $exceptionCode . ": " . $exceptionShortDesc . ""; + if($exceptionLongDesc ne "") + { + print DOCBOOK " -- " . $exceptionLongDesc; + } + print DOCBOOK "\n \n"; + } +} + +print DOCBOOK "\n"; + +close EXCEPT; +close DOCBOOK; + diff --git a/docs/xsl-generic/VERSION b/docs/xsl-generic/VERSION new file mode 100644 index 00000000..14b04f23 --- /dev/null +++ b/docs/xsl-generic/VERSION @@ -0,0 +1,113 @@ + + + + + + + + + + + +docbook-xsl +1.72.0 +6549 +$Revision: 7388 $ +$URL: https://docbook.svn.sourceforge.net/svnroot/docbook/trunk/xsl/VERSION $ + + + + + DocBook + XSL Stylesheets + 1.73.2 + + + + + + + +Minor bugfixes + + + + + http://sourceforge.net/projects/docbook/ + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.tar.gz?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.zip?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.bz2?download + http://sourceforge.net/project/shownotes.php?release_id={SFRELID} + http://docbook.svn.sourceforge.net/viewvc/docbook/ + http://lists.oasis-open.org/archives/docbook-apps/ + This is a bug-fix update to the 1.73.1 release. + + + + + + + + + + + + + + + + + + + + + You must specify the sf-relid as a parameter. + + + + + + + + + + + + + + + + + + : + + + + + + + + + : + + + + + + + + + : + + + + + diff --git a/docs/xsl-generic/common/af.xml b/docs/xsl-generic/common/af.xml new file mode 100644 index 00000000..a60fe6a3 --- /dev/null +++ b/docs/xsl-generic/common/af.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/am.xml b/docs/xsl-generic/common/am.xml new file mode 100644 index 00000000..10be46ee --- /dev/null +++ b/docs/xsl-generic/common/am.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ምልክቶች +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ar.xml b/docs/xsl-generic/common/ar.xml new file mode 100644 index 00000000..64d6f3bc --- /dev/null +++ b/docs/xsl-generic/common/ar.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/autoidx-kimber.xsl b/docs/xsl-generic/common/autoidx-kimber.xsl new file mode 100644 index 00000000..151d3af2 --- /dev/null +++ b/docs/xsl-generic/common/autoidx-kimber.xsl @@ -0,0 +1,43 @@ + + + + + + +]> + + + + + + + + + + ERROR: the 'kimber' index method requires the + Saxon version 6 or 8 XSLT processor. + + + 1 + + + + + + + + diff --git a/docs/xsl-generic/common/autoidx-kosek.xsl b/docs/xsl-generic/common/autoidx-kosek.xsl new file mode 100644 index 00000000..386e4528 --- /dev/null +++ b/docs/xsl-generic/common/autoidx-kosek.xsl @@ -0,0 +1,150 @@ + + + +]> + + + + + + + + + + ERROR: the 'kosek' index method does not + work with the xsltproc XSLT processor. + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of index grouping letters exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of index grouping letters exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/az.xml b/docs/xsl-generic/common/az.xml new file mode 100644 index 00000000..b1b14c59 --- /dev/null +++ b/docs/xsl-generic/common/az.xml @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +İşarələr +A +a +B +b +C +c +Ç +ç +D +d +E +e +e +e +Ə +ə +G +g +Ğ +ğ +H +h +X +x +I +ı +İ +i +J +j +K +k +Q +q +L +l +M +m +N +n +O +o +Ö +ö +P +p +R +r +S +s +Ş +ş +T +t +U +u +Ü +ü +V +v +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/bg.xml b/docs/xsl-generic/common/bg.xml new file mode 100644 index 00000000..1e6e7a13 --- /dev/null +++ b/docs/xsl-generic/common/bg.xml @@ -0,0 +1,718 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Цифри и знаци +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +П +п +Р +р +С +с +Т +т +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ь +ь +Ю +ю +Я +я +Э +э +Ы +ы +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/bn.xml b/docs/xsl-generic/common/bn.xml new file mode 100644 index 00000000..f31e01c4 --- /dev/null +++ b/docs/xsl-generic/common/bn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/bs.xml b/docs/xsl-generic/common/bs.xml new file mode 100644 index 00000000..341c211d --- /dev/null +++ b/docs/xsl-generic/common/bs.xml @@ -0,0 +1,656 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboli +A +a +B +b +C +c +Ć +ć +Č +č +D +d +Đ +đ +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +R +r +S +s +Š +š +T +t +U +u +V +v +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/ca.xml b/docs/xsl-generic/common/ca.xml new file mode 100644 index 00000000..fa4332e5 --- /dev/null +++ b/docs/xsl-generic/common/ca.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/charmap.xml b/docs/xsl-generic/common/charmap.xml new file mode 100644 index 00000000..6644f3e5 --- /dev/null +++ b/docs/xsl-generic/common/charmap.xml @@ -0,0 +1,185 @@ + + + + + Common » Character-Map Template Reference + + $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the + character-map templates in the DocBook XSL Stylesheets. + + + +These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + + +The character-map templates don’t import or include + any DocBook XSL stylesheet parameters, so the + character-map templates can be used without importing the + whole set of parameters. + + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +apply-character-map +Applies an XSLT character map + + +<xsl:template name="apply-character-map"> +<xsl:param name="content"/> +<xsl:param name="map.contents"/> + ... +</xsl:template> + + + +<para>This template applies an <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">XSLT character map</link>; that is, it causes certain + individual characters to be substituted with strings of one + or more characters. It is useful mainly for replacing + multiple “special” characters or symbols in the same target + content. It uses the value of + <parameter>map.contents</parameter> to do substitution on + <parameter>content</parameter>, and then returns the + modified contents.</para> + + <note> + +<para>This template is a very slightly modified version of + Jeni Tennison’s <function>replace_strings</function> + template in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/StringReplace.html#d9351e13">multiple string replacements</link> section of Dave Pawson’s + <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT FAQ</link>.</para> + + +<para>The <function>apply-string-subst-map</function> + template is essentially the same template as the + <function>apply-character-map</function> template; the + only difference is that in the map that + <function>apply-string-subst-map</function> expects, <tag class="attribute">oldstring</tag> and <tag class="attribute">newstring</tag> attributes are used + instead of <tag class="attribute">character</tag> and <tag class="attribute">string</tag> attributes.</para> + + </note> + </refsect1><refsect1><title>Parameters + + + content + + +The content on which to perform the character-map + substitution. + + + + map.contents + + +A node set of elements, with each element having + the following attributes: + + + + character, a + character to be replaced + + + string, a + string with which to replace character + + + + + + + + + + + + + +read-character-map +Reads in all or part of an XSLT character map + + +<xsl:template name="read-character-map"> +<xsl:param name="use.subset"/> +<xsl:param name="subset.profile"/> +<xsl:param name="uri"/> + ... +</xsl:template> + + + +<para>The XSLT 2.0 specification describes <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">character maps</link> and explains how they may be used + to allow a specific character appearing in a text or + attribute node in a final result tree to be substituted by + a specified string of characters during serialization. The + <function>read-character-map</function> template provides a + means for reading and using character maps with XSLT + 1.0-based tools.</para> + + +<para>This template reads the character-map contents from + <parameter>uri</parameter> (in full or in part, depending on + the value of the <parameter>use.subset</parameter> + parameter), then passes those contents to the + <function>apply-character-map</function> template, along with + <parameter>content</parameter>, the data on which to perform + the character substitution.</para> + + +<para>Using the character map “in part” means that it uses only + those <tag>output-character</tag> elements that match the + XPath expression given in the value of the + <parameter>subset.profile</parameter> parameter. The current + implementation of that capability here relies on the + <function>evaluate</function> extension XSLT function.</para> + + </refsect1><refsect1><title>Parameters + + + use.subset + + +Specifies whether to use a subset of the character + map instead of the whole map; boolean + 0 or 1 + + + + subset.profile + + +XPath expression that specifies what subset of the + character map to use + + + + uri + + +URI for a character map + + + + + + + + diff --git a/docs/xsl-generic/common/charmap.xsl b/docs/xsl-generic/common/charmap.xsl new file mode 100644 index 00000000..3e0f5d4d --- /dev/null +++ b/docs/xsl-generic/common/charmap.xsl @@ -0,0 +1,221 @@ + + + + + + + Common » Character-Map Template Reference + + $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the + character-map templates in the DocBook XSL Stylesheets. + + These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + The character-map templates don’t import or include + any DocBook XSL stylesheet parameters, so the + character-map templates can be used without importing the + whole set of parameters. + + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + Applies an XSLT character map + + This template applies an XSLT character map; that is, it causes certain + individual characters to be substituted with strings of one + or more characters. It is useful mainly for replacing + multiple “special” characters or symbols in the same target + content. It uses the value of + map.contents to do substitution on + content, and then returns the + modified contents. + + This template is a very slightly modified version of + Jeni Tennison’s replace_strings + template in the multiple string replacements section of Dave Pawson’s + XSLT FAQ. + The apply-string-subst-map + template is essentially the same template as the + apply-character-map template; the + only difference is that in the map that + apply-string-subst-map expects, oldstring and newstring attributes are used + instead of character and string attributes. + + + + + content + + The content on which to perform the character-map + substitution. + + + map.contents + + A node set of elements, with each element having + the following attributes: + + + character, a + character to be replaced + + + string, a + string with which to replace character + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reads in all or part of an XSLT character map + + The XSLT 2.0 specification describes character maps and explains how they may be used + to allow a specific character appearing in a text or + attribute node in a final result tree to be substituted by + a specified string of characters during serialization. The + read-character-map template provides a + means for reading and using character maps with XSLT + 1.0-based tools. + This template reads the character-map contents from + uri (in full or in part, depending on + the value of the use.subset + parameter), then passes those contents to the + apply-character-map template, along with + content, the data on which to perform + the character substitution. + Using the character map “in part” means that it uses only + those output-character elements that match the + XPath expression given in the value of the + subset.profile parameter. The current + implementation of that capability here relies on the + evaluate extension XSLT function. + + + + use.subset + + Specifies whether to use a subset of the character + map instead of the whole map; boolean + 0 or 1 + + + subset.profile + + XPath expression that specifies what subset of the + character map to use + + + uri + + URI for a character map + + + + + + + + + + + + + + + + + + + + + + + +Error: To process character-map subsets, you must use an XSLT engine +that supports the evaluate() XSLT extension function. Your XSLT engine +does not support it. + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/common.xml b/docs/xsl-generic/common/common.xml new file mode 100644 index 00000000..93ed030b --- /dev/null +++ b/docs/xsl-generic/common/common.xml @@ -0,0 +1,622 @@ + + + + + Common » Base Template Reference + + $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the “base” + set of common templates in the DocBook XSL Stylesheets. + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +is.component +Tests if a given node is a component-level element + + +<xsl:template name="is.component"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The node which is to be tested. + + + + + +Returns + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + + +is.section +Tests if a given node is a section-level element + + +<xsl:template name="is.section"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The node which is to be tested. + + + + + +Returns + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + + +section.level +Returns the hierarchical level of a section + + +<xsl:template name="section.level"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template calculates the hierarchical level of a section. +The element <tag>sect1</tag> is at level 1, <tag>sect2</tag> is +at level 2, etc.</para> + + + +<para>Recursive sections are calculated down to the fifth level.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The section node for which the level should be calculated. +Defaults to the context node. + + + + + +Returns + +The section level, 1, 2, etc. + + + + + + +qanda.section.level +Returns the hierarchical level of a QandASet + + +<xsl:template name="qanda.section.level"/> + + + +<para>This template calculates the hierarchical level of a QandASet. +</para> + +</refsect1><refsect1><title>Returns + +The level, 1, 2, etc. + + + + + + +select.mediaobject +Selects and processes an appropriate media object from a list + + +<xsl:template name="select.mediaobject"> +<xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> + ... +</xsl:template> + + + +<para>This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and processes +the "right" object.</para> + + + +<para>This template relies on a template named +"select.mediaobject.index" to determine which object +in the list is appropriate.</para> + + + +<para>If no acceptable object is located, nothing happens.</para> + +</refsect1><refsect1><title>Parameters + + +olist + + +The node list of potential objects to examine. + + + + + +Returns + +Calls <xsl:apply-templates> on the selected object. + + + + + +select.mediaobject.index +Selects the position of the appropriate media object from a list + + +<xsl:template name="select.mediaobject.index"> +<xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> +<xsl:param name="count">1</xsl:param> + ... +</xsl:template> + + + +<para>This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and determines +the "right" object. It returns the position of that object +to be used by the calling template.</para> + + + +<para>If the parameter <parameter>use.role.for.mediaobject</parameter> +is nonzero, then it first checks for an object with +a role attribute of the appropriate value. It takes the first +of those. Otherwise, it takes the first acceptable object +through a recursive pass through the list.</para> + + + +<para>This template relies on a template named "is.acceptable.mediaobject" +to determine if a given object is an acceptable graphic. The semantics +of media objects is that the first acceptable graphic should be used. +</para> + + + +<para>If no acceptable object is located, no index is returned.</para> + +</refsect1><refsect1><title>Parameters + + +olist + + +The node list of potential objects to examine. + + + +count + + +The position in the list currently being considered by the +recursive process. + + + + + +Returns + +Returns the position in the original list of the selected object. + + + + + +is.acceptable.mediaobject +Returns '1' if the specified media object is recognized + + +<xsl:template name="is.acceptable.mediaobject"> +<xsl:param name="object"/> + ... +</xsl:template> + + + +<para>This template examines a media object and returns '1' if the +object is recognized as a graphic.</para> + +</refsect1><refsect1><title>Parameters + + +object + + +The media object to consider. + + + + + +Returns + +0 or 1 + + + + + +check.id.unique +Warn users about references to non-unique IDs + + +<xsl:template name="check.id.unique"> +<xsl:param name="linkend"/> + ... +</xsl:template> + + + +<para>If passed an ID in <varname>linkend</varname>, +<function>check.id.unique</function> prints +a warning message to the user if either the ID does not exist or +the ID is not unique.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.check.idref.targets"> +<refnamediv> +<refname>check.idref.targets</refname> +<refpurpose>Warn users about incorrectly typed references</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="check.idref.targets"> +<xsl:param name="linkend"/> +<xsl:param name="element-list"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>If passed an ID in <varname>linkend</varname>, +<function>check.idref.targets</function> makes sure that the element +pointed to by the link is one of the elements listed in +<varname>element-list</varname> and warns the user otherwise.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.copyright.years"> +<refnamediv> +<refname>copyright.years</refname> +<refpurpose>Print a set of years with collapsed ranges</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="copyright.years"> +<xsl:param name="years"/> +<xsl:param name="print.ranges" select="1"/> +<xsl:param name="single.year.ranges" select="0"/> +<xsl:param name="firstyear" select="0"/> +<xsl:param name="nextyear" select="0"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>This template prints a list of year elements with consecutive +years printed as a range. In other words:</para> + + +<screen><year>1992</year> +<year>1993</year> +<year>1994</year></screen> + + +<para>is printed <quote>1992-1994</quote>, whereas:</para> + + +<screen><year>1992</year> +<year>1994</year></screen> + + +<para>is printed <quote>1992, 1994</quote>.</para> + + + +<para>This template assumes that all the year elements contain only +decimal year numbers, that the elements are sorted in increasing +numerical order, that there are no duplicates, and that all the years +are expressed in full <quote>century+year</quote> +(<quote>1999</quote> not <quote>99</quote>) notation.</para> + +</refsect1><refsect1><title>Parameters + + +years + + +The initial set of year elements. + + + +print.ranges + + +If non-zero, multi-year ranges are collapsed. If zero, all years +are printed discretely. + + + +single.year.ranges + + +If non-zero, two consecutive years will be printed as a range, +otherwise, they will be printed discretely. In other words, a single +year range is 1991-1992 but discretely it's +1991, 1992. + + + + + +Returns + +This template returns the formatted list of years. + + + + + +find.path.params +Search in a table for the "best" match for the node + + +<xsl:template name="find.path.params"> +<xsl:param name="node" select="."/> +<xsl:param name="table" select="''"/> +<xsl:param name="location"> + <xsl:call-template name="xpath.location"> + <xsl:with-param name="node" select="$node"/> + </xsl:call-template> + </xsl:param> + ... +</xsl:template> + + + +<para>This template searches in a table for the value that most-closely +(in the typical best-match sense of XSLT) matches the current (element) +node location.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.string.upper"> +<refnamediv> +<refname>string.upper</refname> +<refpurpose>Converts a string to all uppercase letters</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="string.upper"> +<xsl:param name="string" select="''"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>Given a string, this template does a language-aware conversion +of that string to all uppercase letters, based on the values of the +<literal>lowercase.alpha</literal> and +<literal>uppercase.alpha</literal> gentext keys for the current +locale. It affects only those characters found in the values of +<literal>lowercase.alpha</literal> and +<literal>uppercase.alpha</literal>. All other characters are left +unchanged.</para> + +</refsect1><refsect1><title>Parameters + + +string + + +The string to convert to uppercase. + + + + + + + + + +string.lower +Converts a string to all lowercase letters + + +<xsl:template name="string.lower"> +<xsl:param name="string" select="''"/> + ... +</xsl:template> + + + +<para>Given a string, this template does a language-aware conversion +of that string to all lowercase letters, based on the values of the +<literal>uppercase.alpha</literal> and +<literal>lowercase.alpha</literal> gentext keys for the current +locale. It affects only those characters found in the values of +<literal>uppercase.alpha</literal> and +<literal>lowercase.alpha</literal>. All other characters are left +unchanged.</para> + +</refsect1><refsect1><title>Parameters + + +string + + +The string to convert to lowercase. + + + + + + + + + +select.choice.separator +Returns localized choice separator + + +<xsl:template name="select.choice.separator"/> + + + +<para>This template enables auto-generation of an appropriate + localized "choice" separator (for example, "and" or "or") before + the final item in an inline list (though it could also be useful + for generating choice separators for non-inline lists).</para> + + +<para>It currently works by evaluating a processing instruction + (PI) of the form <?dbchoice choice="foo"?> : + +<itemizedlist> + <listitem> + <simpara>if the value of the <tag>choice</tag> + pseudo-attribute is "and" or "or", returns a localized "and" + or "or"</simpara> + </listitem> + <listitem> + <simpara>otherwise returns the literal value of the + <tag>choice</tag> pseudo-attribute</simpara> + </listitem> + </itemizedlist> + + The latter is provided only as a temporary workaround because the + locale files do not currently have translations for the word + <wordasword>or</wordasword>. So if you want to generate a a + logical "or" separator in French (for example), you currently need + to do this: + <literallayout><?dbchoice choice="ou"?></literallayout> + </para> + + <warning> + +<para>The <tag>dbchoice</tag> processing instruction is + an unfortunate hack; support for it may disappear in the future + (particularly if and when a more appropriate means for marking + up "choice" lists becomes available in DocBook).</para> + + </warning> + </refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.evaluate.info.profile"> +<refnamediv> +<refname>evaluate.info.profile</refname> +<refpurpose>Evaluates an info profile</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="evaluate.info.profile"> +<xsl:param name="profile"/> +<xsl:param name="info"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>This template evaluates an "info profile" matching the XPath + expression given by the <parameter>profile</parameter> + parameter. It relies on the XSLT <function>evaluate()</function> + extension function.</para> + + + +<para>The value of the <parameter>profile</parameter> parameter + can include the literal string <literal>$info</literal>. If found + in the value of the <parameter>profile</parameter> parameter, the + literal string <literal>$info</literal> string is replaced with + the value of the <parameter>info</parameter> parameter, which + should be a set of <replaceable>*info</replaceable> nodes; the + expression is then evaluated using the XSLT + <function>evaluate()</function> extension function.</para> + + </refsect1><refsect1><title>Parameters + + + + profile + + +A string representing an XPath expression + + + + + info + + +A set of *info nodes + + + + + + Returns + +Returns a node (the result of evaluating the + profile parameter) + + + + diff --git a/docs/xsl-generic/common/common.xsl b/docs/xsl-generic/common/common.xsl new file mode 100644 index 00000000..0854ea68 --- /dev/null +++ b/docs/xsl-generic/common/common.xsl @@ -0,0 +1,1981 @@ + + + + + + + + Common » Base Template Reference + + $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the “base” + set of common templates in the DocBook XSL Stylesheets. + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + + + + + + + +Tests if a given node is a component-level element + + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + +node + +The node which is to be tested. + + + + + + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + + + + 1 + 0 + + + + + + +Tests if a given node is a section-level element + + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + +node + +The node which is to be tested. + + + + + + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + + + + 1 + 0 + + + + + + +Returns the hierarchical level of a section + + +This template calculates the hierarchical level of a section. +The element sect1 is at level 1, sect2 is +at level 2, etc. + +Recursive sections are calculated down to the fifth level. + + + + +node + +The section node for which the level should be calculated. +Defaults to the context node. + + + + + + +The section level, 1, 2, etc. + + + + + + + + 1 + 2 + 3 + 4 + 5 + + + 6 + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + 2 + 3 + 4 + 5 + 5 + + + 5 + 4 + 3 + 2 + + + 1 + + + 1 + + + + +Returns the hierarchical level of a QandASet + + +This template calculates the hierarchical level of a QandASet. + + + + +The level, 1, 2, etc. + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + 2 + 3 + + + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + question + answer + qandadiv + qandaset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [FAMILY Given] + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ +] +{ +} + + +[ +] +... + + + | +4pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Selects and processes an appropriate media object from a list + + +This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and processes +the "right" object. + +This template relies on a template named +"select.mediaobject.index" to determine which object +in the list is appropriate. + +If no acceptable object is located, nothing happens. + + + + +olist + +The node list of potential objects to examine. + + + + + + +Calls <xsl:apply-templates> on the selected object. + + + + + + + + + + + + + + + + + + + + + +Selects the position of the appropriate media object from a list + + +This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and determines +the "right" object. It returns the position of that object +to be used by the calling template. + +If the parameter use.role.for.mediaobject +is nonzero, then it first checks for an object with +a role attribute of the appropriate value. It takes the first +of those. Otherwise, it takes the first acceptable object +through a recursive pass through the list. + +This template relies on a template named "is.acceptable.mediaobject" +to determine if a given object is an acceptable graphic. The semantics +of media objects is that the first acceptable graphic should be used. + + +If no acceptable object is located, no index is returned. + + + + +olist + +The node list of potential objects to examine. + + +count + +The position in the list currently being considered by the +recursive process. + + + + + + +Returns the position in the original list of the selected object. + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + + + + 0 + + + + 1 + + + + 0 + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Returns '1' if the specified media object is recognized + + +This template examines a media object and returns '1' if the +object is recognized as a graphic. + + + + +object + +The media object to consider. + + + + + + +0 or 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + + + + + . + + + + + + + + + + + + + + + + +Warn users about references to non-unique IDs + +If passed an ID in linkend, +check.id.unique prints +a warning message to the user if either the ID does not exist or +the ID is not unique. + + + + + + + + + + + + Error: no ID for constraint linkend: + + . + + + + + + + Warning: multiple "IDs" for constraint linkend: + + . + + + + + + +Warn users about incorrectly typed references + +If passed an ID in linkend, +check.idref.targets makes sure that the element +pointed to by the link is one of the elements listed in +element-list and warns the user otherwise. + + + + + + + + + + + + + + Error: linkend ( + + ) points to " + + " not (one of): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unexpected context in procedure.step.numeration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + loweralpha + lowerroman + upperalpha + upperroman + arabic + arabic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + circle + square + disc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Print a set of years with collapsed ranges + + +This template prints a list of year elements with consecutive +years printed as a range. In other words: + +1992
+1993 +1994]]> + +is printed 1992-1994, whereas: + +1992
+1994]]> + +is printed 1992, 1994. + +This template assumes that all the year elements contain only +decimal year numbers, that the elements are sorted in increasing +numerical order, that there are no duplicates, and that all the years +are expressed in full century+year +(1999 not 99) notation. + + + + +years + +The initial set of year elements. + + +print.ranges + +If non-zero, multi-year ranges are collapsed. If zero, all years +are printed discretely. + + +single.year.ranges + +If non-zero, two consecutive years will be printed as a range, +otherwise, they will be printed discretely. In other words, a single +year range is 1991-1992 but discretely it's +1991, 1992. + + + + + + +This template returns the formatted list of years. + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + , + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + , + + , + + + + - + + , + + + + + + + + + + + + + + + + +Search in a table for the "best" match for the node + + +This template searches in a table for the value that most-closely +(in the typical best-match sense of XSLT) matches the current (element) +node location. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + +Converts a string to all uppercase letters + + +Given a string, this template does a language-aware conversion +of that string to all uppercase letters, based on the values of the +lowercase.alpha and +uppercase.alpha gentext keys for the current +locale. It affects only those characters found in the values of +lowercase.alpha and +uppercase.alpha. All other characters are left +unchanged. + + + + +string + +The string to convert to uppercase. + + + + + + + + + + + + + + + + + + + + + + + +Converts a string to all lowercase letters + + +Given a string, this template does a language-aware conversion +of that string to all lowercase letters, based on the values of the +uppercase.alpha and +lowercase.alpha gentext keys for the current +locale. It affects only those characters found in the values of +uppercase.alpha and +lowercase.alpha. All other characters are left +unchanged. + + + + +string + +The string to convert to lowercase. + + + + + + + + + + + + + + + + + + + + + + + + Returns localized choice separator + + This template enables auto-generation of an appropriate + localized "choice" separator (for example, "and" or "or") before + the final item in an inline list (though it could also be useful + for generating choice separators for non-inline lists). + It currently works by evaluating a processing instruction + (PI) of the form <?dbchoice choice="foo"?> : + + + if the value of the choice + pseudo-attribute is "and" or "or", returns a localized "and" + or "or" + + + otherwise returns the literal value of the + choice pseudo-attribute + + + The latter is provided only as a temporary workaround because the + locale files do not currently have translations for the word + or. So if you want to generate a a + logical "or" separator in French (for example), you currently need + to do this: + <?dbchoice choice="ou"?> + + + The dbchoice processing instruction is + an unfortunate hack; support for it may disappear in the future + (particularly if and when a more appropriate means for marking + up "choice" lists becomes available in DocBook). + + + + + + + + + + + + + + + + + + + + + + + + + + Evaluates an info profile + + This template evaluates an "info profile" matching the XPath + expression given by the profile + parameter. It relies on the XSLT evaluate() + extension function. + + The value of the profile parameter + can include the literal string $info. If found + in the value of the profile parameter, the + literal string $info string is replaced with + the value of the info parameter, which + should be a set of *info nodes; the + expression is then evaluated using the XSLT + evaluate() extension function. + + + + + profile + + A string representing an XPath expression + + + + info + + A set of *info nodes + + + + + + + Returns a node (the result of evaluating the + profile parameter) + + + + + + + + + + + + + + + + +Error: The "info profiling" mechanism currently requires an XSLT +engine that supports the evaluate() XSLT extension function. Your XSLT +engine does not support it. + + + + + diff --git a/docs/xsl-generic/common/cs.xml b/docs/xsl-generic/common/cs.xml new file mode 100644 index 00000000..b00f83fd --- /dev/null +++ b/docs/xsl-generic/common/cs.xml @@ -0,0 +1,694 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symboly +A +a +Á +á +B +b +C +c +Č +č +D +d +Ď +ď +E +e +É +é +Ě +ě +Ë +ë +F +f +G +g +H +h +Ch +ch +cH +CH +I +i +Í +í +J +j +K +k +L +l +M +m +N +n +Ň +ň +O +o +Ó +ó +Ö +ö +P +p +Q +q +R +r +Ř +ř +S +s +Š +š +T +t +Ť +ť +U +u +Ú +ú +Ů +ů +Ü +ü +V +v +W +w +X +x +Y +y +Ý +ý +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/cy.xml b/docs/xsl-generic/common/cy.xml new file mode 100644 index 00000000..0790f5a2 --- /dev/null +++ b/docs/xsl-generic/common/cy.xml @@ -0,0 +1,1239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +Ch +ch +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +Dd +dd +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +Ff +ff +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +Ng +ng +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +Ll +ll +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Ph +ph +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +Rh +rh +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +Th +th +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/da.xml b/docs/xsl-generic/common/da.xml new file mode 100644 index 00000000..c415b8a2 --- /dev/null +++ b/docs/xsl-generic/common/da.xml @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Æ +æ +Ø +ø +Å +å + + diff --git a/docs/xsl-generic/common/de.xml b/docs/xsl-generic/common/de.xml new file mode 100644 index 00000000..bc2aedf6 --- /dev/null +++ b/docs/xsl-generic/common/de.xml @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbole +A +a +Ä +ä +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +P +p +Q +q +R +r +S +s +T +t +U +u +Ü +ü +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/el.xml b/docs/xsl-generic/common/el.xml new file mode 100644 index 00000000..a0556621 --- /dev/null +++ b/docs/xsl-generic/common/el.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/en.xml b/docs/xsl-generic/common/en.xml new file mode 100644 index 00000000..1aa99856 --- /dev/null +++ b/docs/xsl-generic/common/en.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/entities.ent b/docs/xsl-generic/common/entities.ent new file mode 100644 index 00000000..e6b7b8eb --- /dev/null +++ b/docs/xsl-generic/common/entities.ent @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + normalize.sort.input + + + + + + normalize.sort.output + + +'> diff --git a/docs/xsl-generic/common/eo.xml b/docs/xsl-generic/common/eo.xml new file mode 100644 index 00000000..14a66e15 --- /dev/null +++ b/docs/xsl-generic/common/eo.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/es.xml b/docs/xsl-generic/common/es.xml new file mode 100644 index 00000000..11592e1e --- /dev/null +++ b/docs/xsl-generic/common/es.xml @@ -0,0 +1,670 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Símbolos +A +a +á +Á +B +b +C +c +CH +ch +D +d +E +e +É +é +F +f +G +g +H +h +I +i +Í +í +J +j +K +k +L +l +LL +ll +M +m +N +n +Ñ +ñ +O +o +Ó +ó +P +p +Q +q +R +r +S +s +T +t +U +u +Ú +ú +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/et.xml b/docs/xsl-generic/common/et.xml new file mode 100644 index 00000000..e63621f7 --- /dev/null +++ b/docs/xsl-generic/common/et.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/eu.xml b/docs/xsl-generic/common/eu.xml new file mode 100644 index 00000000..f58f0d4d --- /dev/null +++ b/docs/xsl-generic/common/eu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/fa.xml b/docs/xsl-generic/common/fa.xml new file mode 100644 index 00000000..ac2aed2c --- /dev/null +++ b/docs/xsl-generic/common/fa.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/fi.xml b/docs/xsl-generic/common/fi.xml new file mode 100644 index 00000000..d28802ae --- /dev/null +++ b/docs/xsl-generic/common/fi.xml @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbole +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +Š +š +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Ž +ž +Å +å +Ä +ä +Ö +ö + + diff --git a/docs/xsl-generic/common/fr.xml b/docs/xsl-generic/common/fr.xml new file mode 100644 index 00000000..58826b96 --- /dev/null +++ b/docs/xsl-generic/common/fr.xml @@ -0,0 +1,684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symboles +A +a +à +À +â + +Æ +æ +B +b +C +c +ç +D +d +E +e +ê +Ê +é +É +è +È +ë +Ë + +F +f +G +g +H +h +I +i +Î +î +Ï +ï +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +Œ +œ +P +p +Q +q +R +r +S +s +T +t +U +u +Ù +ù +Û +û +Ü +ü +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/ga.xml b/docs/xsl-generic/common/ga.xml new file mode 100644 index 00000000..78274b29 --- /dev/null +++ b/docs/xsl-generic/common/ga.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Siombailí +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/gentext.xsl b/docs/xsl-generic/common/gentext.xsl new file mode 100644 index 00000000..e1da36d9 --- /dev/null +++ b/docs/xsl-generic/common/gentext.xsl @@ -0,0 +1,831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .formal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + object.xref.markup: empty xref template + for linkend=" + + " and @xrefstyle=" + + " + + + + + + + + + + + + + + + + + + + + + + + + + + + Xref is only supported to listitems in an + orderedlist: + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + %n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + Attempt to use %d in gentext with no referrer! + + + + + + + % + + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + labelnumber + + + labelname + + + label + + + + + + + + quotedtitle + + + title + + + + + + + + + + + + + + nopage + + + pagenumber + + + pageabbrev + + + Page + + + page + + + + + + + + + + + nodocname + + + docnamelong + + + docname + + + + + + + + + + + + + + + + + + + + + + %n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %t + + + + + + %t + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %p + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/gu.xml b/docs/xsl-generic/common/gu.xml new file mode 100644 index 00000000..050483e4 --- /dev/null +++ b/docs/xsl-generic/common/gu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/he.xml b/docs/xsl-generic/common/he.xml new file mode 100644 index 00000000..8e234b8d --- /dev/null +++ b/docs/xsl-generic/common/he.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hi.xml b/docs/xsl-generic/common/hi.xml new file mode 100644 index 00000000..690a2cd2 --- /dev/null +++ b/docs/xsl-generic/common/hi.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hr.xml b/docs/xsl-generic/common/hr.xml new file mode 100644 index 00000000..d8378e4d --- /dev/null +++ b/docs/xsl-generic/common/hr.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hu.xml b/docs/xsl-generic/common/hu.xml new file mode 100644 index 00000000..be795c7f --- /dev/null +++ b/docs/xsl-generic/common/hu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/id.xml b/docs/xsl-generic/common/id.xml new file mode 100644 index 00000000..7ad10d0a --- /dev/null +++ b/docs/xsl-generic/common/id.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/insertfile.xsl b/docs/xsl-generic/common/insertfile.xsl new file mode 100644 index 00000000..66bcf410 --- /dev/null +++ b/docs/xsl-generic/common/insertfile.xsl @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/it.xml b/docs/xsl-generic/common/it.xml new file mode 100644 index 00000000..fc473dfe --- /dev/null +++ b/docs/xsl-generic/common/it.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ja.xml b/docs/xsl-generic/common/ja.xml new file mode 100644 index 00000000..aedc1b12 --- /dev/null +++ b/docs/xsl-generic/common/ja.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/kn.xml b/docs/xsl-generic/common/kn.xml new file mode 100644 index 00000000..7128add7 --- /dev/null +++ b/docs/xsl-generic/common/kn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ko.xml b/docs/xsl-generic/common/ko.xml new file mode 100644 index 00000000..02a4ec01 --- /dev/null +++ b/docs/xsl-generic/common/ko.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/l10n.dtd b/docs/xsl-generic/common/l10n.dtd new file mode 100644 index 00000000..1d6f8361 --- /dev/null +++ b/docs/xsl-generic/common/l10n.dtd @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/l10n.xml b/docs/xsl-generic/common/l10n.xml new file mode 100644 index 00000000..23b2636b --- /dev/null +++ b/docs/xsl-generic/common/l10n.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + +⁡ +&am; +&ar; +&az; +&bg; +&bn; +&bs; +&ca; +&cs; +&cy; +&da; +&de; +⪙ +&en; +&eo; +&es; +&et; +&eu; +&fa; +&fi; +&fr; +&ga; +&gu; +&he; +&hi; +&hr; +&hu; +&id; +⁢ +&ja; +&kn; +&ko; +&la; +&lit; +&lv; +&mn; +&nl; +&nn; +&no; +∨ +&pa; +&pl; +&pt; +&pt_br; +&ro; +&ru; +&sk; +&sl; +&sq; +&sr; +&sr_Latn; +&sv; +&ta; +&th; +&tl; +&tr; +&uk; +&vi; +&xh; +&zh_cn; +&zh_tw; + diff --git a/docs/xsl-generic/common/l10n.xsl b/docs/xsl-generic/common/l10n.xsl new file mode 100644 index 00000000..c385a788 --- /dev/null +++ b/docs/xsl-generic/common/l10n.xsl @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _ + + + + + + + + + + + + + + + + + + + + No localization exists for " + + " or " + + ". Using default " + + ". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of " + + " exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bullet + + + + + + + + + + + + + + + + + + No " + + " localization of dingbat + + exists; using "en". + + + + + + + + + + startquote + + + + + + endquote + + + + + + nestedstartquote + + + + + + nestedendquote + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization exists. + + + + + + + + + + No context named " + + " exists in the " + + " localization. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No template for " + + " (or any of its leaves) exists +in the context named " + + " in the " + + " localization. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + diff --git a/docs/xsl-generic/common/la.xml b/docs/xsl-generic/common/la.xml new file mode 100644 index 00000000..ee1a4ec4 --- /dev/null +++ b/docs/xsl-generic/common/la.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/labels.xsl b/docs/xsl-generic/common/labels.xsl new file mode 100644 index 00000000..5596f1b7 --- /dev/null +++ b/docs/xsl-generic/common/labels.xsl @@ -0,0 +1,869 @@ + + + + + + + + + + +Provides access to element labels + +Processing an element in the +label.markup mode produces the +element label. +Trailing punctuation is not added to the label. + + + + + + . + + + + + + + Request for label of unexpected element: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label.markup: this can't happen! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + a + i + A + I + + + + Unexpected numeration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + +Returns true if $section should be labelled + +Returns true if the specified section should be labelled. +By default, this template returns zero unless +the section level is less than or equal to the value of the +$section.autolabel.max.depth parameter, in +which case it returns +$section.autolabel. +Custom stylesheets may override it to get more selective behavior. + + + + + + + + + + + + + + + 1 + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + Unexpected .autolabel value: + ; using default. + + + + + + + + + +Returns format for autolabel parameters + +Returns format passed as parameter if non zero. Supported + format are 'arabic' or '1', 'loweralpha' or 'a', 'lowerroman' or 'i', + 'upperlapha' or 'A', 'upperroman' or 'I', 'arabicindic' or '١'. + If its not one of these then + returns the default format. + + + + + + diff --git a/docs/xsl-generic/common/lt.xml b/docs/xsl-generic/common/lt.xml new file mode 100644 index 00000000..7ee48f07 --- /dev/null +++ b/docs/xsl-generic/common/lt.xml @@ -0,0 +1,672 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboliai +A +a +Ą +ą +B +b +C +c +Č +č +D +d +E +e +Ę +ę +Ė +ė +F +f +G +g +H +h +I +i +Į +į +Y +y +J +j +K +k +L +l +M +m +N +n +O +o +P +p +R +r +S +s +Š +š +T +t +U +u +Ų +ų +Ū +ū +V +v +Z +z +Ž +ž +Q +q +W +w +X +x + + diff --git a/docs/xsl-generic/common/lv.xml b/docs/xsl-generic/common/lv.xml new file mode 100644 index 00000000..b1eb73a6 --- /dev/null +++ b/docs/xsl-generic/common/lv.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/mn.xml b/docs/xsl-generic/common/mn.xml new file mode 100644 index 00000000..ce24b213 --- /dev/null +++ b/docs/xsl-generic/common/mn.xml @@ -0,0 +1,724 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Тэмдэгтүүд +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ё +ё +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +Ө +ө +П +п +Р +р +С +с +Т +т +У +у +Ү +ү +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ы +ы +Ь +ь +Э +э +Ю +ю +Я +я + + diff --git a/docs/xsl-generic/common/nl.xml b/docs/xsl-generic/common/nl.xml new file mode 100644 index 00000000..41b69ed6 --- /dev/null +++ b/docs/xsl-generic/common/nl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/nn.xml b/docs/xsl-generic/common/nn.xml new file mode 100644 index 00000000..fa1db047 --- /dev/null +++ b/docs/xsl-generic/common/nn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/no.xml b/docs/xsl-generic/common/no.xml new file mode 100644 index 00000000..27e2279e --- /dev/null +++ b/docs/xsl-generic/common/no.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/olink.xsl b/docs/xsl-generic/common/olink.xsl new file mode 100644 index 00000000..91849263 --- /dev/null +++ b/docs/xsl-generic/common/olink.xsl @@ -0,0 +1,1149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olinks not processed: must specify a + $target.database.document parameter + when using olinks with targetdoc + and targetptr attributes. + + + + + + Olink error: could not open target database ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: cases for targetdoc=' + + ' and targetptr=' + + ' in language ' + + '. + + + + + + + + + + + + + + Olink debug: CaseA matched. + + + + Olink debug: CaseA NOT matched + + + + + + + + + + + + + + + + Olink debug: CaseB matched. + + + + Olink debug: CaseB NOT matched + + + + + + + + + + + + + + + + + Olink debug: CaseC matched. + + + + Olink debug: CaseC NOT matched. + + + + + + + + + + + + + + + + + Olink debug: CaseD matched. + + + + Olink debug: CaseD NOT matched + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: CaseE matched. + + + + Olink debug: CaseE NOT matched. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: CaseF matched. + + + + Olink debug: CaseF NOT matched. + + + + + + + + + + + + + + Olink debug: CaseB key is the final selection: + + + + + + + + + Olink debug: CaseA key is the final selection: + + + + + + + + + Olink debug: CaseC key is the final selection: + + + + + + + + + Olink debug: CaseD key is the final selection: + + + + + + + + + Olink debug: CaseF key is the final selection: + + + + + + + + + Olink debug: CaseE key is the final selection: + + + + + + + + Olink debug: No case matched for lang ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: cannot compute relative + sitemap path because $current.docid ' + + ' not found in target database. + + + + + + + Olink warning: cannot compute relative + sitemap path without $current.docid parameter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xrefstyle is ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref-number-and-title + '. Using template without @style. + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref-number + '. Using template without @style. + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref + '. Using template without @style. + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + '. Trying '%t'. + + + + + + + + + + + Olink debug: xrefstyle template is ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: no generated text for + targetdoc/targetptr/lang = ' + + '. + + ???? + + + + + + + Olink error: no generated text for + targetdoc/targetptr/lang = ' + + '. + + + ???? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: cannot locate targetdoc in sitemap + + + + + + + / + + + + + + + + + + + ../ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/or.xml b/docs/xsl-generic/common/or.xml new file mode 100644 index 00000000..0c3593f7 --- /dev/null +++ b/docs/xsl-generic/common/or.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pa.xml b/docs/xsl-generic/common/pa.xml new file mode 100644 index 00000000..c54bf9dd --- /dev/null +++ b/docs/xsl-generic/common/pa.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pi.xsl b/docs/xsl-generic/common/pi.xsl new file mode 100644 index 00000000..bab86644 --- /dev/null +++ b/docs/xsl-generic/common/pi.xsl @@ -0,0 +1,346 @@ + + + + + +Common Processing Instruction Reference + + $Id: pi.xsl 7107 2007-07-22 10:22:06Z xmldoc $ + + + + Introduction + This is generated reference documentation for all + user-specifiable processing instructions (PIs) in the + “common” part of the DocBook XSL stylesheets. + + You add these PIs at particular points in a document to + cause specific “exceptions” to formatting/output behavior. To + make global changes in formatting/output behavior across an + entire document, it’s better to do it by setting an + appropriate stylesheet parameter (if there is one). + + + + + + + + Generates a localized choice separator + + Use the dbchoice choice PI to + generate an appropriate localized “choice” separator (for + example, and or or) + before the final item in an inline simplelist + + This PI is a less-than-ideal hack; support for it may + disappear in the future (particularly if and when a more + appropriate means for marking up "choice" lists becomes + available in DocBook). + + + + dbchoice choice="and"|"or"|string" + + + + choice="and" + + generates a localized and separator + + + choice="or" + + generates a localized or separator + + + choice="string" + + generates a literal string separator + + + + + + + + + + choice + + + + + Inserts a date timestamp + + Use the dbtimestamp PI at any point in a + source document to cause a date timestamp (a formatted + string representing the current date and time) to be + inserted in output of the document. + + + dbtimestamp format="formatstring" [padding="0"|"1"] + + + + format="formatstring" + + Specifies format in which the date and time are + output + + For details of the content of the format string, + see Date and time. + + + + padding="0"|"1" + + Specifies padding behavior; if non-zero, padding is is added + + + + + + + + + + + format + + + + + + + + + + + + + + + + + + + padding + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + Timestamp processing requires XSLT processor with EXSLT date support. + + + + + + + Generates delimiters around embedded TeX equations + in output + + Use the dbtex delims PI as a + child of a textobject containing embedded TeX + markup, to cause that markup to be surrounded by + $ delimiter characters in output. + + + dbtex delims="no"|"yes" + + + + dbtex delims="no"|"yes" + + Specifies whether delimiters are output + + + + + + tex.math.delims + + + DBTeXMath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + 0 + + + + + + + 0 + + + + 0 + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + Timestamp processing requires an XSLT processor with support + for the EXSLT node-set() function. + + + + + + + diff --git a/docs/xsl-generic/common/pl.xml b/docs/xsl-generic/common/pl.xml new file mode 100644 index 00000000..5112a8ee --- /dev/null +++ b/docs/xsl-generic/common/pl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pt.xml b/docs/xsl-generic/common/pt.xml new file mode 100644 index 00000000..d5b32b1e --- /dev/null +++ b/docs/xsl-generic/common/pt.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pt_br.xml b/docs/xsl-generic/common/pt_br.xml new file mode 100644 index 00000000..56c1c910 --- /dev/null +++ b/docs/xsl-generic/common/pt_br.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/refentry.xml b/docs/xsl-generic/common/refentry.xml new file mode 100644 index 00000000..c5e6776b --- /dev/null +++ b/docs/xsl-generic/common/refentry.xml @@ -0,0 +1,781 @@ + + + + + Common » Refentry Metadata Template Reference + + $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the “refentry + metadata” templates in the DocBook XSL Stylesheets. + + +This is not intended to be user documentation. It is provided + for developers writing customization layers for the stylesheets. + + + +Currently, only the manpages stylesheets make use of these + templates. They are, however, potentially useful elsewhere. + + + + + + +get.refentry.metadata +Gathers metadata from a refentry and its ancestors + + +<xsl:template name="get.refentry.metadata"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>Reference documentation for particular commands, functions, + etc., is sometimes viewed in isolation from its greater "context". For + example, users view Unix man pages as, well, individual pages, not as + part of a "book" of some kind. Therefore, it is sometimes necessary to + embed "context" information in output for each <tag>refentry</tag>.</para> + + + +<para>However, one problem is that different users mark up that + context information in different ways. Often (usually), the + context information is not actually part of the content of the + <tag>refentry</tag> itself, but instead part of the content of a + parent or ancestor element to the the <tag>refentry</tag>. And + even then, DocBook provides a variety of elements that users might + potentially use to mark up the same kind of information. One user + might use the <tag>productnumber</tag> element to mark up version + information about a particular product, while another might use + the <tag>releaseinfo</tag> element.</para> + + + +<para>Taking all that in mind, the + <function>get.refentry.metadata</function> template tries to gather + metadata from a <tag>refentry</tag> element and its ancestor + elements in an intelligent and user-configurable way. The basic + mechanism used in the XPath expressions throughout this stylesheet + is to select the relevant metadata from the *info element that is + closest to the actual <tag>refentry</tag> – either on the + <tag>refentry</tag> itself, or on its nearest ancestor.</para> + + + <note> + +<para>The <function>get.refentry.metadata</function> + template is actually just sort of a "driver" template; it + calls other templates that do the actual data collection, + then returns the data as a set.</para> + + </note> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing user preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a node set with the following elements. The + descriptions are verbatim from the man(7) man + page. + + + + title + + +the title of the man page (e.g., MAN) + + + + + section + + +the section number the man page should be placed in (e.g., + 7) + + + + + date + + +the date of the last revision + + + + + source + + +the source of the command + + + + + manual + + +the title of the manual (e.g., Linux + Programmer's Manual) + + + + + + + + + + + +get.refentry.title +Gets title metadata for a refentry + + +<xsl:template name="get.refentry.title"> +<xsl:param name="refname"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + title of the man page (e.g., <literal>MAN</literal>). This differs + from <tag>refname</tag> in that, if the <tag>refentry</tag> has a + <tag>refentrytitle</tag>, we use that as the <tag>title</tag>; + otherwise, we just use first <tag>refname</tag> in the first + <tag>refnamediv</tag> in the source.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + + Returns + +Returns a title node. + + + + +get.refentry.section +Gets section metadata for a refentry + + +<xsl:template name="get.refentry.section"> +<xsl:param name="refname"/> +<xsl:param name="quiet" select="0"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + section number the man page should be placed in (e.g., + <literal>7</literal>)". If we do not find a <tag>manvolnum</tag> + specified in the source, and we find that the <tag>refentry</tag> is + for a function, we use the section number <literal>3</literal> + ["Library calls (functions within program libraries)"]; otherwise, we + default to using <literal>1</literal> ["Executable programs or shell + commands"].</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + quiet + + +If non-zero, no "missing" message is emitted + + + + + + Returns + +Returns a string representing a section number. + + + + +get.refentry.date +Gets date metadata for a refentry + + +<xsl:template name="get.refentry.date"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + date of the last revision". If we cannot find a date in the source, we + generate one.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global stylesheet parameters) + + + + + + Returns + +Returns a date node. + + + + + +get.refentry.source +Gets source metadata for a refentry + + +<xsl:template name="get.refentry.source"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + source of the command", and provides the following examples: + +<itemizedlist> + <listitem> + +<para>For binaries, use something like: GNU, NET-2, SLS + Distribution, MCC Distribution.</para> + + </listitem> + <listitem> + +<para>For system calls, use the version of the kernel that you are + currently looking at: Linux 0.99.11.</para> + + </listitem> + <listitem> + +<para>For library calls, use the source of the function: GNU, BSD + 4.3, Linux DLL 4.4.1.</para> + + </listitem> + </itemizedlist> + + </para> + + + +<para>The <literal>solbook(5)</literal> man page describes + something very much like what <literal>man(7)</literal> calls + "source", except that <literal>solbook(5)</literal> names it + "software" and describes it like this: + <blockquote> + +<para>This is the name of the software product that the topic + discussed on the reference page belongs to. For example UNIX + commands are part of the <literal>SunOS x.x</literal> + release.</para> + + </blockquote> + </para> + + + +<para>In practice, there are many pages that simply have a version + number in the "source" field. So, it looks like what we have is a + two-part field, + <replaceable>Name</replaceable> <replaceable>Version</replaceable>, + where: + +<variablelist> + <varlistentry> + <term>Name</term> + <listitem> + +<para>product name (e.g., BSD) or org. name (e.g., GNU)</para> + + </listitem> + </varlistentry> + <varlistentry> + <term>Version</term> + <listitem> + +<para>version name</para> + + </listitem> + </varlistentry> + </variablelist> + + Each part is optional. If the <replaceable>Name</replaceable> is a + product name, then the <replaceable>Version</replaceable> is probably + the version of the product. Or there may be no + <replaceable>Name</replaceable>, in which case, if there is a + <replaceable>Version</replaceable>, it is probably the version of the + item itself, not the product it is part of. Or, if the + <replaceable>Name</replaceable> is an organization name, then there + probably will be no <replaceable>Version</replaceable>. + </para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a source node. + + + + + +get.refentry.source.name +Gets source-name metadata for a refentry + + +<xsl:template name="get.refentry.source.name"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>A "source name" is one part of a (potentially) two-part + <replaceable>Name</replaceable> <replaceable>Version</replaceable> + source field. For more details, see the documentation for the + <function>get.refentry.source</function> template.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "source name" data. + + + + + +get.refentry.version +Gets version metadata for a refentry + + +<xsl:template name="get.refentry.version"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>A "version" is one part of a (potentially) two-part + <replaceable>Name</replaceable> <replaceable>Version</replaceable> + source field. For more details, see the documentation for the + <function>get.refentry.source</function> template.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "version" data. + + + + + +get.refentry.manual +Gets source metadata for a refentry + + +<xsl:template name="get.refentry.manual"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + title of the manual (e.g., <citetitle>Linux Programmer's + Manual</citetitle>)". Here are some examples from existing man pages: + +<itemizedlist> + <listitem> + +<para><citetitle>dpkg utilities</citetitle> + (<command>dpkg-name</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>User Contributed Perl Documentation</citetitle> + (<command>GET</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>GNU Development Tools</citetitle> + (<command>ld</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>Emperor Norton Utilities</citetitle> + (<command>ddate</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>Debian GNU/Linux manual</citetitle> + (<command>faked</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>GIMP Manual Pages</citetitle> + (<command>gimp</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>KDOC Documentation System</citetitle> + (<command>qt2kdoc</command>)</para> + + </listitem> + </itemizedlist> + + </para> + + + +<para>The <literal>solbook(5)</literal> man page describes + something very much like what <literal>man(7)</literal> calls + "manual", except that <literal>solbook(5)</literal> names it + "sectdesc" and describes it like this: + <blockquote> + +<para>This is the section title of the reference page; for + example <literal>User Commands</literal>.</para> + + </blockquote> + </para> + + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a manual node. + + + + + +get.refentry.metadata.prefs +Gets user preferences for refentry metadata gathering + + +<xsl:template name="get.refentry.metadata.prefs"/> + + + +<para>The DocBook XSL stylesheets include several user-configurable + global stylesheet parameters for controlling <tag>refentry</tag> + metadata gathering. Those parameters are not read directly by the + other <tag>refentry</tag> metadata-gathering + templates. Instead, they are read only by the + <function>get.refentry.metadata.prefs</function> template, + which assembles them into a structure that is then passed to + the other <tag>refentry</tag> metadata-gathering + templates.</para> + + + +<para>So the, <function>get.refentry.metadata.prefs</function> + template is the only interface to collecting stylesheet parameters for + controlling <tag>refentry</tag> metadata gathering.</para> + + </refsect1><refsect1><title>Parameters + +There are no local parameters for this template; however, it + does rely on a number of global parameters. + + Returns + +Returns a manual node. + + + + + +set.refentry.metadata +Sets content of a refentry metadata item + + +<xsl:template name="set.refentry.metadata"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="contents"/> +<xsl:param name="context"/> +<xsl:param name="preferred"/> + ... +</xsl:template> + + + +<para>The <function>set.refentry.metadata</function> template is + called each time a suitable source element is found for a certain + metadata field.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A single *info node that contains the selected source element. + + + + + contents + + +A node containing the selected source element. + + + + + context + + +A string describing the metadata context in which the + set.refentry.metadata template was + called: either "date", "source", "version", or "manual". + + + + + + Returns + +Returns formatted contents of a selected source element. + + + diff --git a/docs/xsl-generic/common/refentry.xsl b/docs/xsl-generic/common/refentry.xsl new file mode 100644 index 00000000..75bc84b0 --- /dev/null +++ b/docs/xsl-generic/common/refentry.xsl @@ -0,0 +1,1277 @@ + + + + + + + + + Common » Refentry Metadata Template Reference + + $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the “refentry + metadata” templates in the DocBook XSL Stylesheets. + This is not intended to be user documentation. It is provided + for developers writing customization layers for the stylesheets. + + Currently, only the manpages stylesheets make use of these + templates. They are, however, potentially useful elsewhere. + + + + + + + Gathers metadata from a refentry and its ancestors + + Reference documentation for particular commands, functions, + etc., is sometimes viewed in isolation from its greater "context". For + example, users view Unix man pages as, well, individual pages, not as + part of a "book" of some kind. Therefore, it is sometimes necessary to + embed "context" information in output for each refentry. + + However, one problem is that different users mark up that + context information in different ways. Often (usually), the + context information is not actually part of the content of the + refentry itself, but instead part of the content of a + parent or ancestor element to the the refentry. And + even then, DocBook provides a variety of elements that users might + potentially use to mark up the same kind of information. One user + might use the productnumber element to mark up version + information about a particular product, while another might use + the releaseinfo element. + + Taking all that in mind, the + get.refentry.metadata template tries to gather + metadata from a refentry element and its ancestor + elements in an intelligent and user-configurable way. The basic + mechanism used in the XPath expressions throughout this stylesheet + is to select the relevant metadata from the *info element that is + closest to the actual refentry – either on the + refentry itself, or on its nearest ancestor. + + + The get.refentry.metadata + template is actually just sort of a "driver" template; it + calls other templates that do the actual data collection, + then returns the data as a set. + + + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing user preferences (from global + stylesheet parameters) + + + + + + Returns a node set with the following elements. The + descriptions are verbatim from the man(7) man + page. + + + title + + the title of the man page (e.g., MAN) + + + + section + + the section number the man page should be placed in (e.g., + 7) + + + + date + + the date of the last revision + + + + source + + the source of the command + + + + manual + + the title of the manual (e.g., Linux + Programmer's Manual) + + + + + + + + + + + + <xsl:call-template name="get.refentry.title"> + <xsl:with-param name="refname" select="$refname"/> + </xsl:call-template> + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + Gets title metadata for a refentry + + The man(7) man page describes this as "the + title of the man page (e.g., MAN). This differs + from refname in that, if the refentry has a + refentrytitle, we use that as the title; + otherwise, we just use first refname in the first + refnamediv in the source. + + + + + refname + + The first refname in the refentry + + + + + + Returns a title node. + + + + + + + + + + + + + + + + + + Gets section metadata for a refentry + + The man(7) man page describes this as "the + section number the man page should be placed in (e.g., + 7)". If we do not find a manvolnum + specified in the source, and we find that the refentry is + for a function, we use the section number 3 + ["Library calls (functions within program libraries)"]; otherwise, we + default to using 1 ["Executable programs or shell + commands"]. + + + + + refname + + The first refname in the refentry + + + + quiet + + If non-zero, no "missing" message is emitted + + + + + + Returns a string representing a section number. + + + + + + + + + + + + + Note + + meta manvol + + no refentry/refmeta/manvolnum + + + + Note + + meta manvol + + see http://docbook.sf.net/el/manvolnum + + + + + + + + + + Note + + meta manvol + + Setting man section to 3 + + + + + 3 + + + 1 + + + + + + + + + Gets date metadata for a refentry + + The man(7) man page describes this as "the + date of the last revision". If we cannot find a date in the source, we + generate one. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global stylesheet parameters) + + + + + + Returns a date node. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + + meta date + + no date; using generated date + + + + Note + + meta date + + see http://docbook.sf.net/el/date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gets source metadata for a refentry + + The man(7) man page describes this as "the + source of the command", and provides the following examples: + + + For binaries, use something like: GNU, NET-2, SLS + Distribution, MCC Distribution. + + + For system calls, use the version of the kernel that you are + currently looking at: Linux 0.99.11. + + + For library calls, use the source of the function: GNU, BSD + 4.3, Linux DLL 4.4.1. + + + + + The solbook(5) man page describes + something very much like what man(7) calls + "source", except that solbook(5) names it + "software" and describes it like this: +
+ This is the name of the software product that the topic + discussed on the reference page belongs to. For example UNIX + commands are part of the SunOS x.x + release. +
+
+ + In practice, there are many pages that simply have a version + number in the "source" field. So, it looks like what we have is a + two-part field, + Name Version, + where: + + + Name + + product name (e.g., BSD) or org. name (e.g., GNU) + + + + Version + + version name + + + + Each part is optional. If the Name is a + product name, then the Version is probably + the version of the product. Or there may be no + Name, in which case, if there is a + Version, it is probably the version of the + item itself, not the product it is part of. Or, if the + Name is an organization name, then there + probably will be no Version. + +
+ + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns a source node. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warn + + meta source + + no valid fallback for source; leaving empty + + + + + + + + + + Warn + + meta source + + no source fallback specified; leaving empty + + + + + + + + + + Gets source-name metadata for a refentry + + A "source name" is one part of a (potentially) two-part + Name Version + source field. For more details, see the documentation for the + get.refentry.source template. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "source name" data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + source + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + Note + + meta source + + no *info/productname or alternative + + + + Note + + meta source + + see http://docbook.sf.net/el/productname + + + + Note + + meta source + + no refentry/refmeta/refmiscinfo@class=source + + + + Note + + meta source + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + Gets version metadata for a refentry + + A "version" is one part of a (potentially) two-part + Name Version + source field. For more details, see the documentation for the + get.refentry.source template. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "version" data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + version + + + + + + + + version + productnumber + + + + + + + + version + productnumber + + + + + + Note + + meta version + + no *info/productnumber or alternative + + + + Note + + meta version + + see http://docbook.sf.net/el/productnumber + + + + Note + + meta version + + no refentry/refmeta/refmiscinfo@class=version + + + + Note + + meta version + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + Gets source metadata for a refentry + + The man(7) man page describes this as "the + title of the manual (e.g., Linux Programmer's + Manual)". Here are some examples from existing man pages: + + + dpkg utilities + (dpkg-name) + + + User Contributed Perl Documentation + (GET) + + + GNU Development Tools + (ld) + + + Emperor Norton Utilities + (ddate) + + + Debian GNU/Linux manual + (faked) + + + GIMP Manual Pages + (gimp) + + + KDOC Documentation System + (qt2kdoc) + + + + + The solbook(5) man page describes + something very much like what man(7) calls + "manual", except that solbook(5) names it + "sectdesc" and describes it like this: +
+ This is the section title of the reference page; for + example User Commands. +
+
+ +
+ + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns a manual node. + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + manual + + + + + + + + manual + + + + + + Note + + meta manual + + no titled ancestor of refentry + + + + Note + + meta manual + + no refentry/refmeta/refmiscinfo@class=manual + + + + Note + + meta manual + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warn + + meta manual + + no valid fallback for manual; leaving empty + + + + + + + + + + + Warn + + meta manual + + no manual fallback specified; leaving empty + + + + + + + + + + Gets user preferences for refentry metadata gathering + + The DocBook XSL stylesheets include several user-configurable + global stylesheet parameters for controlling refentry + metadata gathering. Those parameters are not read directly by the + other refentry metadata-gathering + templates. Instead, they are read only by the + get.refentry.metadata.prefs template, + which assembles them into a structure that is then passed to + the other refentry metadata-gathering + templates. + + So the, get.refentry.metadata.prefs + template is the only interface to collecting stylesheet parameters for + controlling refentry metadata gathering. + + + There are no local parameters for this template; however, it + does rely on a number of global parameters. + + + Returns a manual node. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sets content of a refentry metadata item + + The set.refentry.metadata template is + called each time a suitable source element is found for a certain + metadata field. + + + + + refname + + The first refname in the refentry + + + + info + + A single *info node that contains the selected source element. + + + + contents + + A node containing the selected source element. + + + + context + + A string describing the metadata context in which the + set.refentry.metadata template was + called: either "date", "source", "version", or "manual". + + + + + + Returns formatted contents of a selected source element. + + + + + + + + + + + Note + + + + + + Note + + + + no refentry/refmeta/refmiscinfo@class= + + + + + Note + + + + + + + + + +
diff --git a/docs/xsl-generic/common/ro.xml b/docs/xsl-generic/common/ro.xml new file mode 100644 index 00000000..50bd8dc7 --- /dev/null +++ b/docs/xsl-generic/common/ro.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ru.xml b/docs/xsl-generic/common/ru.xml new file mode 100644 index 00000000..290a9b1b --- /dev/null +++ b/docs/xsl-generic/common/ru.xml @@ -0,0 +1,720 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ё +ё +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +П +п +Р +р +С +с +Т +т +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ы +ы +Ь +ь +Э +э +Ю +ю +Я +я + + diff --git a/docs/xsl-generic/common/sk.xml b/docs/xsl-generic/common/sk.xml new file mode 100644 index 00000000..50f7e2cd --- /dev/null +++ b/docs/xsl-generic/common/sk.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sl.xml b/docs/xsl-generic/common/sl.xml new file mode 100644 index 00000000..43d229ea --- /dev/null +++ b/docs/xsl-generic/common/sl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sq.xml b/docs/xsl-generic/common/sq.xml new file mode 100644 index 00000000..66c97b94 --- /dev/null +++ b/docs/xsl-generic/common/sq.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sr.xml b/docs/xsl-generic/common/sr.xml new file mode 100644 index 00000000..db5db1d9 --- /dev/null +++ b/docs/xsl-generic/common/sr.xml @@ -0,0 +1,714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Симболи +А +а +Б +б +В +в +Г +г +Д +д +Ђ +ђ +Е +е +Ж +ж +З +з +И +и +Ј +ј +К +к +Л +л +Љ +љ +М +м +Н +н +Њ +њ +О +о +П +п +Р +р +С +с +Т +т +Ћ +ћ +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Џ +џ +Ш +ш +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +Q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/sr_Latn.xml b/docs/xsl-generic/common/sr_Latn.xml new file mode 100644 index 00000000..a6e03d0e --- /dev/null +++ b/docs/xsl-generic/common/sr_Latn.xml @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboli +A +a +B +b +C +c +Č +č +Ć +ć +D +d + + + +Đ +đ +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +LJ +Lj +lj +M +m +N +n +NJ +Nj +nj +O +o +P +p +Q +Q +R +r +S +s +Š +š +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/stripns.xsl b/docs/xsl-generic/common/stripns.xsl new file mode 100644 index 00000000..dd1cd7f2 --- /dev/null +++ b/docs/xsl-generic/common/stripns.xsl @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + info + + + objectinfo + + blockinfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WARNING: cannot add @xml:base to node + set root element. + Relative paths may not work. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + + + + + + Stripping namespace from DocBook 5 document. + + + + Processing stripped document. + + + + + + + + + diff --git a/docs/xsl-generic/common/subtitles.xsl b/docs/xsl-generic/common/subtitles.xsl new file mode 100644 index 00000000..8211c84b --- /dev/null +++ b/docs/xsl-generic/common/subtitles.xsl @@ -0,0 +1,155 @@ + + + + + + + + + + +Provides access to element subtitles + +Processing an element in the +subtitle.markup mode produces the +subtitle of the element. + + + + + + + Request for subtitle of unexpected element: + + + ???SUBTITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/sv.xml b/docs/xsl-generic/common/sv.xml new file mode 100644 index 00000000..1e7adbbe --- /dev/null +++ b/docs/xsl-generic/common/sv.xml @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Å +å +Ä +ä +Ö +ö + + diff --git a/docs/xsl-generic/common/ta.xml b/docs/xsl-generic/common/ta.xml new file mode 100644 index 00000000..448136a1 --- /dev/null +++ b/docs/xsl-generic/common/ta.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/table.xsl b/docs/xsl-generic/common/table.xsl new file mode 100644 index 00000000..72d50d38 --- /dev/null +++ b/docs/xsl-generic/common/table.xsl @@ -0,0 +1,514 @@ + + + + + + + + + + + 0: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0: + + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + +Determine the column number in which a given entry occurs + +If an entry has a +colname or +namest attribute, this template +will determine the number of the column in which the entry should occur. +For other entrys, nothing is returned. + + + +entry + +The entry-element which is to be tested. + + + + + + +This template returns the column number if it can be determined, +or 0 (the empty string) + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/targetdatabase.dtd b/docs/xsl-generic/common/targetdatabase.dtd new file mode 100644 index 00000000..2ace1e00 --- /dev/null +++ b/docs/xsl-generic/common/targetdatabase.dtd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/targets.xsl b/docs/xsl-generic/common/targets.xsl new file mode 100644 index 00000000..8098d9ba --- /dev/null +++ b/docs/xsl-generic/common/targets.xsl @@ -0,0 +1,272 @@ + + + + + + + + + + +Collects information for potential cross reference targets + +Processing the root element in the +collect.targets mode produces +a set of target database elements that can be used by +the olink mechanism to resolve external cross references. +The collection process is controlled by the +collect.xref.targets parameter, which can be +yes to collect targets and process +the document for output, only to +only collect the targets, and no +(default) to not collect the targets and only process the document. + + +A targets.filename parameter must be +specified to receive the output if +collect.xref.targets is +set to yes so as to +redirect the target data to a file separate from the +document output. + + + + + + + + + + + Must specify a $targets.filename parameter when + $collect.xref.targets is set to 'yes'. + The xref targets were not collected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/docs/xsl-generic/common/th.xml b/docs/xsl-generic/common/th.xml new file mode 100644 index 00000000..9728900a --- /dev/null +++ b/docs/xsl-generic/common/th.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/titles.xsl b/docs/xsl-generic/common/titles.xsl new file mode 100644 index 00000000..62e1db56 --- /dev/null +++ b/docs/xsl-generic/common/titles.xsl @@ -0,0 +1,740 @@ + + + + + + + + + + +Provides access to element titles + +Processing an element in the +title.markup mode produces the +title of the element. This does not include the label. + + + + + + + + + + + + + + + + + + + + + + + Request for title of element with no title: + + + + (id=" + + ") + + + (xml:id=" + + ") + + + + + ???TITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REFENTRY WITHOUT TITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ERROR: glossdiv missing its required title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + Important + Caution + Warning + Tip + + + + + + + + + + Question + + + + + Answer + + + + + Question + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + XRef to nonexistent id: + + + ??? + + + + + + + + + + Endterm points to nonexistent ID: + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/tl.xml b/docs/xsl-generic/common/tl.xml new file mode 100644 index 00000000..b4894f23 --- /dev/null +++ b/docs/xsl-generic/common/tl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/tr.xml b/docs/xsl-generic/common/tr.xml new file mode 100644 index 00000000..b17b9408 --- /dev/null +++ b/docs/xsl-generic/common/tr.xml @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Semboller +A +a +B +b +C +c +Ç +ç +D +d +E +e +F +f +G +g +Ğ +ğ +H +h +I +ı +İ +i +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +P +p +R +r +S +s +Ş +ş +T +t +U +u +Ü +ü +V +v +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/uk.xml b/docs/xsl-generic/common/uk.xml new file mode 100644 index 00000000..17678e55 --- /dev/null +++ b/docs/xsl-generic/common/uk.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/utility.xml b/docs/xsl-generic/common/utility.xml new file mode 100644 index 00000000..ead9419e --- /dev/null +++ b/docs/xsl-generic/common/utility.xml @@ -0,0 +1,259 @@ + + + + + Common » Utility Template Reference + + $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the + miscellaneous utility templates in the DocBook XSL + Stylesheets. + + + +These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + + +The utility templates don’t import or include any DocBook + XSL stylesheet parameters, so the utility templates can be used + without importing the whole set of parameters. + + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +log.message +Logs/emits formatted notes and warnings + + +<xsl:template name="log.message"> +<xsl:param name="level"/> +<xsl:param name="source"/> +<xsl:param name="context-desc"/> +<xsl:param name="context-desc-field-length">12</xsl:param> +<xsl:param name="context-desc-padded"> + <xsl:if test="not($context-desc = '')"> + <xsl:call-template name="pad-string"> + <xsl:with-param name="leftRight">right</xsl:with-param> + <xsl:with-param name="padVar" select="substring($context-desc, 1, $context-desc-field-length)"/> + <xsl:with-param name="length" select="$context-desc-field-length"/> + </xsl:call-template> + </xsl:if> + </xsl:param> +<xsl:param name="message"/> +<xsl:param name="message-field-length" select="45"/> +<xsl:param name="message-padded"> + <xsl:variable name="spaces-for-blank-level"> + <!-- * if the level field is blank, we'll need to pad out --> + <!-- * the message field with spaces to compensate --> + <xsl:choose> + <xsl:when test="$level = ''"> + <xsl:value-of select="4 + 2"/> + <!-- * 4 = hard-coded length of comment text ("Note" or "Warn") --> + <!-- * + 2 = length of colon-plus-space separator ": " --> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:variable name="spaces-for-blank-context-desc"> + <!-- * if the context-description field is blank, we'll need --> + <!-- * to pad out the message field with spaces to compensate --> + <xsl:choose> + <xsl:when test="$context-desc = ''"> + <xsl:value-of select="$context-desc-field-length + 2"/> + <!-- * + 2 = length of colon-plus-space separator ": " --> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:variable name="extra-spaces" select="$spaces-for-blank-level + $spaces-for-blank-context-desc"/> + <xsl:call-template name="pad-string"> + <xsl:with-param name="leftRight">right</xsl:with-param> + <xsl:with-param name="padVar" select="substring($message, 1, ($message-field-length + $extra-spaces))"/> + <xsl:with-param name="length" select="$message-field-length + $extra-spaces"/> + </xsl:call-template> + </xsl:param> + ... +</xsl:template> + + + +<para>The <function>log.message</function> template is a utility + template for logging/emitting formatted messages – that is, + notes and warnings, along with a given log “level” and an + identifier for the “source” that the message relates to.</para> + + </refsect1><refsect1><title>Parameters + + + level + + +Text to log/emit in the message-level field to + indicate the message level + (Note or + Warning) + + + + source + + +Text to log/emit in the source field to identify the + “source” to which the notification/warning relates. + This can be any arbitrary string, but because the + message lacks line and column numbers to identify the + exact part of the source document to which it + relates, the intention is that the value you pass + into the source parameter should + give the user some way to identify the portion of + their source document on which to take potentially + take action in response to the log message (for + example, to edit, change, or add content). + + +So the source value should be, + for example, an ID, book/chapter/article title, title + of some formal object, or even a string giving an + XPath expression. + + + + context-desc + + +Text to log/emit in the context-description field to + describe the context for the message. + + + + context-desc-field-length + + +Specifies length of the context-description field + (in characters); default is 12 + + +If the text specified by the + context-desc parameter is longer + than the number of characters specified in + context-desc-field-length, it is + truncated to context-desc-field-length + (12 characters by default). + + +If the specified text is shorter than + context-desc-field-length, + it is right-padded out to + context-desc-field-length (12 by + default). + + +If no value has been specified for the + context-desc parameter, the field is + left empty and the text of the log message begins with + the value of the message + parameter. + + + + message + + +Text to log/emit in the actual message field + + + + message-field-length + + +Specifies length of the message + field (in characters); default is 45 + + + + + + Returns + +Outputs a message (generally, to standard error). + + + + +get.doc.title +Gets a title from the current document + + +<xsl:template name="get.doc.title"/> + + + +<para>The <function>get.doc.title</function> template is a + utility template for returning the first title found in the + current document.</para> + + </refsect1><refsect1><title>Returns + +Returns a string containing some identifying title for the + current document . + + + + +pad-string +Right-pads or left-pads a string out to a certain length + + +<xsl:template name="pad-string"> +<xsl:param name="padChar" select="' '"/> +<xsl:param name="leftRight">left</xsl:param> +<xsl:param name="padVar"/> +<xsl:param name="length"/> + ... +</xsl:template> + + + +<para>This function takes string <parameter>padVar</parameter> and + pads it out in the direction <parameter>rightLeft</parameter> to + the string-length <parameter>length</parameter>, using string + <parameter>padChar</parameter> (a space character by default) as + the padding string (note that <parameter>padChar</parameter> can + be a string; it is not limited to just being a single + character).</para> + + <note> + +<para>This function began as a copy of Nate Austin's + <function>prepend-pad</function> function in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/padding.html">Padding + Content</link> section of Dave Pawson's <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT + FAQ</link>.</para> + + </note> + </refsect1><refsect1><title>Returns + +Returns a (padded) string. + + + diff --git a/docs/xsl-generic/common/utility.xsl b/docs/xsl-generic/common/utility.xsl new file mode 100644 index 00000000..37092b7c --- /dev/null +++ b/docs/xsl-generic/common/utility.xsl @@ -0,0 +1,290 @@ + + + + + + + Common » Utility Template Reference + + $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the + miscellaneous utility templates in the DocBook XSL + Stylesheets. + + These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + The utility templates don’t import or include any DocBook + XSL stylesheet parameters, so the utility templates can be used + without importing the whole set of parameters. + + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + + Logs/emits formatted notes and warnings + + + The log.message template is a utility + template for logging/emitting formatted messages – that is, + notes and warnings, along with a given log “level” and an + identifier for the “source” that the message relates to. + + + + + level + + Text to log/emit in the message-level field to + indicate the message level + (Note or + Warning) + + + source + + Text to log/emit in the source field to identify the + “source” to which the notification/warning relates. + This can be any arbitrary string, but because the + message lacks line and column numbers to identify the + exact part of the source document to which it + relates, the intention is that the value you pass + into the source parameter should + give the user some way to identify the portion of + their source document on which to take potentially + take action in response to the log message (for + example, to edit, change, or add content). + So the source value should be, + for example, an ID, book/chapter/article title, title + of some formal object, or even a string giving an + XPath expression. + + + context-desc + + Text to log/emit in the context-description field to + describe the context for the message. + + + context-desc-field-length + + Specifies length of the context-description field + (in characters); default is 12 + If the text specified by the + context-desc parameter is longer + than the number of characters specified in + context-desc-field-length, it is + truncated to context-desc-field-length + (12 characters by default). + If the specified text is shorter than + context-desc-field-length, + it is right-padded out to + context-desc-field-length (12 by + default). + If no value has been specified for the + context-desc parameter, the field is + left empty and the text of the log message begins with + the value of the message + parameter. + + + message + + Text to log/emit in the actual message field + + + message-field-length + + Specifies length of the message + field (in characters); default is 45 + + + + + + Outputs a message (generally, to standard error). + + + + + + 12 + + + + right + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + right + + + + + + + + + : + + + + : + + + + + + + + + + Gets a title from the current document + + The get.doc.title template is a + utility template for returning the first title found in the + current document. + + + Returns a string containing some identifying title for the + current document . + + + + + + + + + + + + + + + Right-pads or left-pads a string out to a certain length + + This function takes string padVar and + pads it out in the direction rightLeft to + the string-length length, using string + padChar (a space character by default) as + the padding string (note that padChar can + be a string; it is not limited to just being a single + character). + + This function began as a copy of Nate Austin's + prepend-pad function in the Padding + Content section of Dave Pawson's XSLT + FAQ. + + + + Returns a (padded) string. + + + + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/vi.xml b/docs/xsl-generic/common/vi.xml new file mode 100644 index 00000000..9363b8d2 --- /dev/null +++ b/docs/xsl-generic/common/vi.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/xh.xml b/docs/xsl-generic/common/xh.xml new file mode 100644 index 00000000..8d10e300 --- /dev/null +++ b/docs/xsl-generic/common/xh.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/zh_cn.xml b/docs/xsl-generic/common/zh_cn.xml new file mode 100644 index 00000000..6a782fca --- /dev/null +++ b/docs/xsl-generic/common/zh_cn.xml @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +其它 +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/zh_tw.xml b/docs/xsl-generic/common/zh_tw.xml new file mode 100644 index 00000000..db63cdca --- /dev/null +++ b/docs/xsl-generic/common/zh_tw.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/highlighting/c-hl.xml b/docs/xsl-generic/highlighting/c-hl.xml new file mode 100644 index 00000000..ad86bb3f --- /dev/null +++ b/docs/xsl-generic/highlighting/c-hl.xml @@ -0,0 +1,105 @@ + + + + + /* + */ + + + + // + + + + # + + + + " + \ + + + + ' + \ + + + + <<< + + + + and + auto + break + case + char + class + __CLASS__ + const + continue + declare + default + do + double + else + enum + exit + extern + __FILE__ + float + for + global + goto + if + include + int + __LINE__ + long + new + or + private + protected + public + register + return + short + signed + sizeof + static + struct + switch + typedef + union + unsigned + void + volatile + while + + + + + \ No newline at end of file diff --git a/docs/xsl-generic/highlighting/common.xsl b/docs/xsl-generic/highlighting/common.xsl new file mode 100644 index 00000000..32f1bbcf --- /dev/null +++ b/docs/xsl-generic/highlighting/common.xsl @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/highlighting/delphi-hl.xml b/docs/xsl-generic/highlighting/delphi-hl.xml new file mode 100644 index 00000000..3fab5c2e --- /dev/null +++ b/docs/xsl-generic/highlighting/delphi-hl.xml @@ -0,0 +1,174 @@ + + + + + { + } + + + + (* + *) + + + + // + + + + ' + + + + + + + and + else + inherited + packed + then + array + end + initialization + procedure + threadvar + as + except + inline + program + to + asm + exports + interface + property + try + begin + file + is + raise + type + case + final + label + record + unit + class + finalization + library + repeat + unsafe + const + finally + mod + resourcestring + until + constructor + for + nil + sealed + uses + destructor + function + not + set + var + dispinterface + goto + object + shl + while + div + if + of + shr + with + do + implementation + or + static + xor + downto + in + out + string + + + at + on + + + absolute + dynamic + local + platform + requires + abstract + export + message + private + resident + assembler + external + name + protected + safecall + automated + far + near + public + stdcall + cdecl + forward + nodefault + published + stored + contains + implements + overload + read + varargs + default + index + override + readonly + virtual + deprecated + inline + package + register + write + dispid + library + pascal + reintroduce + writeonly + + + + + + diff --git a/docs/xsl-generic/highlighting/ini-hl.xml b/docs/xsl-generic/highlighting/ini-hl.xml new file mode 100644 index 00000000..dbebfa7d --- /dev/null +++ b/docs/xsl-generic/highlighting/ini-hl.xml @@ -0,0 +1,43 @@ + + + + + (?m)(;.*)$ + + + + + (?m)^(\[.+\]\s*)$ + + + + + (?m)^(.+=) + + + + + diff --git a/docs/xsl-generic/highlighting/java-hl.xml b/docs/xsl-generic/highlighting/java-hl.xml new file mode 100644 index 00000000..419b7cb8 --- /dev/null +++ b/docs/xsl-generic/highlighting/java-hl.xml @@ -0,0 +1,98 @@ + + + + + /* + */ + + + + // + + + + " + \ + + + + ' + \ + + + + abstract + boolean + break + byte + case + catch + char + class + const + continue + default + do + double + else + extends + final + finally + float + for + goto + if + implements + import + instanceof + int + interface + long + native + new + package + private + protected + public + return + short + static + strictfp + super + switch + synchronized + this + throw + throws + transient + try + void + volatile + while + + + + diff --git a/docs/xsl-generic/highlighting/m2-hl.xml b/docs/xsl-generic/highlighting/m2-hl.xml new file mode 100644 index 00000000..2d2c7693 --- /dev/null +++ b/docs/xsl-generic/highlighting/m2-hl.xml @@ -0,0 +1,86 @@ + + + + + (* + *) + + + + " + + + + ' + + + + and + array + begin + by + case + const + definition + div + do + else + elsif + end + exit + export + for + from + if + implementation + import + in + loop + mod + module + not + of + or + pointer + procedure + qualified + record + repeat + return + set + then + to + type + until + var + while + with + + + + + + diff --git a/docs/xsl-generic/highlighting/myxml-hl.xml b/docs/xsl-generic/highlighting/myxml-hl.xml new file mode 100644 index 00000000..495d4e87 --- /dev/null +++ b/docs/xsl-generic/highlighting/myxml-hl.xml @@ -0,0 +1,131 @@ + + + + + + + A + ABBR + ACRONYM + ADDRESS + APPLET + AREA + B + BASE + BASEFONT + BDO + BIG + BLOCKQUOTE + BODY + BR + BUTTON + CAPTION + CENTER + CITE + CODE + COL + COLGROUP + DD + DEL + DFN + DIR + DIV + DL + DT + EM + FIELDSET + FONT + FORM + FRAME + FRAMESET + H1 + H2 + H3 + H4 + H5 + H6 + HEAD + HR + HTML + I + IFRAME + IMG + INPUT + INS + ISINDEX + KBD + LABEL + LEGEND + LI + LINK + MAP + MENU + META + NOFRAMES + NOSCRIPT + OBJECT + OL + OPTGROUP + OPTION + P + PARAM + PRE + Q + S + SAMP + SCRIPT + SELECT + SMALL + SPAN + STRIKE + STRONG + STYLE + SUB + SUP + TABLE + TBODY + TD + TEXTAREA + TFOOT + TH + THEAD + TITLE + TR + TT + U + UL + VAR + XMP + + + + + xsl: + + + + + diff --git a/docs/xsl-generic/highlighting/php-hl.xml b/docs/xsl-generic/highlighting/php-hl.xml new file mode 100644 index 00000000..7cd0d6a7 --- /dev/null +++ b/docs/xsl-generic/highlighting/php-hl.xml @@ -0,0 +1,127 @@ + + + + + /* + */ + + + + // + + + + # + + + + " + \ + + + + ' + \ + + + + <<< + + + + and + or + xor + __FILE__ + exception + __LINE__ + array + as + break + case + class + const + continue + declare + default + die + do + echo + else + elseif + empty + enddeclare + endfor + endforeach + endif + endswitch + endwhile + eval + exit + extends + for + foreach + function + global + if + include + include_once + isset + list + new + print + require + require_once + return + static + switch + unset + use + var + while + __FUNCTION__ + __CLASS__ + __METHOD__ + final + php_user_filter + interface + implements + extends + public + private + protected + abstract + clone + try + catch + throw + cfunction + old_function + + + + + diff --git a/docs/xsl-generic/highlighting/xslthl-config.xml b/docs/xsl-generic/highlighting/xslthl-config.xml new file mode 100644 index 00000000..7c77f6fc --- /dev/null +++ b/docs/xsl-generic/highlighting/xslthl-config.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/xsl-generic/html/admon.xsl b/docs/xsl-generic/html/admon.xsl new file mode 100644 index 00000000..6a3e9f8e --- /dev/null +++ b/docs/xsl-generic/html/admon.xsl @@ -0,0 +1,132 @@ + + + + + + + + 25 + + + + + + + + + + + + + + + + + + note + warning + caution + tip + important + note + + + + + + + + Note + Warning + Caution + Tip + Important + Note + + + + + + + + + +
+ + + + + + + + + + + + : + + + + + + + + + + +
+ + + + [{$alt}] + + + + + + + + + +
+ +
+
+
+ + +
+ + + + + + + + +

+ + +

+
+ + +
+
+ + + + + + + +
diff --git a/docs/xsl-generic/html/annotations.xsl b/docs/xsl-generic/html/annotations.xsl new file mode 100644 index 00000000..f0106320 --- /dev/null +++ b/docs/xsl-generic/html/annotations.xsl @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + + + namesp. cut + + + stripped namespace before processing + + + + + + + + Note + + + namesp. cut + + + processing stripped document + + + + + + + + Unable to strip the namespace from DB5 document, + cannot proceed. + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + diff --git a/docs/xsl-generic/html/ebnf.xsl b/docs/xsl-generic/html/ebnf.xsl new file mode 100644 index 00000000..94a66c55 --- /dev/null +++ b/docs/xsl-generic/html/ebnf.xsl @@ -0,0 +1,329 @@ + + + + + + + + +$Id: ebnf.xsl 6910 2007-06-28 23:23:30Z xmldoc $ + +Walsh +Norman +19992000 +Norman Walsh + + +HTML EBNF Reference + + +
Introduction + +This is technical reference documentation for the DocBook XSL +Stylesheets; it documents (some of) the parameters, templates, and +other elements of the stylesheets. + +This reference describes the templates and parameters relevant +to formatting EBNF markup. + +This is not intended to be user documentation. +It is provided for developers writing customization layers for the +stylesheets, and for anyone who's interested in how it +works. + +Although I am trying to be thorough, this documentation is known +to be incomplete. Don't forget to read the source, too :-) +
+
+
+ + + + + + + + + + + + 1 + + + + + + EBNF + + for + + + + + + + + + + + + +
+ + +
+ + + + + + + + + + EBNF productions + +
+
+
+ + + + + + + + + + [ + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + Error: no ID for productionrecap linkend: + + . + + + + + + Warning: multiple "IDs" for productionrecap linkend: + + . + + + + + + + + + + + + + + + + | +
+
+
+ + + + + + + + + + + + + + + production + + + + + + + + + Non-terminals with no content must point to + production elements in the current document. + + + Invalid xpointer for empty nt: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + /*  + +  */ +
+
+ + + + + + + + + constraintdef + + + + + + + + + + + + + + + + : + + + + + + + : + + + + + + + + + +  ] + +
+
+
+ + +
+ + + +
+
+ + +

+
+ + + +
diff --git a/docs/xsl-generic/html/footnote.xsl b/docs/xsl-generic/html/footnote.xsl new file mode 100644 index 00000000..ca996380 --- /dev/null +++ b/docs/xsl-generic/html/footnote.xsl @@ -0,0 +1,299 @@ + + + + + + + + + + + #ftn. + + + + + + + [ + + + + + ] + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + #ftn. + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ftn. + + + + + + # + + + + +

+ + + + + + + [ + + + + + ] + + +

+
+ + + + + + ftn. + + + + + + # + + + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ + +
+
+

The following annotations are from this essay. You are seeing + them here because your browser doesn’t support the user-interface + techniques used to make them appear as ‘popups’ on modern browsers.

+
+ + +
+
+
+ + + + + + + + +
+ + +
+
+ + +
+ + + +
+
+ + + + Warning: footnote number may not be generated + correctly; + + unexpected as first child of footnote. + +
+ + +
+
+
+
+ + + + + + + + +
diff --git a/docs/xsl-generic/html/formal.xsl b/docs/xsl-generic/html/formal.xsl new file mode 100644 index 00000000..b264fef9 --- /dev/null +++ b/docs/xsl-generic/html/formal.xsl @@ -0,0 +1,400 @@ + + + + + +1 + + + + + + + + + + +
+ + + + + + + +
+ +
+ + + + + +

+ + +

+

+ + + + + + + +
+
+ +
+
+
+ + + + + + + + + -float + + + + + + + + + +
+ + + + + + + + + +

+ + + +

+
+ + + + + +
+

+ + + + + + + + +

+

+
+ + + + + + + + + -float + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Broken table: tr descendent of CALS Table. + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + Broken table: row descendent of HTML table. + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + float: + + ; + + + +
+
+ +
diff --git a/docs/xsl-generic/html/glossary.xsl b/docs/xsl-generic/html/glossary.xsl new file mode 100644 index 00000000..92178067 --- /dev/null +++ b/docs/xsl-generic/html/glossary.xsl @@ -0,0 +1,482 @@ + + +%common.entities; +]> + + + + + + + + &setup-language-variable; + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ + + +
+ + + + +
+
+ + + + + + + + + + + &setup-language-variable; +
+ + + + + +
+ + + + + + + + + + +
+
+
+ + + + + &setup-language-variable; + + +
+ + + +
+ + + + + + + + + + +
+
+
+ + +

+ + +

+
+ + + + + + + + +
+ + + + 0 + 1 + + + + + + + + ( + + ) + + + + + +
+
+ +
+ + + + 0 + 1 + + + + + + + + ( + + ) + +
+
+ +
+ + + + 0 + 1 + + + + + +
+
+
+ + +
+ + + + , + + + + + , + + + + + , + + + + + + + + + + +
+

+ + + + + + + + + + + + + + + + + + + + + + + Warning: glosssee @otherterm reference not found: + + + + + + + + + + + + + + + . +

+
+
+ + +
+ + +

+ + + + + + + + + + + + + +

+
+
+
+ + + + + + + + + + + + + + + + + + + + + Warning: glossseealso @otherterm reference not found: + + + + + + + + + + + + . + + + , + + + + + + + + + + &setup-language-variable; + + + + + + + + Warning: processing automatic glossary + without a glossary.collection file. + + + + + + Warning: processing automatic glossary but unable to + open glossary.collection file ' + + ' + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + + +
+
+ + + + + + + + + + &setup-language-variable; + +
+ + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + +
diff --git a/docs/xsl-generic/html/graphics.xsl b/docs/xsl-generic/html/graphics.xsl new file mode 100644 index 00000000..3a03edb6 --- /dev/null +++ b/docs/xsl-generic/html/graphics.xsl @@ -0,0 +1,1489 @@ + + + + + + + + + + + + + + 1 + + + + + + 1 + + + + + +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + 1 + 0 + + + + + + 1.0 + 1.0 + + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + px + + + + + + + + + + + px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + px + + + + + + + + + + + px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: imagemaps not supported + on scaled images + + + + 0 + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + height: + + px + + + + + + + + + + + +
+ + + + + background-color: + + + + + + + + + + + + + + + + + + + + + +
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + calspair + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + , + + , + + + + + + + + + + + + Warning: only calspair or + otherunits='imagemap' supported + in imageobjectco + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + +
+ + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + + + + + + + + + +
+ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/docs/xsl-generic/html/highlight.xsl b/docs/xsl-generic/html/highlight.xsl new file mode 100644 index 00000000..30f2153e --- /dev/null +++ b/docs/xsl-generic/html/highlight.xsl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/html-rtf.xsl b/docs/xsl-generic/html/html-rtf.xsl new file mode 100644 index 00000000..5855f64b --- /dev/null +++ b/docs/xsl-generic/html/html-rtf.xsl @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/docs/xsl-generic/html/html.xsl b/docs/xsl-generic/html/html.xsl new file mode 100644 index 00000000..79ce1555 --- /dev/null +++ b/docs/xsl-generic/html/html.xsl @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + # + + + + + + + + + # + + + + + + + + + + + + + + + + + + + bullet + + + + + + + + + bullet + + + © + + + ® + (SM) +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ID recommended on + + + : + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/htmltbl.xsl b/docs/xsl-generic/html/htmltbl.xsl new file mode 100644 index 00000000..5c7e3073 --- /dev/null +++ b/docs/xsl-generic/html/htmltbl.xsl @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/index.xsl b/docs/xsl-generic/html/index.xsl new file mode 100644 index 00000000..e8dfb93e --- /dev/null +++ b/docs/xsl-generic/html/index.xsl @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ + + + + + + + + + +
+
+
+ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + +
+ + + + + + + + + +
+ +
+
+
+ + +

+ + +

+
+ + + + + + + + + +
+ + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ + +
+ +
+ + +
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ + +
+ +
+ +
+
+ +
+
+
+
+ + +
+ +
+
+ + diff --git a/docs/xsl-generic/html/info.xsl b/docs/xsl-generic/html/info.xsl new file mode 100644 index 00000000..61ade512 --- /dev/null +++ b/docs/xsl-generic/html/info.xsl @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/inline.xsl b/docs/xsl-generic/html/inline.xsl new file mode 100644 index 00000000..a3e10ded --- /dev/null +++ b/docs/xsl-generic/html/inline.xsl @@ -0,0 +1,1439 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + XLink to nonexistent id: + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + span + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + , + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abbr + + + + + + acronym + + + + + + + + + + + + + + + + + + + + + + + + + + http://example.com/cgi-bin/man.cgi? + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: glossary.collection specified, but there are + + automatic glossaries + + + + + + + + + + + + + + + + + + + + + + + + There's no entry for + + in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error: no glossentry for glossterm: + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + element + + + + + + + + + + + + + + + + </ + + > + + + & + + ; + + + &# + + ; + + + % + + ; + + + <? + + > + + + <? + + ?> + + + < + + > + + + < + + /> + + + <!-- + + --> + + + + + + + + + + + + + + + + + + + + + + + < + + + + + mailto: + + + + + + > + + + + + + + + + + + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + + + + + + + + + + + + + + + + + + + ] + + + [ + + ] + + + + + + + + + + + + +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/keywords.xsl b/docs/xsl-generic/html/keywords.xsl new file mode 100644 index 00000000..c12e39fc --- /dev/null +++ b/docs/xsl-generic/html/keywords.xsl @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + , + + + + + diff --git a/docs/xsl-generic/html/lists.xsl b/docs/xsl-generic/html/lists.xsl new file mode 100644 index 00000000..2ada156c --- /dev/null +++ b/docs/xsl-generic/html/lists.xsl @@ -0,0 +1,1103 @@ + + + + + + + + +
+ + + + + + + + + +
    + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + circle + disc + square + + + + + + +
  • + + + list-style-type: + + + + + + + + + + + +
    + +
    +
    + + + +
    +
  • +
    + + + + + + + + + + + + + 1 + a + i + A + I + + + + Unexpected numeration: + + + + + + + +
    + + + + + + + + + + +
      + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + +
  • + + + + + + + + + + + + + +
    + +
    +
    + + + +
    +
  • +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + +

    +
    +
    +
    + + +
    + + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + +
    + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    + + + +
    +
    + + + + + + + + + + + + + + 1 + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + 1 + + + +
    +
    + + + + + + + + + + + 1 + + + +
    +
    + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + 1 + + + + + + + + +   + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + + 1 + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + +
    + + + + + 0 + 1 + + + + + + + + + + + + +
      + +
    +
    + +
      + + + + +
    +
    +
    + + + + +
    +
    + + + + + + + + + + + + +
      + +
    +
    + + +
  • + + +
  • +
    + + + +
      + +
    +
    + + +

    + + + +

    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    +
    + + +
    + +
    +
    + + + + + + + + + +
    + + +
    +
    + + + + + + + + +
    + + + + : + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +

    + + + + +

    + + + + + +
    + +
    + + + + +
    +
    +
    +
    +
    + + + + + + + + + +

    + + + + + + + + +

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/maketoc.xsl b/docs/xsl-generic/html/maketoc.xsl new file mode 100644 index 00000000..1ba3931e --- /dev/null +++ b/docs/xsl-generic/html/maketoc.xsl @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filename=" + + " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/manifest.xsl b/docs/xsl-generic/html/manifest.xsl new file mode 100644 index 00000000..01faaccf --- /dev/null +++ b/docs/xsl-generic/html/manifest.xsl @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/docs/xsl-generic/html/math.xsl b/docs/xsl-generic/html/math.xsl new file mode 100644 index 00000000..5bb4377c --- /dev/null +++ b/docs/xsl-generic/html/math.xsl @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unsupported TeX math notation: + + + + + + + + + + + + + \nopagenumbers + + + + \bye + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $ + + + + $ + + \vfill\eject + + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $$ + + + + $$ + + \vfill\eject + + + + + + + + + \documentclass{article} + \pagestyle{empty} + \begin{document} + + + + \end{document} + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $ + + + + $ + + \newpage + + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $$ + + + + $$ + + \newpage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + diff --git a/docs/xsl-generic/html/oldchunker.xsl b/docs/xsl-generic/html/oldchunker.xsl new file mode 100644 index 00000000..fe6b17c3 --- /dev/null +++ b/docs/xsl-generic/html/oldchunker.xsl @@ -0,0 +1,202 @@ + + + + + + + + + + + + + +Encoding used in generated HTML pages + +This encoding is used in files generated by chunking stylesheet. Currently +only Saxon is able to change output encoding. + + + + + + + + + +Saxon character representation used in generated HTML pages + +This character representation is used in files generated by chunking stylesheet. If +you want to suppress entity references for characters with direct representation +in default.encoding, set this parameter to value native. + + + + + + + + + + + + + + + + + + + + + + + + Chunking isn't supported with + + + + + + + + + + + + + + + Writing + + + for + + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can't make chunks with + + 's processor. + + + + + + + + + + + + + + + + Writing + + + for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can't make chunks with + + 's processor. + + + + + + diff --git a/docs/xsl-generic/html/onechunk.xsl b/docs/xsl-generic/html/onechunk.xsl new file mode 100644 index 00000000..527dccfd --- /dev/null +++ b/docs/xsl-generic/html/onechunk.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + +1 + + + + # + + + + + + diff --git a/docs/xsl-generic/html/param.xsl b/docs/xsl-generic/html/param.xsl new file mode 100644 index 00000000..389068fc --- /dev/null +++ b/docs/xsl-generic/html/param.xsl @@ -0,0 +1,412 @@ + + + + + + +.png + +images/ + + margin-left: 0.5in; margin-right: 0.5in; + + + + +/* ====================================================================== + Annotations +*/ + +div.annotation-list { visibility: hidden; + } + +div.annotation-nocss { position: absolute; + visibility: hidden; + } + +div.annotation-popup { position: absolute; + z-index: 4; + visibility: hidden; + padding: 0px; + margin: 2px; + border-style: solid; + border-width: 1px; + width: 200px; + background-color: white; + } + +div.annotation-title { padding: 1px; + font-weight: bold; + border-bottom-style: solid; + border-bottom-width: 1px; + color: white; + background-color: black; + } + +div.annotation-body { padding: 2px; + } + +div.annotation-body p { margin-top: 0px; + padding-top: 0px; + } + +div.annotation-close { position: absolute; + top: 2px; + right: 2px; + } + + + +http://docbook.sourceforge.net/release/script/AnchorPosition.js http://docbook.sourceforge.net/release/script/PopupWindow.js + +http://docbook.sourceforge.net/release/images/annot-open.png + +http://docbook.sourceforge.net/release/images/annot-close.png + +A + +. + + +. +http://docbook.sourceforge.net/release/bibliography/bibliography.xml + + +normal + + +60 +.png + + +15 + +images/callouts/ + + +10 +10102 + + + + + + + + + + + +no + +1 + + + + + left + before + + + + +all +maybe +http://docbook.sourceforge.net/release/images/draft.png +#F5DCB3 + + +::= + + + + + +DocBook Online Help Sample +com.example.help +Example provider +1 + + + + + +1 + + + +figure before +example before +equation before +table before +procedure before +task before + + +kr +40 + + + + + + + + + +appendix toc,title +article/appendix nop +article toc,title +book toc,title,figure,table,example,equation +chapter toc,title +part toc,title +preface toc,title +qandadiv toc +qandaset toc +reference toc,title +sect1 toc +sect2 toc +sect3 toc +sect4 toc +sect5 toc +section toc +set toc,title + + + + +no + + + + + + + + + + + + +.html + +copyright + + + + +text/css +alias.h + + + + + + + +User1 + + +User2 + + + + + + + + + +htmlhelp.chm + + +iso-8859-1 + + + + + +toc.hhc +5 + + +index.hhk +htmlhelp.hhp + +Main + +context.h + + + + + + + + + + + +basic + + + + + + + + +no +iso-8859-1 + + +en + + + + +5 + + +3 + + + + + + HTML.manifest + + + + ++ +.gif + +images/ +1 + + +6in + + + + replace + +no + + + +no +fragid= +.olink +pubid + /cgi-bin/olink +sysid + +0 + +I + +90 +10 + + + + + + + + + + + + + + + +; + + + + + +. +number + + + + + + + + + I + +index + +. +.!?: + +8 + + + + + 0 + #E0E0E0 + + + + + + +0 + + + + + +solid +0.5pt +a + + + +solid +0.5pt + + olinkdb.xml +target.db + + +tex-math-equations.tex + + +dl +2 +8 +_top + + + + + + + + +, +0 + +: + + + + diff --git a/docs/xsl-generic/html/pi.xsl b/docs/xsl-generic/html/pi.xsl new file mode 100644 index 00000000..44e323b4 --- /dev/null +++ b/docs/xsl-generic/html/pi.xsl @@ -0,0 +1,1240 @@ + + + + + +HTML Processing Instruction Reference + + $Id: pi.xsl 7250 2007-08-18 10:19:00Z xmldoc $ + + + + Introduction + This is generated reference documentation for all + user-specifiable processing instructions (PIs) in the DocBook + XSL stylesheets for HTML output. + + You add these PIs at particular points in a document to + cause specific “exceptions” to formatting/output behavior. To + make global changes in formatting/output behavior across an + entire document, it’s better to do it by setting an + appropriate stylesheet parameter (if there is one). + + + + + + + + + Sets background color for an image + + Use the dbhtml background-color PI before or + after an image (graphic, inlinegraphic, + imagedata, or videodata element) as a + sibling to the element, to set a background color for the + image. + + + dbhtml background-color="color" + + + + background-color="color" + + An HTML color value + + + + + + Background color + + + + + + + + + + + + Sets background color on a table row or table cell + + Use the dbhtml bgcolor PI as child of a table row + or cell to set a background color for that table row or cell. + + + dbhtml bgcolor="color" + + + + bgcolor="color" + + An HTML color value + + + + + + Cell background color + + + + + + + + + + + + Specifies cellpadding in table or qandaset output + + Use the dbhtml cellpadding PI as a child of a + table or qandaset to specify the value + for the HTML cellpadding attribute in the + output HTML table. + + + dbhtml cellpadding="number" + + + + cellpadding="number" + + Specifies the cellpadding + + + + + + html.cellpadding + + + Cell spacing and cell padding, + Q and A formatting + + + + + + + + + + + + Specifies cellspacing in table or qandaset output + + Use the dbhtml cellspacing PI as a child of a + table or qandaset to specify the value + for the HTML cellspacing attribute in the + output HTML table. + + + dbhtml cellspacing="number" + + + + cellspacing="number" + + Specifies the cellspacing + + + + + + html.cellspacing + + + Cell spacing and cell padding, + Q and A formatting + + + + + + + + + + + + Set value of the class attribute for a table row + + Use the dbhtml class PI as a child of a + row to specify a class + attribute and value in the HTML output for that row. + + + dbhtml class="name" + + + + class="name" + + Specifies the class name + + + + + + Table styles in HTML output + + + + + + + + + + + + Specifies a directory name in which to write files + + When chunking output, use the dbhtml dir PI + as a child of a chunk source to cause the output of that + chunk to be written to the specified directory; also, use it + as a child of a mediaobject to specify a + directory into which any long-description files for that + mediaobject will be written. + + + dbhtml dir="path" + + + + dir="path" + + Specifies the pathname for the directory + + + + + + base.dir + + + dbhtml dir processing instruction + + + + + + + + + + + + Specifies a filename for a chunk + + When chunking output, use the dbhtml filename + PI as a child of a chunk source to specify a filename for + the output file for that chunk. + + + dbhtml filename="filename" + + + + filename="path" + + Specifies the filename for the file + + + + + + use.id.as.filename + + + dbhtml filenames + + + + + + + + + + + + Specifies presentation style for a funcsynopsis + + Use the dbhtml funcsynopsis-style PI as a child of + a funcprototype or anywhere within a funcprototype + control the presentation style for the funcsynopsis + in output. + + + dbhtml funcsynopsis-style="kr"|"ansi" + + + + funcsynopsis-style="kr" + + Displays the funcprototype in K&R style + + + funcsynopsis-style="ansi" + + Displays the funcprototype in ANSI style + + + + + + funcsynopsis.style + + + + + + + + + + + + Specifies a path to the location of an image file + + Use the dbhtml img.src.path PI before or + after an image (graphic, + inlinegraphic, imagedata, or + videodata element) as a sibling to the element, + to specify a path to the location of the image; in HTML + output, the value specified for the + img.src.path attribute is prepended to the + filename. + + + dbhtml img.src.path="path" + + + + img.src.path="path" + + Specifies the pathname to prepend to the name of the image file + + + + + + img.src.path + + + Using fileref + + + + + + + + + + + + Specifies the label width for a qandaset + + Use the dbhtml label-width PI as a child of a + qandaset to specify the width of labels. + + + dbhtml label-width="width" + + + + label-width="width" + + Specifies the label width (including units) + + + + + + Q and A formatting + + + + + + + + + + + + Specifies interval for lines numbers in verbatims + + Use the dbhtml linenumbering.everyNth PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the interval at which lines are numbered. + + + dbhtml linenumbering.everyNth="N" + + + + linenumbering.everyNth="N" + + Specifies numbering interval; a number is output + before every Nth line + + + + + + linenumbering.everyNth + + + Line numbering + + + + + + + + + + + + Specifies separator text for line numbers in verbatims + + Use the dbhtml linenumbering.separator PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the separator text output between the line numbers and content. + + + dbhtml linenumbering.separator="text" + + + + linenumbering.separator="text" + + Specifies the text (zero or more characters) + + + + + + linenumbering.separator + + + Line numbering + + + + + + + + + + + + Specifies width for line numbers in verbatims + + Use the dbhtml linenumbering.width PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the width set aside for line numbers. + + + dbhtml linenumbering.width="width" + + + + linenumbering.width="width" + + Specifies the width (inluding units) + + + + + + linenumbering.width + + + Line numbering + + + + + + + + + + + + Specifies presentation style for a variablelist or + segmentedlist + + Use the dbhtml list-presentation PI as a child of + a variablelist or segmentedlist to + control the presentation style for the list (to cause it, for + example, to be displayed as a table). + + + dbhtml list-presentation="list"|"table" + + + + list-presentation="list" + + Displays the list as a list + + + list-presentation="table" + + Displays the list as a table + + + + + + + + variablelist.as.table + + + segmentedlist.as.table + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the width of a variablelist or simplelist + + Use the dbhtml list-width PI as a child of a + variablelist or a simplelist presented + as a table, to specify the output width. + + + dbhtml list-width="width" + + + + list-width="width" + + Specifies the output width (including units) + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the height for a table row + + Use the dbhtml row-height PI as a child of a + row to specify the height of the row. + + + dbhtml row-height="height" + + + + row-height="height" + + Specifies the label height (including units) + + + + + + Row height + + + + + + + + + + + + (obsolete) Sets the starting number on an ordered list + + This PI is obsolete. The intent of + this PI was to provide a means for setting a specific starting + number for an ordered list. Instead of this PI, set a value + for the override attribute on the first + listitem in the list; that will have the same + effect as what this PI was intended for. + + + dbhtml start="character" + + + + start="character" + + Specifies the character to use as the starting + number; use 0-9, a-z, A-Z, or lowercase or uppercase + Roman numerals + + + + + + List starting number + + + + + + + + + + + + Specifies summary for table, variablelist, segmentedlist, or qandaset output + + Use the dbhtml table-summary PI as a child of + a table, variablelist, + segmentedlist, or qandaset to specify + the text for the HTML summary attribute + in the output HTML table. + + + dbhtml table-summary="text" + + + + table-summary="text" + + Specifies the summary text (zero or more characters) + + + + + + Variable list formatting in HTML, + Table summary text + + + + + + + + + + + + Specifies the width for a table + + Use the dbhtml table-width PI as a child of a + table to specify the width of the table in + output. + + + dbhtml table-width="width" + + + + table-width="width" + + Specifies the table width (including units or as a percentage) + + + + + + default.table.width + + + Table width + + + + + + + + + + + + Sets character formatting for terms in a variablelist + + Use the dbhtml term-presentation PI as a child + of a variablelist to set character formatting for + the term output of the list. + + + dbhtml term-presentation="bold"|"italic"|"bold-italic" + + + + term-presentation="bold" + + Specifies that terms are displayed in bold + + + term-presentation="italic" + + Specifies that terms are displayed in italic + + + term-presentation="bold-italic" + + Specifies that terms are displayed in bold-italic + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies separator text among terms in a varlistentry + + Use the dbhtml term-separator PI as a child + of a variablelist to specify the separator text + among term instances. + + + dbhtml term-separator="text" + + + + term-separator="text" + + Specifies the text (zero or more characters) + + + + + + variablelist.term.separator + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the term width for a variablelist + + Use the dbhtml term-width PI as a child of a + variablelist to specify the width for + term output. + + + dbhtml term-width="width" + + + + term-width="width" + + Specifies the term width (including units) + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies whether a TOC should be generated for a qandaset + + Use the dbhtml toc PI as a child of a + qandaset to specify whether a table of contents + (TOC) is generated for the qandaset. + + + dbhtml toc="0"|"1" + + + + toc="0" + + If zero, no TOC is generated + + + toc="1" + + If 1 (or any non-zero value), + a TOC is generated + + + + + + Q and A list of questions, + Q and A formatting + + + + + + + + + + + + Generates a hyperlinked list of commands + + Use the dbcmdlist PI as the child of any + element (for example, refsynopsisdiv) containing multiple + cmdsynopsis instances; a hyperlinked navigational + “command list” will be generated at the top of output for that + element, enabling users to quickly jump + to each command synopsis. + + + dbcmdlist + + + [No parameters] + + + + + + No cmdsynopsis elements matched dbcmdlist PI, perhaps it's nested too deep? + + +
    + + + +
    +
    + + + Generates a hyperlinked list of functions + + Use the dbfunclist PI as the child of any + element (for example, refsynopsisdiv) containing multiple + funcsynopsis instances; a hyperlinked + navigational “function list” will be generated at the top of + output for that element, enabling users to quickly + jump to to each function synopsis. + + + dbfunclist + + + [No parameters] + + + + + + No funcsynopsis elements matched dbfunclist PI, perhaps it's nested too deep? + + +
    + + + +
    +
    + + + Copies an external well-formed HTML/XML file into current doc + + Use the dbhtml-include href PI anywhere in a + document to cause the contents of the file referenced by the + href pseudo-attribute to be copied/inserted “as + is” into your HTML output at the point in document order + where the PI occurs in the source. + + The referenced file may contain plain text (as long as + it is “wrapped” in an html element — see the + note below) or markup in any arbitrary vocabulary, + including HTML — but it must conform to XML + well-formedness constraints (because the feature in XSLT + 1.0 for opening external files, the + document() function, can only handle + files that meet XML well-formedness constraints). + Among other things, XML well-formedness constraints + require a document to have a single root + element. So if the content you want to + include is plain text or is markup that does + not have a single root element, + wrap the content in an + html element. The stylesheets will + strip out that surrounding html “wrapper” when + they find it, leaving just the content you want to + insert. + + + + dbhtml-include href="URI" + + + + href="URI" + + Specifies the URI for the file to include; the URI + can be, for example, a remote http: + URI, or a local filesystem file: + URI + + + + + + textinsert.extension + + + Inserting external HTML code, + External code files + + + + + + + href + + + + + + + + + + + + + + + + + + + + ERROR: dbhtml-include processing instruction + href has no content. + + + + + + + ERROR: dbhtml-include processing instruction has + missing or empty href value. + + + + + + + + + + + + filename + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + # + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    + + + + + + + + + + + + + + + +
    + + + # + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + / + + + + + + + Sets topic name and topic id for context-sensitive HTML Help + + Use the dbhh PI as a child of components + that should be used as targets for context-sensitive help requests. + + + dbhh topicname="name" topicid="id" + + + + topicname="name" + + Specifies a unique string constant that identifies a help topic + + + topicid="id" + + Specifies a unique integer value for the topicname string + + + + + + Context-sensitive help + + + + +
    diff --git a/docs/xsl-generic/html/profile-chunk-code.xsl b/docs/xsl-generic/html/profile-chunk-code.xsl new file mode 100644 index 00000000..364bcc95 --- /dev/null +++ b/docs/xsl-generic/html/profile-chunk-code.xsl @@ -0,0 +1,609 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bk + + + + + + + + + + + + + + + ar + + + + + + + + + + + + + + + pr + + + + + + + + + + + + + + + ch + + + + + + + + + + + + + + + ap + + + + + + + + + + + + + + + + + + + pt + + + + + + + + + + + + + + + + + + + rn + + + + + + + + + + + + + + + + + + re + + + + + + + + + + + + + + + + + + + co + + + + + + + + + + + s + + + + + + + + + + + + + + + + + + + bi + + + + + + + + + + + + + + + + + + + go + + + + + + + + + + + + + + + + + + + ix + + + + + + + + si + + + + + + + + chunk-filename-error- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document + + + + + + + + + + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/profile-chunk.xsl b/docs/xsl-generic/html/profile-chunk.xsl new file mode 100644 index 00000000..02920b12 --- /dev/null +++ b/docs/xsl-generic/html/profile-chunk.xsl @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/profile-docbook.xsl b/docs/xsl-generic/html/profile-docbook.xsl new file mode 100644 index 00000000..e680eede --- /dev/null +++ b/docs/xsl-generic/html/profile-docbook.xsl @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Element + + in namespace ' + + ' encountered + + in + + + , but no template matches. + + + + < + + > + + </ + + > + + + + + + + + + white + black + #0000FF + #840084 + #0000FF + + + + + + + + + + <xsl:copy-of select="$title"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document + + + + + + + + + + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + diff --git a/docs/xsl-generic/html/profile-onechunk.xsl b/docs/xsl-generic/html/profile-onechunk.xsl new file mode 100644 index 00000000..325b8b12 --- /dev/null +++ b/docs/xsl-generic/html/profile-onechunk.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + +1 + + + + # + + + + + + diff --git a/docs/xsl-generic/html/qandaset.xsl b/docs/xsl-generic/html/qandaset.xsl new file mode 100644 index 00000000..0bc45773 --- /dev/null +++ b/docs/xsl-generic/html/qandaset.xsl @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + + + + + + + +
    + + + + + + + + + + +
    + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + +
    + + + +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1% + + + + + + +
    +
    + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/refentry.xsl b/docs/xsl-generic/html/refentry.xsl new file mode 100644 index 00000000..bce630b9 --- /dev/null +++ b/docs/xsl-generic/html/refentry.xsl @@ -0,0 +1,309 @@ + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + +
    + + + + + + +
    +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + +
    + + + + + + + + + + + +

    + + + +

    +
    + +

    + + + + + + + + +

    +
    +
    + +

    + +

    +
    +
    + + + + + + , + + + + + + + + + em-dash + + + + + + + + + + + + +

    + + + + : + + + +

    +
    +
    + + +
    + + + + + +

    + + + + + + + + + + +

    + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    + + + + + + 0 + 1 + + + + 6 + + + + + + + + + + + + +

    + +

    +
    + + + +

    + +

    +
    + + + +

    + +

    +
    + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/sections.xsl b/docs/xsl-generic/html/sections.xsl new file mode 100644 index 00000000..efa45298 --- /dev/null +++ b/docs/xsl-generic/html/sections.xsl @@ -0,0 +1,622 @@ + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + 1 + 2 + 3 + 4 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + clear: both + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + 1 + + + + + + + 2 + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/docs/xsl-generic/html/synop.xsl b/docs/xsl-generic/html/synop.xsl new file mode 100644 index 00000000..1d52c520 --- /dev/null +++ b/docs/xsl-generic/html/synop.xsl @@ -0,0 +1,1596 @@ + + +]> + + + + + + + + + + + +
    + +

    + + + + + + + + + + + + +

    +
    +
    + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + ( + + ) + +   + + + + + + + + + + + + +

    + + + + + ( + + ) + + + +

    +
    + + + + + + + + + + + + + + + + + +
    +    
    +    
    +  
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + +
    + +
    +

    +
    + + + + + + ( + + + + + + + + + + + + + + + + ) + ; + + + + ... + ) + ; + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + +
    + + + + ; +
    + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + + + + + + + +
    + +
     
    + + + + padding-bottom: 1em + + +
    +
    +
    + + + + + + ( + + + + + + + + + + + + + + + + + ) + ; + +   + + + + + ... + ) + ; + +   + + + + + + + + , + + + ) + ; + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + ; + + + + + + +

    + +

    +
    + + + + + + ( + + + + + + + + + + + + + + + + void) + ; + + + + ... + ) + ; + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + padding-bottom: 1em + + + + + + + + + + + +
    + +
     
    +
    + + + + + + ( + + + + + + + + + + + + + + + + + void) + ; + +   + + + + + ... + ) + ; + +   + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + , + + + ) + ; + + + + + + + +   + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + +java + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unrecognized language on + + : + + + + + + + + + + + +
    +
    +
    + + + + + +
    +    
    +    
    +    
    +       extends
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + + , + + + + + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + 0 + + , +
    + + +   + + + +
    + + + + +
    + + + + + + + + + + + + + + +    + + + + + + + + + + + + + + + ( + + + + ) + +
    +     throws  + +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    
    +    
    +      : 
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + +    + + + + + + + + + + ( + + ) + +
    +     throws  + +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    interface 
    +    
    +    
    +      : 
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + +    + + + + + + + + + + ( + + ) + +
    +     raises( + + ) +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    package 
    +    
    +    ;
    +    
    + + + @ISA = ( + + ); +
    +
    + + +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + sub + + + { ... }; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/table.xsl b/docs/xsl-generic/html/table.xsl new file mode 100644 index 00000000..9e85d6d0 --- /dev/null +++ b/docs/xsl-generic/html/table.xsl @@ -0,0 +1,1120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + border- + + : + + + + + + ; + + + + + border- + + -width: + + ; + + + + border- + + -style: + + ; + + + + border- + + -color: + + ; + + + + + + + + + + + Error: CALS tables must specify the number of columns. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100% + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + border: none; + + + + + border-collapse: collapse; + + + + + + + 0 + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + 100% + + + + + + + + + + + + + + + + + + + + + + + + No convertLength function available. + + + + + + + + + + + + + + + + + + + + + + + + + + No adjustColumnWidths function available. + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: overlapped row contains content! + + + This row intentionally left blank + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + th + th + td + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + : + + + + + + + + 0: + + + + + + + + + + + + + + + 0 + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/docs/xsl-generic/html/task.xsl b/docs/xsl-generic/html/task.xsl new file mode 100644 index 00000000..d4d22f60 --- /dev/null +++ b/docs/xsl-generic/html/task.xsl @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + before + + + + + + + + +
    + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/titlepage.templates.xml b/docs/xsl-generic/html/titlepage.templates.xml new file mode 100644 index 00000000..664dc14e --- /dev/null +++ b/docs/xsl-generic/html/titlepage.templates.xml @@ -0,0 +1,662 @@ + + + + + + + + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="set" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="book" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="part" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="division.title" + param:node="ancestor-or-self::part[1]"/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="partintro" t:wrapper="div"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="reference" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="refentry" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> +<!-- uncomment this if you want refentry titlepages + <title t:force="1" + t:named-template="refentry.title" + param:node="ancestor-or-self::refentry[1]"/> +--> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator/> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + + <t:titlepage t:element="dedication" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::dedication[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="preface" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="chapter" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="appendix" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="section" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect1" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect2" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect3" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect4" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect5" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="simplesect" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="bibliography" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::bibliography[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="glossary" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::glossary[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="index" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::index[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="setindex" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::setindex[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +</t:templates> diff --git a/docs/xsl-generic/html/titlepage.templates.xsl b/docs/xsl-generic/html/titlepage.templates.xsl new file mode 100644 index 00000000..5fef8eca --- /dev/null +++ b/docs/xsl-generic/html/titlepage.templates.xsl @@ -0,0 +1,3622 @@ +<?xml version="1.0"?> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="exsl"> + +<!-- This stylesheet was created by template/titlepage.xsl--> + +<xsl:template name="article.titlepage.recto"> + <xsl:choose> + <xsl:when test="articleinfo/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/title"/> + </xsl:when> + <xsl:when test="artheader/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="articleinfo/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/subtitle"/> + </xsl:when> + <xsl:when test="artheader/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/abstract"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/abstract"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="article.titlepage.verso"> +</xsl:template> + +<xsl:template name="article.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="article.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="article.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="article.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="article.titlepage.before.recto"/> + <xsl:call-template name="article.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="article.titlepage.before.verso"/> + <xsl:call-template name="article.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="article.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="article.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="article.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="set.titlepage.recto"> + <xsl:choose> + <xsl:when test="setinfo/title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="setinfo/subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/corpauthor"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/authorgroup"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/author"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/othercredit"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/releaseinfo"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/copyright"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/legalnotice"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/pubdate"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/revision"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/revhistory"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/abstract"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="set.titlepage.verso"> +</xsl:template> + +<xsl:template name="set.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="set.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="set.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="set.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="set.titlepage.before.recto"/> + <xsl:call-template name="set.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="set.titlepage.before.verso"/> + <xsl:call-template name="set.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="set.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="set.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="set.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="book.titlepage.recto"> + <xsl:choose> + <xsl:when test="bookinfo/title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="bookinfo/subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/corpauthor"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/authorgroup"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/author"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/othercredit"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/releaseinfo"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/copyright"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/legalnotice"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/pubdate"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/revision"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/revhistory"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/abstract"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="book.titlepage.verso"> +</xsl:template> + +<xsl:template name="book.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="book.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="book.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="book.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="book.titlepage.before.recto"/> + <xsl:call-template name="book.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="book.titlepage.before.verso"/> + <xsl:call-template name="book.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="book.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="book.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="book.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="part.titlepage.recto"> + <div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:call-template name="division.title"> +<xsl:with-param name="node" select="ancestor-or-self::part[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="partinfo/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/abstract"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="part.titlepage.verso"> +</xsl:template> + +<xsl:template name="part.titlepage.separator"> +</xsl:template> + +<xsl:template name="part.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="part.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="part.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="part.titlepage.before.recto"/> + <xsl:call-template name="part.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="part.titlepage.before.verso"/> + <xsl:call-template name="part.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="part.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="part.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="part.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="partintro.titlepage.recto"> + <xsl:choose> + <xsl:when test="partintroinfo/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="partintroinfo/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/abstract"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="partintro.titlepage.verso"> +</xsl:template> + +<xsl:template name="partintro.titlepage.separator"> +</xsl:template> + +<xsl:template name="partintro.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="partintro.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="partintro.titlepage"> + <div> + <xsl:variable name="recto.content"> + <xsl:call-template name="partintro.titlepage.before.recto"/> + <xsl:call-template name="partintro.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="partintro.titlepage.before.verso"/> + <xsl:call-template name="partintro.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="partintro.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="partintro.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="partintro.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="reference.titlepage.recto"> + <xsl:choose> + <xsl:when test="referenceinfo/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="referenceinfo/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/abstract"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="reference.titlepage.verso"> +</xsl:template> + +<xsl:template name="reference.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="reference.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="reference.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="reference.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="reference.titlepage.before.recto"/> + <xsl:call-template name="reference.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="reference.titlepage.before.verso"/> + <xsl:call-template name="reference.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="reference.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="reference.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="reference.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="refentry.titlepage.recto"> +</xsl:template> + +<xsl:template name="refentry.titlepage.verso"> +</xsl:template> + +<xsl:template name="refentry.titlepage.separator"> +</xsl:template> + +<xsl:template name="refentry.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="refentry.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="refentry.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="refentry.titlepage.before.recto"/> + <xsl:call-template name="refentry.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="refentry.titlepage.before.verso"/> + <xsl:call-template name="refentry.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="refentry.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="refentry.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="refentry.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template name="dedication.titlepage.recto"> + <div xsl:use-attribute-sets="dedication.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::dedication[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="dedicationinfo/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="dedicationinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="dedication.titlepage.verso"> +</xsl:template> + +<xsl:template name="dedication.titlepage.separator"> +</xsl:template> + +<xsl:template name="dedication.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="dedication.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="dedication.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="dedication.titlepage.before.recto"/> + <xsl:call-template name="dedication.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="dedication.titlepage.before.verso"/> + <xsl:call-template name="dedication.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="dedication.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="dedication.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="dedication.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="dedication.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="dedication.titlepage.recto.style"> +<xsl:apply-templates select="." mode="dedication.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="preface.titlepage.recto"> + <xsl:choose> + <xsl:when test="prefaceinfo/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="prefaceinfo/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/abstract"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="preface.titlepage.verso"> +</xsl:template> + +<xsl:template name="preface.titlepage.separator"> +</xsl:template> + +<xsl:template name="preface.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="preface.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="preface.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="preface.titlepage.before.recto"/> + <xsl:call-template name="preface.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="preface.titlepage.before.verso"/> + <xsl:call-template name="preface.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="preface.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="preface.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="preface.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="chapter.titlepage.recto"> + <xsl:choose> + <xsl:when test="chapterinfo/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="chapterinfo/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/abstract"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="chapter.titlepage.verso"> +</xsl:template> + +<xsl:template name="chapter.titlepage.separator"> +</xsl:template> + +<xsl:template name="chapter.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="chapter.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="chapter.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="chapter.titlepage.before.recto"/> + <xsl:call-template name="chapter.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="chapter.titlepage.before.verso"/> + <xsl:call-template name="chapter.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="chapter.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="chapter.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="chapter.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="appendix.titlepage.recto"> + <xsl:choose> + <xsl:when test="appendixinfo/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="appendixinfo/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/abstract"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="appendix.titlepage.verso"> +</xsl:template> + +<xsl:template name="appendix.titlepage.separator"> +</xsl:template> + +<xsl:template name="appendix.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="appendix.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="appendix.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="appendix.titlepage.before.recto"/> + <xsl:call-template name="appendix.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="appendix.titlepage.before.verso"/> + <xsl:call-template name="appendix.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="appendix.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="appendix.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="appendix.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="section.titlepage.recto"> + <xsl:choose> + <xsl:when test="sectioninfo/title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sectioninfo/subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/corpauthor"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/authorgroup"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/author"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/othercredit"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/releaseinfo"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/copyright"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/legalnotice"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/pubdate"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/revision"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/revhistory"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/abstract"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="section.titlepage.verso"> +</xsl:template> + +<xsl:template name="section.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="section.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="section.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="section.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="section.titlepage.before.recto"/> + <xsl:call-template name="section.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="section.titlepage.before.verso"/> + <xsl:call-template name="section.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="section.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="section.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="section.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect1.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect1info/title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect1info/subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/corpauthor"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/authorgroup"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/author"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/othercredit"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/releaseinfo"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/copyright"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/legalnotice"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/pubdate"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/revision"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/revhistory"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/abstract"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect1.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect1.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect1.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect1.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect1.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect1.titlepage.before.recto"/> + <xsl:call-template name="sect1.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect1.titlepage.before.verso"/> + <xsl:call-template name="sect1.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect1.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect1.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect1.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect2.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect2info/title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect2info/subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/corpauthor"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/authorgroup"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/author"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/othercredit"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/releaseinfo"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/copyright"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/legalnotice"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/pubdate"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/revision"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/revhistory"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/abstract"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect2.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect2.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect2.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect2.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect2.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect2.titlepage.before.recto"/> + <xsl:call-template name="sect2.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect2.titlepage.before.verso"/> + <xsl:call-template name="sect2.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect2.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect2.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect2.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect3.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect3info/title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect3info/subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/corpauthor"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/authorgroup"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/author"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/othercredit"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/releaseinfo"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/copyright"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/legalnotice"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/pubdate"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/revision"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/revhistory"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/abstract"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect3.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect3.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect3.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect3.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect3.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect3.titlepage.before.recto"/> + <xsl:call-template name="sect3.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect3.titlepage.before.verso"/> + <xsl:call-template name="sect3.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect3.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect3.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect3.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect4.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect4info/title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect4info/subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/corpauthor"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/authorgroup"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/author"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/othercredit"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/releaseinfo"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/copyright"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/legalnotice"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/pubdate"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/revision"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/revhistory"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/abstract"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect4.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect4.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect4.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect4.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect4.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect4.titlepage.before.recto"/> + <xsl:call-template name="sect4.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect4.titlepage.before.verso"/> + <xsl:call-template name="sect4.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect4.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect4.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect4.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect5.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect5info/title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect5info/subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/corpauthor"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/authorgroup"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/author"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/othercredit"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/releaseinfo"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/copyright"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/legalnotice"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/pubdate"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/revision"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/revhistory"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/abstract"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect5.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect5.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect5.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect5.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect5.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect5.titlepage.before.recto"/> + <xsl:call-template name="sect5.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect5.titlepage.before.verso"/> + <xsl:call-template name="sect5.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect5.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect5.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect5.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="simplesect.titlepage.recto"> + <xsl:choose> + <xsl:when test="simplesectinfo/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="simplesectinfo/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/abstract"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="simplesect.titlepage.verso"> +</xsl:template> + +<xsl:template name="simplesect.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="simplesect.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="simplesect.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="simplesect.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="simplesect.titlepage.before.recto"/> + <xsl:call-template name="simplesect.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="simplesect.titlepage.before.verso"/> + <xsl:call-template name="simplesect.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="simplesect.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="simplesect.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="simplesect.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="bibliography.titlepage.recto"> + <div xsl:use-attribute-sets="bibliography.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::bibliography[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="bibliographyinfo/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="bibliographyinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="bibliography.titlepage.verso"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.separator"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="bibliography.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="bibliography.titlepage.before.recto"/> + <xsl:call-template name="bibliography.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="bibliography.titlepage.before.verso"/> + <xsl:call-template name="bibliography.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="bibliography.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="bibliography.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="bibliography.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="bibliography.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="bibliography.titlepage.recto.style"> +<xsl:apply-templates select="." mode="bibliography.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="glossary.titlepage.recto"> + <div xsl:use-attribute-sets="glossary.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::glossary[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="glossaryinfo/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="glossaryinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="glossary.titlepage.verso"> +</xsl:template> + +<xsl:template name="glossary.titlepage.separator"> +</xsl:template> + +<xsl:template name="glossary.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="glossary.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="glossary.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="glossary.titlepage.before.recto"/> + <xsl:call-template name="glossary.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="glossary.titlepage.before.verso"/> + <xsl:call-template name="glossary.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="glossary.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="glossary.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="glossary.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="glossary.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="glossary.titlepage.recto.style"> +<xsl:apply-templates select="." mode="glossary.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="index.titlepage.recto"> + <div xsl:use-attribute-sets="index.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::index[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="indexinfo/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="indexinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="index.titlepage.verso"> +</xsl:template> + +<xsl:template name="index.titlepage.separator"> +</xsl:template> + +<xsl:template name="index.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="index.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="index.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="index.titlepage.before.recto"/> + <xsl:call-template name="index.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="index.titlepage.before.verso"/> + <xsl:call-template name="index.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="index.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="index.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="index.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="index.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="index.titlepage.recto.style"> +<xsl:apply-templates select="." mode="index.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="setindex.titlepage.recto"> + <div xsl:use-attribute-sets="setindex.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::setindex[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="setindexinfo/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="setindexinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="setindex.titlepage.verso"> +</xsl:template> + +<xsl:template name="setindex.titlepage.separator"> +</xsl:template> + +<xsl:template name="setindex.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="setindex.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="setindex.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="setindex.titlepage.before.recto"/> + <xsl:call-template name="setindex.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="setindex.titlepage.before.verso"/> + <xsl:call-template name="setindex.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="setindex.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="setindex.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="setindex.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="setindex.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="setindex.titlepage.recto.style"> +<xsl:apply-templates select="." mode="setindex.titlepage.recto.mode"/> +</div> +</xsl:template> + +</xsl:stylesheet> + diff --git a/docs/xsl-generic/html/titlepage.xsl b/docs/xsl-generic/html/titlepage.xsl new file mode 100644 index 00000000..e2445516 --- /dev/null +++ b/docs/xsl-generic/html/titlepage.xsl @@ -0,0 +1,1031 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: titlepage.xsl 7253 2007-08-18 16:49:39Z mzjn $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:attribute-set name="book.titlepage.recto.style"/> +<xsl:attribute-set name="book.titlepage.verso.style"/> + +<xsl:attribute-set name="article.titlepage.recto.style"/> +<xsl:attribute-set name="article.titlepage.verso.style"/> + +<xsl:attribute-set name="set.titlepage.recto.style"/> +<xsl:attribute-set name="set.titlepage.verso.style"/> + +<xsl:attribute-set name="part.titlepage.recto.style"/> +<xsl:attribute-set name="part.titlepage.verso.style"/> + +<xsl:attribute-set name="partintro.titlepage.recto.style"/> +<xsl:attribute-set name="partintro.titlepage.verso.style"/> + +<xsl:attribute-set name="reference.titlepage.recto.style"/> +<xsl:attribute-set name="reference.titlepage.verso.style"/> + +<xsl:attribute-set name="refentry.titlepage.recto.style"/> +<xsl:attribute-set name="refentry.titlepage.verso.style"/> + +<xsl:attribute-set name="dedication.titlepage.recto.style"/> +<xsl:attribute-set name="dedication.titlepage.verso.style"/> + +<xsl:attribute-set name="preface.titlepage.recto.style"/> +<xsl:attribute-set name="preface.titlepage.verso.style"/> + +<xsl:attribute-set name="chapter.titlepage.recto.style"/> +<xsl:attribute-set name="chapter.titlepage.verso.style"/> + +<xsl:attribute-set name="appendix.titlepage.recto.style"/> +<xsl:attribute-set name="appendix.titlepage.verso.style"/> + +<xsl:attribute-set name="bibliography.titlepage.recto.style"/> +<xsl:attribute-set name="bibliography.titlepage.verso.style"/> + +<xsl:attribute-set name="glossary.titlepage.recto.style"/> +<xsl:attribute-set name="glossary.titlepage.verso.style"/> + +<xsl:attribute-set name="index.titlepage.recto.style"/> +<xsl:attribute-set name="index.titlepage.verso.style"/> + +<xsl:attribute-set name="setindex.titlepage.recto.style"/> +<xsl:attribute-set name="setindex.titlepage.verso.style"/> + +<xsl:attribute-set name="section.titlepage.recto.style"/> +<xsl:attribute-set name="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect1.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect1.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect2.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect2.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect3.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect3.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect4.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect4.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect5.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect5.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="simplesect.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="simplesect.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="table.of.contents.titlepage.recto.style"/> +<xsl:attribute-set name="table.of.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.tables.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.tables.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.figures.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.figures.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.equations.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.equations.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.examples.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.examples.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.unknowns.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.unknowns.contents.titlepage.verso.style"/> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="titlepage.mode"> + <!-- if an element isn't found in this mode, try the default mode --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="abbrev" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="abstract" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="anchor"/> + <xsl:if test="$abstract.notitle.enabled = 0"> + <xsl:call-template name="formal.object.heading"> + <xsl:with-param name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + <xsl:apply-templates mode="titlepage.mode"/> + <xsl:call-template name="process.footnotes"/> + </div> +</xsl:template> + +<xsl:template match="abstract/title" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="address" mode="titlepage.mode"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="paragraph"> + <xsl:with-param name="content"> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </div> + </xsl:when> + + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="paragraph"> + <xsl:with-param name="content"> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="affiliation" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="artpagenums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="author|editor" mode="titlepage.mode"> + <xsl:call-template name="credits.div"/> +</xsl:template> + +<xsl:template name="credits.div"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="self::editor[position()=1] and not($editedby.enabled = 0)"> + <h4 class="editedby"><xsl:call-template name="gentext.edited.by"/></h4> + </xsl:if> + <h3> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:choose> + <xsl:when test="orgname"> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="person.name"/> + </xsl:otherwise> + </xsl:choose> + </h3> + <xsl:if test="not($contrib.inline.enabled = 0)"> + <xsl:apply-templates mode="titlepage.mode" select="contrib"/> + </xsl:if> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + <xsl:apply-templates mode="titlepage.mode" select="email"/> + <xsl:if test="not($blurb.on.titlepage.enabled = 0)"> + <xsl:choose> + <xsl:when test="$contrib.inline.enabled = 0"> + <xsl:apply-templates mode="titlepage.mode" + select="contrib|authorblurb|personblurb"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode" + select="authorblurb|personblurb"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </div> +</xsl:template> + +<xsl:template match="authorblurb|personblurb" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="authorgroup" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="anchor"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="authorinitials" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="bibliomisc" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="bibliomset" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="collab" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="collabname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </span> +</xsl:template> + +<xsl:template match="confgroup" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="confdates" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="confsponsor" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="conftitle" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="confnum" mode="titlepage.mode"> + <!-- suppress --> +</xsl:template> + +<xsl:template match="contractnum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="contractsponsor" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="contrib" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="not($contrib.inline.enabled = 0)"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </span><xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p><xsl:apply-templates mode="titlepage.mode"/></p> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="copyright" mode="titlepage.mode"> + <p> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Copyright'"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="dingbat"> + <xsl:with-param name="dingbat">copyright</xsl:with-param> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="copyright.years"> + <xsl:with-param name="years" select="year"/> + <xsl:with-param name="print.ranges" select="$make.year.ranges"/> + <xsl:with-param name="single.year.ranges" + select="$make.single.year.ranges"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:apply-templates select="holder" mode="titlepage.mode"/> + </p> +</xsl:template> + +<xsl:template match="year" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="holder" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="position() < last()"> + <xsl:text>, </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="corpauthor" mode="titlepage.mode"> + <h3> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </h3> +</xsl:template> + +<xsl:template match="corpcredit" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="corpname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="date" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="edition" mode="titlepage.mode"> + <p> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Edition'"/> + </xsl:call-template> + </p> +</xsl:template> + +<xsl:template match="email" mode="titlepage.mode"> + <!-- use the normal e-mail handling code --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="firstname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="graphic" mode="titlepage.mode"> + <!-- use the normal graphic handling code --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="honorific" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="isbn" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="issn" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="biblioid" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="itermset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="invpartnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="issuenum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="jobtitle" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="keywordset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="legalnotice" mode="titlepage.mode"> + <xsl:variable name="id"><xsl:call-template name="object.id"/></xsl:variable> + + <xsl:choose> + <xsl:when test="$generate.legalnotice.link != 0"> + + <!-- Compute name of legalnotice file --> + <xsl:variable name="file"> + <xsl:call-template name="ln.or.rh.filename"/> + </xsl:variable> + + <xsl:variable name="filename"> + <xsl:call-template name="make-relative-filename"> + <xsl:with-param name="base.dir" select="$base.dir"/> + <xsl:with-param name="base.name" select="$file"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:variable> + + <a href="{$file}"> + <xsl:copy-of select="$title"/> + </a> + + <xsl:call-template name="write.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="quiet" select="$chunk.quietly"/> + <xsl:with-param name="content"> + <xsl:call-template name="user.preroot"/> + <html> + <head> + <xsl:call-template name="system.head.content"/> + <xsl:call-template name="head.content"/> + <xsl:call-template name="user.head.content"/> + </head> + <body> + <xsl:call-template name="body.attributes"/> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> + </body> + </html> + <xsl:value-of select="$chunk.append"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <a name="{$id}"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="legalnotice/title" mode="titlepage.mode"> + <p class="legalnotice-title"><b><xsl:apply-templates/></b></p> +</xsl:template> + +<xsl:template match="lineage" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="modespec" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="orgdiv" mode="titlepage.mode"> + <xsl:if test="preceding-sibling::*[1][self::orgname]"> + <xsl:text> </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="orgname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="othercredit" mode="titlepage.mode"> +<xsl:choose> + <xsl:when test="not($othercredit.like.author.enabled = 0)"> + <xsl:variable name="contrib" select="string(contrib)"/> + <xsl:choose> + <xsl:when test="contrib"> + <xsl:if test="not(preceding-sibling::othercredit[string(contrib)=$contrib])"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode" select="contrib"/> + <xsl:text>: </xsl:text> + <xsl:call-template name="person.name"/> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + <xsl:apply-templates select="following-sibling::othercredit[string(contrib)=$contrib]" mode="titlepage.othercredits"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:call-template name="person.name"/> + </xsl:with-param> + </xsl:call-template> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="credits.div"/> + </xsl:otherwise> +</xsl:choose> +</xsl:template> + +<xsl:template match="othercredit" mode="titlepage.othercredits"> + <xsl:text>, </xsl:text> + <xsl:call-template name="person.name"/> +</xsl:template> + +<xsl:template match="othername" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pagenums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="printhistory" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="productname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="productnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pubdate" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="publisher" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="publishername" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pubsnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="releaseinfo" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="revhistory" mode="titlepage.mode"> + <xsl:variable name="numcols"> + <xsl:choose> + <xsl:when test=".//authorinitials|.//author">3</xsl:when> + <xsl:otherwise>2</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="id"><xsl:call-template name="object.id"/></xsl:variable> + + <xsl:variable name="title"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key">RevHistory</xsl:with-param> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="contents"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <table border="1" width="100%" summary="Revision history"> + <tr> + <th align="left" valign="top" colspan="{$numcols}"> + <b> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RevHistory'"/> + </xsl:call-template> + </b> + </th> + </tr> + <xsl:apply-templates mode="titlepage.mode"> + <xsl:with-param name="numcols" select="$numcols"/> + </xsl:apply-templates> + </table> + </div> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$generate.revhistory.link != 0"> + + <!-- Compute name of revhistory file --> + <xsl:variable name="file"> + <xsl:call-template name="ln.or.rh.filename"> + <xsl:with-param name="is.ln" select="false()"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="filename"> + <xsl:call-template name="make-relative-filename"> + <xsl:with-param name="base.dir" select="$base.dir"/> + <xsl:with-param name="base.name" select="$file"/> + </xsl:call-template> + </xsl:variable> + + <a href="{$file}"> + <xsl:copy-of select="$title"/> + </a> + + <xsl:call-template name="write.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="quiet" select="$chunk.quietly"/> + <xsl:with-param name="content"> + <xsl:call-template name="user.preroot"/> + <html> + <head> + <xsl:call-template name="system.head.content"/> + <xsl:call-template name="head.content"> + <xsl:with-param name="title"> + <xsl:value-of select="$title"/> + <xsl:if test="../../title"> + <xsl:value-of select="concat(' (', ../../title, ')')"/> + </xsl:if> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="user.head.content"/> + </head> + <body> + <xsl:call-template name="body.attributes"/> + <xsl:copy-of select="$contents"/> + </body> + </html> + <xsl:text> </xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="revhistory/revision" mode="titlepage.mode"> + <xsl:param name="numcols" select="'3'"/> + <xsl:variable name="revnumber" select="revnumber"/> + <xsl:variable name="revdate" select="date"/> + <xsl:variable name="revauthor" select="authorinitials|author"/> + <xsl:variable name="revremark" select="revremark|revdescription"/> + <tr> + <td align="left"> + <xsl:if test="$revnumber"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Revision'"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:apply-templates select="$revnumber[1]" mode="titlepage.mode"/> + </xsl:if> + </td> + <td align="left"> + <xsl:apply-templates select="$revdate[1]" mode="titlepage.mode"/> + </td> + <xsl:choose> + <xsl:when test="$revauthor"> + <td align="left"> + <xsl:for-each select="$revauthor"> + <xsl:apply-templates select="." mode="titlepage.mode"/> + <xsl:if test="position() != last()"> + <xsl:text>, </xsl:text> + </xsl:if> + </xsl:for-each> + </td> + </xsl:when> + <xsl:when test="$numcols > 2"> + <td> </td> + </xsl:when> + <xsl:otherwise></xsl:otherwise> + </xsl:choose> + </tr> + <xsl:if test="$revremark"> + <tr> + <td align="left" colspan="{$numcols}"> + <xsl:apply-templates select="$revremark[1]" mode="titlepage.mode"/> + </td> + </tr> + </xsl:if> +</xsl:template> + +<xsl:template match="revision/revnumber" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/date" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/authorinitials" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/author" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/revremark" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/revdescription" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="seriesvolnums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="shortaffil" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="subjectset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="subtitle" mode="titlepage.mode"> + <h2> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </h2> +</xsl:template> + +<xsl:template match="surname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="title" mode="titlepage.mode"> + <xsl:variable name="id"> + <xsl:choose> + <!-- if title is in an *info wrapper, get the grandparent --> + <xsl:when test="contains(local-name(..), 'info')"> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="../.."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select=".."/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <h1> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="$generate.id.attributes = 0"> + <a name="{$id}"/> + </xsl:if> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> + </h1> +</xsl:template> + +<xsl:template match="titleabbrev" mode="titlepage.mode"> + <!-- nop; title abbreviations don't belong on the title page! --> +</xsl:template> + +<xsl:template match="volumenum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<!-- This template computes the filename for legalnotice and revhistory chunks --> +<xsl:template name="ln.or.rh.filename"> + <xsl:param name="node" select="."/> + <xsl:param name="is.ln" select="true()"/> + + <xsl:variable name="dbhtml-filename"> + <xsl:call-template name="pi.dbhtml_filename"> + <xsl:with-param name="node" select="$node"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <!-- 1. If there is a dbhtml_filename PI, use that --> + <xsl:when test="$dbhtml-filename != ''"> + <xsl:value-of select="$dbhtml-filename"/> + </xsl:when> + <xsl:when test="($node/@id or $node/@xml:id) and not($use.id.as.filename = 0)"> + <!-- * 2. If this legalnotice/revhistory has an ID, then go ahead and use --> + <!-- * just the value of that ID as the basename for the file --> + <!-- * (that is, without prepending an "ln-" or "rh-" to it) --> + <xsl:value-of select="($node/@id|$node/@xml:id)[1]"/> + <xsl:value-of select="$html.ext"/> + </xsl:when> + <xsl:when test="not ($node/@id or $node/@xml:id) or $use.id.as.filename = 0"> + <!-- * 3. Otherwise, if this legalnotice/revhistory does not have an ID, or --> + <!-- * if $use.id.as.filename = 0 --> + <!-- * then we generate an ID... --> + <xsl:variable name="id"> + <xsl:value-of select="generate-id($node)"/> + </xsl:variable> + <!-- * ...and then we take that generated ID, prepend a --> + <!-- * prefix to it, and use that as the basename for the file --> + <xsl:choose> + <xsl:when test="$is.ln"> + <xsl:value-of select="concat('ln-',$id,$html.ext)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="concat('rh-',$id,$html.ext)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/toc.xsl b/docs/xsl-generic/html/toc.xsl new file mode 100644 index 00000000..bf1e62c7 --- /dev/null +++ b/docs/xsl-generic/html/toc.xsl @@ -0,0 +1,173 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: toc.xsl 6910 2007-06-28 23:23:30Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="toc"> + <xsl:choose> + <xsl:when test="*"> + <xsl:if test="$process.source.toc != 0"> + <!-- if the toc isn't empty, process it --> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates/> + </xsl:element> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:if test="$process.empty.source.toc != 0"> + <xsl:choose> + <xsl:when test="parent::section + or parent::sect1 + or parent::sect2 + or parent::sect3 + or parent::sect4 + or parent::sect5"> + <xsl:apply-templates select="parent::*" + mode="toc.for.section"/> + </xsl:when> + <xsl:when test="parent::article"> + <xsl:apply-templates select="parent::*" + mode="toc.for.component"/> + </xsl:when> + <xsl:when test="parent::book + or parent::part"> + <xsl:apply-templates select="parent::*" + mode="toc.for.division"/> + </xsl:when> + <xsl:when test="parent::set"> + <xsl:apply-templates select="parent::*" + mode="toc.for.set"/> + </xsl:when> + <!-- there aren't any other contexts that allow toc --> + <xsl:otherwise> + <xsl:message> + <xsl:text>I don't know how to make a TOC in this context!</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocpart|tocchap + |toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> + <xsl:variable name="sub-toc"> + <xsl:if test="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dd> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> + </xsl:element> + </dd> + </xsl:when> + <xsl:otherwise> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:variable> + + <xsl:apply-templates select="tocentry[position() != last()]"/> + + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dt> + <xsl:apply-templates select="tocentry[position() = last()]"/> + </dt> + <xsl:copy-of select="$sub-toc"/> + </xsl:when> + <xsl:otherwise> + <li> + <xsl:apply-templates select="tocentry[position() = last()]"/> + <xsl:copy-of select="$sub-toc"/> + </li> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocentry|tocfront|tocback"> + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dt> + <xsl:call-template name="tocentry-content"/> + </dt> + </xsl:when> + <xsl:otherwise> + <li> + <xsl:call-template name="tocentry-content"/> + </li> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocentry[position() = last()]" priority="2"> + <xsl:call-template name="tocentry-content"/> +</xsl:template> + +<xsl:template name="tocentry-content"> + <xsl:variable name="targets" select="key('id',@linkend)"/> + <xsl:variable name="target" select="$targets[1]"/> + + <xsl:choose> + <xsl:when test="@linkend"> + <xsl:call-template name="check.id.unique"> + <xsl:with-param name="linkend" select="@linkend"/> + </xsl:call-template> + <a> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:attribute> + <xsl:apply-templates/> + </a> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="toc.for.section"> + <xsl:call-template name="section.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.component"> + <xsl:call-template name="component.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.section"> + <xsl:call-template name="section.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.division"> + <xsl:call-template name="division.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.set"> + <xsl:call-template name="set.toc"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="lot|lotentry"> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/verbatim.xsl b/docs/xsl-generic/html/verbatim.xsl new file mode 100644 index 00000000..13ad10ef --- /dev/null +++ b/docs/xsl-generic/html/verbatim.xsl @@ -0,0 +1,376 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:sverb="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.Verbatim" + xmlns:xverb="xalan://com.nwalsh.xalan.Verbatim" + xmlns:lxslt="http://xml.apache.org/xslt" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="sverb xverb lxslt exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: verbatim.xsl 6946 2007-07-04 10:21:57Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:include href="../highlighting/common.xsl"/> +<xsl:include href="highlight.xsl"/> + +<lxslt:component prefix="xverb" + functions="numberLines"/> + +<xsl:template match="programlisting|screen|synopsis"> + <xsl:param name="suppress-numbers" select="'0'"/> + <xsl:variable name="id"> + <xsl:call-template name="object.id"/> + </xsl:variable> + + <xsl:call-template name="anchor"/> + + <xsl:if test="$shade.verbatim != 0"> + <xsl:message> + <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> + <xsl:text>Use CSS instead,</xsl:text> + </xsl:message> + <xsl:message> + <xsl:text>for example: pre.</xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text> { background-color: #E0E0E0; }</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <xsl:variable name="rtf"> + <xsl:call-template name="apply-highlighting"/> + </xsl:variable> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </pre> + </xsl:when> + <xsl:otherwise> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="apply-highlighting"/> + </pre> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="literallayout"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates/> + </xsl:variable> + + <xsl:if test="$shade.verbatim != 0 and @class='monospaced'"> + <xsl:message> + <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> + <xsl:text>Use CSS instead,</xsl:text> + </xsl:message> + <xsl:message> + <xsl:text>for example: pre.</xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text> { background-color: #E0E0E0; }</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <xsl:choose> + <xsl:when test="@class='monospaced'"> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </pre> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="@class='monospaced'"> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$rtf"/> + </pre> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="address"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:when> + + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="number.rtf.lines"> + <xsl:param name="rtf" select="''"/> + <xsl:param name="pi.context" select="."/> + + <!-- Save the global values --> + <xsl:variable name="global.linenumbering.everyNth" + select="$linenumbering.everyNth"/> + + <xsl:variable name="global.linenumbering.separator" + select="$linenumbering.separator"/> + + <xsl:variable name="global.linenumbering.width" + select="$linenumbering.width"/> + + <!-- Extract the <?dbhtml linenumbering.*?> PI values --> + <xsl:variable name="pi.linenumbering.everyNth"> + <xsl:call-template name="pi.dbhtml_linenumbering.everyNth"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="pi.linenumbering.separator"> + <xsl:call-template name="pi.dbhtml_linenumbering.separator"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="pi.linenumbering.width"> + <xsl:call-template name="pi.dbhtml_linenumbering.width"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <!-- Construct the 'in-context' values --> + <xsl:variable name="linenumbering.everyNth"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.everyNth != ''"> + <xsl:value-of select="$pi.linenumbering.everyNth"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.everyNth"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.separator"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.separator != ''"> + <xsl:value-of select="$pi.linenumbering.separator"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.separator"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.width"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.width != ''"> + <xsl:value-of select="$pi.linenumbering.width"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.width"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.startinglinenumber"> + <xsl:choose> + <xsl:when test="$pi.context/@startinglinenumber"> + <xsl:value-of select="$pi.context/@startinglinenumber"/> + </xsl:when> + <xsl:when test="$pi.context/@continuation='continues'"> + <xsl:variable name="lastLine"> + <xsl:choose> + <xsl:when test="$pi.context/self::programlisting"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::programlisting[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::screen"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::screen[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::literallayout"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::literallayout[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::address"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::address[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::synopsis"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::synopsis[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unexpected verbatim environment: </xsl:text> + <xsl:value-of select="local-name($pi.context)"/> + </xsl:message> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:value-of select="$lastLine + 1"/> + </xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('sverb:numberLines')"> + <xsl:copy-of select="sverb:numberLines($rtf)"/> + </xsl:when> + <xsl:when test="function-available('xverb:numberLines')"> + <xsl:copy-of select="xverb:numberLines($rtf)"/> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>No numberLines function available.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="make-verbatim"> + <xsl:param name="rtf"/> + + <!-- I want to make this RTF verbatim. There are two possibilities: either + I have access to the exsl:node-set extension function and I can "do it right" + or I have to rely on CSS. --> + + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:apply-templates select="exsl:node-set($rtf)" mode="make.verbatim.mode"/> + </xsl:when> + <xsl:otherwise> + <span style="white-space: pre;"> + <xsl:copy-of select="$rtf"/> + </span> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ======================================================================== --> + +<xsl:template name="lastLineNumber"> + <xsl:param name="listings"/> + <xsl:param name="number" select="0"/> + + <xsl:variable name="lines"> + <xsl:call-template name="countLines"> + <xsl:with-param name="listing" select="string($listings[1])"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <xsl:when test="not($listings)"> + <xsl:value-of select="$number"/> + </xsl:when> + <xsl:when test="$listings[1]/@startinglinenumber"> + <xsl:value-of select="$number + $listings[1]/@startinglinenumber + $lines - 1"/> + </xsl:when> + <xsl:when test="$listings[1]/@continuation='continues'"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" select="listings[position() > 1]"/> + <xsl:with-param name="number" select="$number + $lines"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$lines"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="countLines"> + <xsl:param name="listing"/> + <xsl:param name="count" select="1"/> + + <xsl:choose> + <xsl:when test="contains($listing, ' ')"> + <xsl:call-template name="countLines"> + <xsl:with-param name="listing" select="substring-after($listing, ' ')"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/xref.xsl b/docs/xsl-generic/html/xref.xsl new file mode 100644 index 00000000..7c8dd68d --- /dev/null +++ b/docs/xsl-generic/html/xref.xsl @@ -0,0 +1,1348 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:suwl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.UnwrapLinks" + xmlns:exsl="http://exslt.org/common" + xmlns:xlink='http://www.w3.org/1999/xlink' + exclude-result-prefixes="suwl exsl xlink" + version='1.0'> + +<!-- ******************************************************************** + $Id: xref.xsl 7107 2007-07-22 10:22:06Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- Use internal variable for olink xlink role for consistency --> +<xsl:variable + name="xolink.role">http://docbook.org/xlink/role/olink</xsl:variable> + +<!-- ==================================================================== --> + +<xsl:template match="anchor"> + <xsl:call-template name="anchor"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="xref" name="xref"> + <xsl:param name="xhref" select="@xlink:href"/> + <!-- is the @xlink:href a local idref link? --> + <xsl:param name="xlink.idref"> + <xsl:if test="starts-with($xhref,'#') + and (not(contains($xhref,'(')) + or starts-with($xhref, '#xpointer(id('))"> + <xsl:call-template name="xpointer.idref"> + <xsl:with-param name="xpointer" select="$xhref"/> + </xsl:call-template> + </xsl:if> + </xsl:param> + <xsl:param name="xlink.targets" select="key('id',$xlink.idref)"/> + <xsl:param name="linkend.targets" select="key('id',@linkend)"/> + <xsl:param name="target" select="($xlink.targets | $linkend.targets)[1]"/> + + <xsl:variable name="xrefstyle"> + <xsl:choose> + <xsl:when test="@role and not(@xrefstyle) + and $use.role.as.xrefstyle != 0"> + <xsl:value-of select="@role"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@xrefstyle"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="anchor"/> + + <xsl:variable name="content"> + <xsl:choose> + + <xsl:when test="@endterm"> + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$target/@xreflabel"> + <xsl:call-template name="xref.xreflabel"> + <xsl:with-param name="target" select="$target"/> + </xsl:call-template> + </xsl:when> + + <xsl:when test="$target"> + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-prefix"/> + </xsl:if> + + <xsl:apply-templates select="$target" mode="xref-to"> + <xsl:with-param name="referrer" select="."/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + </xsl:apply-templates> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-suffix"/> + </xsl:if> + </xsl:when> + + <xsl:otherwise> + <xsl:message> + <xsl:text>ERROR: xref linking to </xsl:text> + <xsl:value-of select="@linkend|@xlink:href"/> + <xsl:text> has no generated link text.</xsl:text> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="simple.xlink"> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + +</xsl:template> + +<!-- ==================================================================== --> + +<!-- biblioref handled largely like an xref --> +<!-- To be done: add support for begin, end, and units attributes --> +<xsl:template match="biblioref"> + <xsl:variable name="targets" select="key('id',@linkend)"/> + <xsl:variable name="target" select="$targets[1]"/> + <xsl:variable name="refelem" select="local-name($target)"/> + + <xsl:call-template name="check.id.unique"> + <xsl:with-param name="linkend" select="@linkend"/> + </xsl:call-template> + + <xsl:call-template name="anchor"/> + + <xsl:choose> + <xsl:when test="count($target) = 0"> + <xsl:message> + <xsl:text>XRef to nonexistent id: </xsl:text> + <xsl:value-of select="@linkend"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + + <xsl:when test="@endterm"> + <xsl:variable name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>???</xsl:text> + </a> + </xsl:when> + <xsl:otherwise> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </a> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$target/@xreflabel"> + <a> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:attribute> + <xsl:call-template name="xref.xreflabel"> + <xsl:with-param name="target" select="$target"/> + </xsl:call-template> + </a> + </xsl:when> + + <xsl:otherwise> + <xsl:variable name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-prefix"/> + </xsl:if> + + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="$target/title or $target/*/title"> + <xsl:attribute name="title"> + <xsl:apply-templates select="$target" mode="xref-title"/> + </xsl:attribute> + </xsl:if> + <xsl:apply-templates select="$target" mode="xref-to"> + <xsl:with-param name="referrer" select="."/> + <xsl:with-param name="xrefstyle"> + <xsl:choose> + <xsl:when test="@role and not(@xrefstyle) and $use.role.as.xrefstyle != 0"> + <xsl:value-of select="@role"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@xrefstyle"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:apply-templates> + </a> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-suffix"/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="endterm"> + <!-- Process the children of the endterm element --> + <xsl:variable name="endterm"> + <xsl:apply-templates select="child::node()"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:apply-templates select="exsl:node-set($endterm)" mode="remove-ids"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$endterm"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="remove-ids"> + <xsl:choose> + <!-- handle html or xhtml --> + <xsl:when test="local-name(.) = 'a' + and (namespace-uri(.) = '' + or namespace-uri(.) = 'http://www.w3.org/1999/xhtml')"> + <xsl:choose> + <xsl:when test="(@name and count(@*) = 1) + or (@id and count(@*) = 1) + or (@xml:id and count(@*) = 1) + or (@xml:id and @name and count(@*) = 2) + or (@id and @name and count(@*) = 2)"> + <xsl:message>suppress anchor</xsl:message> + <!-- suppress the whole thing --> + </xsl:when> + <xsl:otherwise> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:choose> + <xsl:when test="local-name(.) != 'name' and local-name(.) != 'id'"> + <xsl:copy/> + </xsl:when> + <xsl:otherwise> + <xsl:message>removing <xsl:value-of + select="local-name(.)"/></xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:copy> + <xsl:apply-templates mode="remove-ids"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:choose> + <xsl:when test="local-name(.) != 'id'"> + <xsl:copy/> + </xsl:when> + <xsl:otherwise> + <xsl:message>removing <xsl:value-of + select="local-name(.)"/></xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:apply-templates mode="remove-ids"/> + </xsl:copy> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="xref-to-prefix"/> +<xsl:template match="*" mode="xref-to-suffix"/> + +<xsl:template match="*" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:if test="$verbose"> + <xsl:message> + <xsl:text>Don't know what gentext to create for xref to: "</xsl:text> + <xsl:value-of select="name(.)"/> + <xsl:text>", ("</xsl:text> + <xsl:value-of select="(@id|@xml:id)[1]"/> + <xsl:text>")</xsl:text> + </xsl:message> + </xsl:if> + <xsl:text>???</xsl:text> +</xsl:template> + +<xsl:template match="title" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- if you xref to a title, xref to the parent... --> + <xsl:choose> + <!-- FIXME: how reliable is this? --> + <xsl:when test="contains(local-name(parent::*), 'info')"> + <xsl:apply-templates select="parent::*[2]" mode="xref-to"> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="parent::*" mode="xref-to"> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="abstract|authorblurb|personblurb|bibliodiv|bibliomset + |biblioset|blockquote|calloutlist|caution|colophon + |constraintdef|formalpara|glossdiv|important|indexdiv + |itemizedlist|legalnotice|lot|msg|msgexplan|msgmain + |msgrel|msgset|msgsub|note|orderedlist|partintro + |productionset|qandadiv|refsynopsisdiv|segmentedlist + |set|setindex|sidebar|tip|toc|variablelist|warning" + mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- catch-all for things with (possibly optional) titles --> + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="author|editor|othercredit|personname" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="person.name"/> +</xsl:template> + +<xsl:template match="authorgroup" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="person.name.list"/> +</xsl:template> + +<xsl:template match="figure|example|table|equation" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="procedure" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="task" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="cmdsynopsis" mode="xref-to"> + <xsl:apply-templates select="(.//command)[1]" mode="xref"/> +</xsl:template> + +<xsl:template match="funcsynopsis" mode="xref-to"> + <xsl:apply-templates select="(.//function)[1]" mode="xref"/> +</xsl:template> + +<xsl:template match="dedication|preface|chapter|appendix|article" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="bibliography" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to-prefix"> + <xsl:text>[</xsl:text> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to-suffix"> + <xsl:text>]</xsl:text> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- handles both biblioentry and bibliomixed --> + <xsl:choose> + <xsl:when test="string(.) = ''"> + <xsl:variable name="bib" select="document($bibliography.collection,.)"/> + <xsl:variable name="id" select="(@id|@xml:id)[1]"/> + <xsl:variable name="entry" select="$bib/bibliography/ + *[@id=$id or @xml:id=$id][1]"/> + <xsl:choose> + <xsl:when test="$entry"> + <xsl:choose> + <xsl:when test="$bibliography.numbered != 0"> + <xsl:number from="bibliography" count="biblioentry|bibliomixed" + level="any" format="1"/> + </xsl:when> + <xsl:when test="local-name($entry/*[1]) = 'abbrev'"> + <xsl:apply-templates select="$entry/*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>No bibliography entry: </xsl:text> + <xsl:value-of select="$id"/> + <xsl:text> found in </xsl:text> + <xsl:value-of select="$bibliography.collection"/> + </xsl:message> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="$bibliography.numbered != 0"> + <xsl:number from="bibliography" count="biblioentry|bibliomixed" + level="any" format="1"/> + </xsl:when> + <xsl:when test="local-name(*[1]) = 'abbrev'"> + <xsl:apply-templates select="*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="glossary" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="glossentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + <xsl:choose> + <xsl:when test="$glossentry.show.acronym = 'primary'"> + <xsl:choose> + <xsl:when test="acronym|abbrev"> + <xsl:apply-templates select="(acronym|abbrev)[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="glossterm[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="glossterm[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="glossterm" mode="xref-to"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="index" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="listitem" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="section|simplesect + |sect1|sect2|sect3|sect4|sect5 + |refsect1|refsect2|refsect3|refsection" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + <!-- FIXME: What about "in Chapter X"? --> +</xsl:template> + +<xsl:template match="bridgehead" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + <!-- FIXME: What about "in Chapter X"? --> +</xsl:template> + +<xsl:template match="qandaset" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="qandadiv" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="qandaentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="question[1]" mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="question|answer" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="part|reference" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="refentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:choose> + <xsl:when test="refmeta/refentrytitle"> + <xsl:apply-templates select="refmeta/refentrytitle"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="refnamediv/refname[1]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates select="refmeta/manvolnum"/> +</xsl:template> + +<xsl:template match="refnamediv" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="refname[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="refname" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates mode="xref-to"/> +</xsl:template> + +<xsl:template match="step" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="varlistentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="term[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="varlistentry/term" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <!-- to avoid the comma that will be generated if there are several terms --> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="co" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:apply-templates select="." mode="callout-bug"/> +</xsl:template> + +<xsl:template match="area|areaset" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="callout-bug"> + <xsl:with-param name="conum"> + <xsl:apply-templates select="." mode="conumber"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="book" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="para" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:variable name="context" select="(ancestor::simplesect + |ancestor::section + |ancestor::sect1 + |ancestor::sect2 + |ancestor::sect3 + |ancestor::sect4 + |ancestor::sect5 + |ancestor::refsection + |ancestor::refsect1 + |ancestor::refsect2 + |ancestor::refsect3 + |ancestor::chapter + |ancestor::appendix + |ancestor::preface + |ancestor::partintro + |ancestor::dedication + |ancestor::colophon + |ancestor::bibliography + |ancestor::index + |ancestor::glossary + |ancestor::glossentry + |ancestor::listitem + |ancestor::varlistentry)[last()]"/> + + <xsl:apply-templates select="$context" mode="xref-to"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="object.title.markup"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="author" mode="xref-title"> + <xsl:variable name="title"> + <xsl:call-template name="person.name"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="authorgroup" mode="xref-title"> + <xsl:variable name="title"> + <xsl:call-template name="person.name.list"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="cmdsynopsis" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="(.//command)[1]" mode="xref"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="funcsynopsis" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="(.//function)[1]" mode="xref"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-title"> + <!-- handles both biblioentry and bibliomixed --> + <xsl:variable name="title"> + <xsl:text>[</xsl:text> + <xsl:choose> + <xsl:when test="local-name(*[1]) = 'abbrev'"> + <xsl:apply-templates select="*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>]</xsl:text> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="step" mode="xref-title"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="step[not(./title)]" mode="title.markup"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="co" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="callout-bug"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="link" name="link"> + <xsl:param name="linkend" select="@linkend"/> + <xsl:param name="a.target"/> + <xsl:param name="xhref" select="@xlink:href"/> + + <xsl:variable name="content"> + <xsl:call-template name="anchor"/> + <xsl:choose> + <xsl:when test="count(child::node()) > 0"> + <!-- If it has content, use it --> + <xsl:apply-templates/> + </xsl:when> + <!-- else look for an endterm --> + <xsl:when test="@endterm"> + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <!-- Use the xlink:href if no other text --> + <xsl:when test="@xlink:href"> + <xsl:value-of select="@xlink:href"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Link element has no content and no Endterm. </xsl:text> + <xsl:text>Nothing to show in the link to </xsl:text> + <xsl:value-of select="(@xlink:href|@linkend)[1]"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="simple.xlink"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="linkend" select="$linkend"/> + <xsl:with-param name="content" select="$content"/> + <xsl:with-param name="a.target" select="$a.target"/> + <xsl:with-param name="xhref" select="$xhref"/> + </xsl:call-template> + +</xsl:template> + +<xsl:template match="ulink" name="ulink"> + <xsl:param name="url" select="@url"/> + <xsl:variable name="link"> + <a> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="@id or @xml:id"> + <xsl:attribute name="name"> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:attribute> + </xsl:if> + <xsl:attribute name="href"><xsl:value-of select="$url"/></xsl:attribute> + <xsl:if test="$ulink.target != ''"> + <xsl:attribute name="target"> + <xsl:value-of select="$ulink.target"/> + </xsl:attribute> + </xsl:if> + <xsl:choose> + <xsl:when test="count(child::node())=0"> + <xsl:value-of select="$url"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </a> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('suwl:unwrapLinks')"> + <xsl:copy-of select="suwl:unwrapLinks($link)"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$link"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="olink" name="olink"> + <!-- olink content may be passed in from xlink olink --> + <xsl:param name="content" select="NOTANELEMENT"/> + + <xsl:call-template name="anchor"/> + + <xsl:variable name="localinfo" select="@localinfo"/> + + <xsl:choose> + <!-- olinks resolved by stylesheet and target database --> + <xsl:when test="@targetdoc or @targetptr or + (@xlink:role=$xolink.role and + contains(@xlink:href, '#') )" > + + <xsl:variable name="targetdoc.att"> + <xsl:choose> + <xsl:when test="@targetdoc != ''"> + <xsl:value-of select="@targetdoc"/> + </xsl:when> + <xsl:when test="@xlink:role=$xolink.role and + contains(@xlink:href, '#')" > + <xsl:value-of select="substring-before(@xlink:href, '#')"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="targetptr.att"> + <xsl:choose> + <xsl:when test="@targetptr != ''"> + <xsl:value-of select="@targetptr"/> + </xsl:when> + <xsl:when test="@xlink:role=$xolink.role and + contains(@xlink:href, '#')" > + <xsl:value-of select="substring-after(@xlink:href, '#')"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="olink.lang"> + <xsl:call-template name="l10n.language"> + <xsl:with-param name="xref-context" select="true()"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="target.database.filename"> + <xsl:call-template name="select.target.database"> + <xsl:with-param name="targetdoc.att" select="$targetdoc.att"/> + <xsl:with-param name="targetptr.att" select="$targetptr.att"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="target.database" + select="document($target.database.filename,/)"/> + + <xsl:if test="$olink.debug != 0"> + <xsl:message> + <xsl:text>Olink debug: root element of target.database '</xsl:text> + <xsl:value-of select="$target.database.filename"/> + <xsl:text>' is '</xsl:text> + <xsl:value-of select="local-name($target.database/*[1])"/> + <xsl:text>'.</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="olink.key"> + <xsl:call-template name="select.olink.key"> + <xsl:with-param name="targetdoc.att" select="$targetdoc.att"/> + <xsl:with-param name="targetptr.att" select="$targetptr.att"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="string-length($olink.key) = 0"> + <xsl:message> + <xsl:text>Error: unresolved olink: </xsl:text> + <xsl:text>targetdoc/targetptr = '</xsl:text> + <xsl:value-of select="$targetdoc.att"/> + <xsl:text>/</xsl:text> + <xsl:value-of select="$targetptr.att"/> + <xsl:text>'.</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="href"> + <xsl:call-template name="make.olink.href"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="hottext"> + <xsl:choose> + <xsl:when test="$content"> + <xsl:copy-of select="$content"/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="olink.hottext"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="olink.docname.citation"> + <xsl:call-template name="olink.document.citation"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="olink.page.citation"> + <xsl:call-template name="olink.page.citation"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$href != ''"> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$hottext"/> + </a> + <xsl:copy-of select="$olink.page.citation"/> + <xsl:copy-of select="$olink.docname.citation"/> + </xsl:when> + <xsl:otherwise> + <span class="olink"><xsl:copy-of select="$hottext"/></span> + <xsl:copy-of select="$olink.page.citation"/> + <xsl:copy-of select="$olink.docname.citation"/> + </xsl:otherwise> + </xsl:choose> + + </xsl:when> + + <!-- Or use old olink mechanism --> + <xsl:otherwise> + <xsl:variable name="href"> + <xsl:choose> + <xsl:when test="@linkmode"> + <!-- use the linkmode to get the base URI, use localinfo as fragid --> + <xsl:variable name="modespec" select="key('id',@linkmode)"/> + <xsl:if test="count($modespec) != 1 + or local-name($modespec) != 'modespec'"> + <xsl:message>Warning: olink linkmode pointer is wrong.</xsl:message> + </xsl:if> + <xsl:value-of select="$modespec"/> + <xsl:if test="@localinfo"> + <xsl:text>#</xsl:text> + <xsl:value-of select="@localinfo"/> + </xsl:if> + </xsl:when> + <xsl:when test="@type = 'href'"> + <xsl:call-template name="olink.outline"> + <xsl:with-param name="outline.base.uri" + select="unparsed-entity-uri(@targetdocent)"/> + <xsl:with-param name="localinfo" select="@localinfo"/> + <xsl:with-param name="return" select="'href'"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$olink.resolver"/> + <xsl:text>?</xsl:text> + <xsl:value-of select="$olink.sysid"/> + <xsl:value-of select="unparsed-entity-uri(@targetdocent)"/> + <!-- XSL gives no access to the public identifier (grumble...) --> + <xsl:if test="@localinfo"> + <xsl:text>&</xsl:text> + <xsl:value-of select="$olink.fragid"/> + <xsl:value-of select="@localinfo"/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$href != ''"> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="olink.hottext"/> + </a> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="olink.hottext"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="pagenumber.markup"> + <!-- no-op in HTML --> +</xsl:template> + + +<xsl:template name="olink.outline"> + <xsl:param name="outline.base.uri"/> + <xsl:param name="localinfo"/> + <xsl:param name="return" select="href"/> + + <xsl:variable name="outline-file" + select="concat($outline.base.uri, + $olink.outline.ext)"/> + + <xsl:variable name="outline" select="document($outline-file,.)/div"/> + + <xsl:variable name="node-href"> + <xsl:choose> + <xsl:when test="$localinfo != ''"> + <xsl:variable name="node" select="$outline// + *[@id=$localinfo or @xml:id=$localinfo]"/> + <xsl:value-of select="$node/@href"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$outline/@href"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="node-xref"> + <xsl:choose> + <xsl:when test="$localinfo != ''"> + <xsl:variable name="node" select="$outline// + *[@id=$localinfo or @xml:id=$localinfo]"/> + <xsl:copy-of select="$node/xref"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$outline/xref"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$return = 'href'"> + <xsl:value-of select="$node-href"/> + </xsl:when> + <xsl:when test="$return = 'xref'"> + <xsl:value-of select="$node-xref"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$node-xref"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="xref.xreflabel"> + <!-- called to process an xreflabel...you might use this to make --> + <!-- xreflabels come out in the right font for different targets, --> + <!-- for example. --> + <xsl:param name="target" select="."/> + <xsl:value-of select="$target/@xreflabel"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="title" mode="xref"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="command" mode="xref"> + <xsl:call-template name="inline.boldseq"/> +</xsl:template> + +<xsl:template match="function" mode="xref"> + <xsl:call-template name="inline.monoseq"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="insert.title.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="title"/> + + <xsl:choose> + <!-- FIXME: what about the case where titleabbrev is inside the info? --> + <xsl:when test="$purpose = 'xref' and titleabbrev"> + <xsl:apply-templates select="." mode="titleabbrev.markup"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$title"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="chapter|appendix" mode="insert.title.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="title"/> + + <xsl:choose> + <xsl:when test="$purpose = 'xref'"> + <i> + <xsl:copy-of select="$title"/> + </i> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$title"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="insert.subtitle.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="subtitle"/> + + <xsl:copy-of select="$subtitle"/> +</xsl:template> + +<xsl:template match="*" mode="insert.label.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="label"/> + + <xsl:copy-of select="$label"/> +</xsl:template> + +<xsl:template match="*" mode="insert.pagenumber.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="pagenumber"/> + + <xsl:copy-of select="$pagenumber"/> +</xsl:template> + +<xsl:template match="*" mode="insert.direction.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="direction"/> + + <xsl:copy-of select="$direction"/> +</xsl:template> + +<xsl:template match="*" mode="insert.olink.docname.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="docname"/> + + <span class="olinkdocname"> + <xsl:copy-of select="$docname"/> + </span> + +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/lib/lib.xsl b/docs/xsl-generic/lib/lib.xsl new file mode 100644 index 00000000..5eee4862 --- /dev/null +++ b/docs/xsl-generic/lib/lib.xsl @@ -0,0 +1,480 @@ +<?xml version="1.0" encoding="ASCII"?> +<!-- ******************************************************************** + $Id: lib.xweb 7102 2007-07-20 15:35:24Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + This module implements DTD-independent functions + + ******************************************************************** --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<xsl:template name="dot.count"> + <!-- Returns the number of "." characters in a string --> + <xsl:param name="string"/> + <xsl:param name="count" select="0"/> + <xsl:choose> + <xsl:when test="contains($string, '.')"> + <xsl:call-template name="dot.count"> + <xsl:with-param name="string" select="substring-after($string, '.')"/> + <xsl:with-param name="count" select="$count+1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="copy-string"> + <!-- returns 'count' copies of 'string' --> + <xsl:param name="string"/> + <xsl:param name="count" select="0"/> + <xsl:param name="result"/> + + <xsl:choose> + <xsl:when test="$count>0"> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="count" select="$count - 1"/> + <xsl:with-param name="result"> + <xsl:value-of select="$result"/> + <xsl:value-of select="$string"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$result"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="string.subst"> + <xsl:param name="string"/> + <xsl:param name="target"/> + <xsl:param name="replacement"/> + + <xsl:choose> + <xsl:when test="contains($string, $target)"> + <xsl:variable name="rest"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="substring-after($string, $target)"/> + <xsl:with-param name="target" select="$target"/> + <xsl:with-param name="replacement" select="$replacement"/> + </xsl:call-template> + </xsl:variable> + <xsl:value-of select="concat(substring-before($string, $target), $replacement, $rest)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$string"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="xpointer.idref"> + <xsl:param name="xpointer">http://...</xsl:param> + <xsl:choose> + <xsl:when test="starts-with($xpointer, '#xpointer(id(')"> + <xsl:variable name="rest" select="substring-after($xpointer, '#xpointer(id(')"/> + <xsl:variable name="quote" select="substring($rest, 1, 1)"/> + <xsl:value-of select="substring-before(substring-after($xpointer, $quote), $quote)"/> + </xsl:when> + <xsl:when test="starts-with($xpointer, '#')"> + <xsl:value-of select="substring-after($xpointer, '#')"/> + </xsl:when> + <!-- otherwise it's a pointer to some other document --> + </xsl:choose> +</xsl:template> +<xsl:template name="length-magnitude"> + <xsl:param name="length" select="'0pt'"/> + + <xsl:choose> + <xsl:when test="string-length($length) = 0"/> + <xsl:when test="substring($length,1,1) = '0' or substring($length,1,1) = '1' or substring($length,1,1) = '2' or substring($length,1,1) = '3' or substring($length,1,1) = '4' or substring($length,1,1) = '5' or substring($length,1,1) = '6' or substring($length,1,1) = '7' or substring($length,1,1) = '8' or substring($length,1,1) = '9' or substring($length,1,1) = '.'"> + <xsl:value-of select="substring($length,1,1)"/> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="substring($length,2)"/> + </xsl:call-template> + </xsl:when> + </xsl:choose> +</xsl:template> +<xsl:template name="length-units"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="default.units" select="'px'"/> + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$units = ''"> + <xsl:value-of select="$default.units"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$units"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="length-spec"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="default.units" select="'px'"/> + + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:value-of select="$magnitude"/> + <xsl:choose> + <xsl:when test="$units='cm' or $units='mm' or $units='in' or $units='pt' or $units='pc' or $units='px' or $units='em'"> + <xsl:value-of select="$units"/> + </xsl:when> + <xsl:when test="$units = ''"> + <xsl:value-of select="$default.units"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized unit of measure: </xsl:text> + <xsl:value-of select="$units"/> + <xsl:text>.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="length-in-points"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="em.size" select="10"/> + <xsl:param name="pixels.per.inch" select="90"/> + + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$units = 'pt'"> + <xsl:value-of select="$magnitude"/> + </xsl:when> + <xsl:when test="$units = 'cm'"> + <xsl:value-of select="$magnitude div 2.54 * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'mm'"> + <xsl:value-of select="$magnitude div 25.4 * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'in'"> + <xsl:value-of select="$magnitude * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'pc'"> + <xsl:value-of select="$magnitude * 12.0"/> + </xsl:when> + <xsl:when test="$units = 'px'"> + <xsl:value-of select="$magnitude div $pixels.per.inch * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'em'"> + <xsl:value-of select="$magnitude * $em.size"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized unit of measure: </xsl:text> + <xsl:value-of select="$units"/> + <xsl:text>.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="pi-attribute"> + <xsl:param name="pis" select="processing-instruction('BOGUS_PI')"/> + <xsl:param name="attribute">filename</xsl:param> + <xsl:param name="count">1</xsl:param> + + <xsl:choose> + <xsl:when test="$count>count($pis)"> + <!-- not found --> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="pi"> + <xsl:value-of select="$pis[$count]"/> + </xsl:variable> + <xsl:variable name="pivalue"> + <xsl:value-of select="concat(' ', normalize-space($pi))"/> + </xsl:variable> + <xsl:choose> + <xsl:when test="contains($pivalue,concat(' ', $attribute, '='))"> + <xsl:variable name="rest" select="substring-after($pivalue,concat(' ', $attribute,'='))"/> + <xsl:variable name="quote" select="substring($rest,1,1)"/> + <xsl:value-of select="substring-before(substring($rest,2),$quote)"/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="pi-attribute"> + <xsl:with-param name="pis" select="$pis"/> + <xsl:with-param name="attribute" select="$attribute"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="lookup.key"> + <xsl:param name="key" select="''"/> + <xsl:param name="table" select="''"/> + + <xsl:if test="contains($table, ' ')"> + <xsl:choose> + <xsl:when test="substring-before($table, ' ') = $key"> + <xsl:variable name="rest" select="substring-after($table, ' ')"/> + <xsl:choose> + <xsl:when test="contains($rest, ' ')"> + <xsl:value-of select="substring-before($rest, ' ')"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$rest"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="lookup.key"> + <xsl:with-param name="key" select="$key"/> + <xsl:with-param name="table" select="substring-after(substring-after($table,' '), ' ')"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:if> +</xsl:template> +<xsl:template name="xpath.location"> + <xsl:param name="node" select="."/> + <xsl:param name="path" select="''"/> + + <xsl:variable name="next.path"> + <xsl:value-of select="local-name($node)"/> + <xsl:if test="$path != ''">/</xsl:if> + <xsl:value-of select="$path"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$node/parent::*"> + <xsl:call-template name="xpath.location"> + <xsl:with-param name="node" select="$node/parent::*"/> + <xsl:with-param name="path" select="$next.path"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:text>/</xsl:text> + <xsl:value-of select="$next.path"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="comment-escape-string"> + <xsl:param name="string" select="''"/> + + <xsl:if test="starts-with($string, '-')"> + <xsl:text> </xsl:text> + </xsl:if> + + <xsl:call-template name="comment-escape-string.recursive"> + <xsl:with-param name="string" select="$string"/> + </xsl:call-template> + + <xsl:if test="substring($string, string-length($string), 1) = '-'"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> +<xsl:template name="comment-escape-string.recursive"> + <xsl:param name="string" select="''"/> + <xsl:choose> + <xsl:when test="contains($string, '--')"> + <xsl:value-of select="substring-before($string, '--')"/> + <xsl:value-of select="'- -'"/> + <xsl:call-template name="comment-escape-string.recursive"> + <xsl:with-param name="string" select="substring-after($string, '--')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$string"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + <xsl:template name="str.tokenize.keep.delimiters"> + <xsl:param name="string" select="''"/> + <xsl:param name="delimiters" select="' '"/> + <xsl:choose> + <xsl:when test="not($string)"/> + <xsl:when test="not($delimiters)"> + <xsl:call-template name="str.tokenize.keep.delimiters-characters"> + <xsl:with-param name="string" select="$string"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="delimiters" select="$delimiters"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template name="str.tokenize.keep.delimiters-characters"> + <xsl:param name="string"/> + <xsl:if test="$string"> + <ssb:token xmlns:ssb="http://sideshowbarker.net/ns"><xsl:value-of select="substring($string, 1, 1)"/></ssb:token> + <xsl:call-template name="str.tokenize.keep.delimiters-characters"> + <xsl:with-param name="string" select="substring($string, 2)"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + <xsl:template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:param name="string"/> + <xsl:param name="delimiters"/> + <xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/> + <xsl:choose> + <xsl:when test="not($delimiter)"> + <ssb:token xmlns:ssb="http://sideshowbarker.net/ns"><xsl:value-of select="$string"/></ssb:token> + </xsl:when> + <xsl:when test="contains($string, $delimiter)"> + <xsl:if test="not(starts-with($string, $delimiter))"> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="substring-before($string, $delimiter)"/> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)"/> + </xsl:call-template> + </xsl:if> + <!-- output each delimiter --> + <xsl:value-of select="$delimiter"/> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="substring-after($string, $delimiter)"/> + <xsl:with-param name="delimiters" select="$delimiters"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template name="apply-string-subst-map"> + <xsl:param name="content"/> + <xsl:param name="map.contents"/> + <xsl:variable name="replaced_text"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="$map.contents[1]/@oldstring"/> + <xsl:with-param name="replacement" select="$map.contents[1]/@newstring"/> + </xsl:call-template> + </xsl:variable> + <xsl:choose> + <xsl:when test="$map.contents[2]"> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$replaced_text"/> + <xsl:with-param name="map.contents" select="$map.contents[position() > 1]"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$replaced_text"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +<xsl:template name="count.uri.path.depth"> + <xsl:param name="filename" select="''"/> + <xsl:param name="count" select="0"/> + + <xsl:choose> + <xsl:when test="contains($filename, '/')"> + <xsl:call-template name="count.uri.path.depth"> + <xsl:with-param name="filename" select="substring-after($filename, '/')"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="trim.common.uri.paths"> + <xsl:param name="uriA" select="''"/> + <xsl:param name="uriB" select="''"/> + <xsl:param name="return" select="'A'"/> + + <xsl:choose> + <xsl:when test="contains($uriA, '/') and contains($uriB, '/') and substring-before($uriA, '/') = substring-before($uriB, '/')"> + <xsl:call-template name="trim.common.uri.paths"> + <xsl:with-param name="uriA" select="substring-after($uriA, '/')"/> + <xsl:with-param name="uriB" select="substring-after($uriB, '/')"/> + <xsl:with-param name="return" select="$return"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="$return = 'A'"> + <xsl:value-of select="$uriA"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$uriB"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + <xsl:template name="trim.text"> + <xsl:param name="contents" select="."/> + <xsl:variable name="contents-left-trimmed"> + <xsl:call-template name="trim-left"> + <xsl:with-param name="contents" select="$contents"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="contents-trimmed"> + <xsl:call-template name="trim-right"> + <xsl:with-param name="contents" select="$contents-left-trimmed"/> + </xsl:call-template> + </xsl:variable> + <xsl:value-of select="$contents-trimmed"/> + </xsl:template> + + <xsl:template name="trim-left"> + <xsl:param name="contents"/> + <xsl:choose> + <xsl:when test="starts-with($contents,' ') or starts-with($contents,' ') or starts-with($contents,' ') or starts-with($contents,' ')"> + <xsl:call-template name="trim-left"> + <xsl:with-param name="contents" select="substring($contents, 2)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template name="trim-right"> + <xsl:param name="contents"/> + <xsl:variable name="last-char"> + <xsl:value-of select="substring($contents, string-length($contents), 1)"/> + </xsl:variable> + <xsl:choose> + <xsl:when test="($last-char = ' ') or ($last-char = ' ') or ($last-char = ' ') or ($last-char = ' ')"> + <xsl:call-template name="trim-right"> + <xsl:with-param name="contents" select="substring($contents, 1, string-length($contents) - 1)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/block.xsl b/docs/xsl-generic/manpages/block.xsl new file mode 100644 index 00000000..efee63be --- /dev/null +++ b/docs/xsl-generic/manpages/block.xsl @@ -0,0 +1,296 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: block.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="caution|important|note|tip|warning"> + <xsl:call-template name="nested-section-title"/> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="formalpara"> + <xsl:variable name="title.wrapper"> + <xsl:value-of select="normalize-space(title[1])"/> + </xsl:variable> + <xsl:text>.PP </xsl:text> + <!-- * don't put linebreak after head; instead render it as a "run in" --> + <!-- * head, that is, inline, with a period and space following it --> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title.wrapper)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text>. </xsl:text> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="formalpara/para"> + <xsl:call-template name="mixed-block"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="para"> + <!-- * FIXME: Need to extract the ancestor::footnote, etc. checking and --> + <!-- * move to named template so that we can call it from templates for --> + <!-- * other block elements also --> + <xsl:choose> + <!-- * If a para is a descendant of a footnote, etc., then indent it --> + <!-- * (unless it is the first child, in which case don't generate --> + <!-- * anything at all to mark its start). --> + <!-- * FIXME: *blurb checking should not be munged in here the way --> + <!-- * it currently is; this probably breaks blurb indenting. --> + <xsl:when test="ancestor::footnote or + ancestor::annotation or + ancestor::authorblurb or + ancestor::personblurb"> + <xsl:if test="preceding-sibling::*[not(name() ='')]"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS 4n</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:text>.PP</xsl:text> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:call-template name="mixed-block"/> + <xsl:if test="ancestor::footnote or + ancestor::annotation or + ancestor::authorblurb or + ancestor::personblurb"> + <xsl:if test="preceding-sibling::*[not(name() ='')]"> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:text> </xsl:text> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * Yes, address, synopsis, and funcsynopsisinfo are verbatim environments. --> +<xsl:template match="literallayout|programlisting|screen| + address|synopsis|funcsynopsisinfo"> + <xsl:param name="indent"> + <!-- * Only indent this verbatim if $man.indent.verbatims is --> + <!-- * non-zero and it is not a child of a *synopsis element --> + <xsl:if test="not($man.indent.verbatims = 0) and + not(substring(local-name(..), + string-length(local-name(..))-7) = 'synopsis')"> + <xsl:text>Yes</xsl:text> + </xsl:if> + </xsl:param> + + <xsl:choose> + <!-- * Check to see if this verbatim item is within a parent element that --> + <!-- * allows mixed content. --> + <!-- * --> + <!-- * If it is within a mixed-content parent, then a line space is --> + <!-- * already added before it by the mixed-block template, so we don't --> + <!-- * need to add one here. --> + <!-- * --> + <!-- * If it is not within a mixed-content parent, then we need to add a --> + <!-- * line space before it. --> + <xsl:when test="parent::caption|parent::entry|parent::para| + parent::td|parent::th" /> <!-- do nothing --> + <xsl:otherwise> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$indent = 'Yes'"> + <!-- * start indented section --> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($man.indent.width = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$man.indent.width"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:choose> + <xsl:when test="self::funcsynopsisinfo"> + <!-- * All Funcsynopsisinfo content is by default rendered in bold, --> + <!-- * because the man(7) man page says this: --> + <!-- * --> + <!-- * For functions, the arguments are always specified using --> + <!-- * italics, even in the SYNOPSIS section, where the rest of --> + <!-- * the function is specified in bold --> + <!-- * --> + <!-- * Look through the contents of the man/man2 and man3 directories --> + <!-- * on your system, and you'll see that most existing pages do follow --> + <!-- * this "bold everything in function synopsis" rule. --> + <!-- * --> + <!-- * Users who don't want the bold output can choose to adjust the --> + <!-- * man.font.funcsynopsisinfo parameter on their own. So even if you --> + <!-- * don't personally like the way it looks, please don't change the --> + <!-- * default to be non-bold - because it's a convention that's --> + <!-- * followed is the vast majority of existing man pages that document --> + <!-- * functions, and we need to follow it by default, like it or no. --> + <xsl:text>.ft </xsl:text> + <xsl:value-of select="$man.font.funcsynopsisinfo"/> + <xsl:text> </xsl:text> + <xsl:text>.nf </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.fi </xsl:text> + <xsl:text>.ft </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Other verbatims do not need to get bolded --> + <xsl:text>.nf </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.fi </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$indent = 'Yes'"> + <!-- * end indented section --> + <xsl:text>.RE </xsl:text> + </xsl:if> + <!-- * if first following sibling node of this verbatim --> + <!-- * environment is a text node, output a line of space before it --> + <xsl:if test="following-sibling::node()[1][name(.) = '']"> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="table|informaltable"> + <xsl:apply-templates select="." mode="to.tbl"> + <!--* we call the to.tbl mode with the "source" param so that we can --> + <!--* preserve the context information and pass it down to the --> + <!--* named templates that do the actual table processing --> + <xsl:with-param name="source" select="ancestor::refentry/refnamediv[1]/refname[1]"/> + </xsl:apply-templates> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="informalexample"> + <xsl:apply-templates/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="figure|example"> + <xsl:variable name="param.placement" + select="substring-after(normalize-space($formal.title.placement), + concat(local-name(.), ' '))"/> + + <xsl:variable name="placement"> + <xsl:choose> + <xsl:when test="contains($param.placement, ' ')"> + <xsl:value-of select="substring-before($param.placement, ' ')"/> + </xsl:when> + <xsl:when test="$param.placement = ''">before</xsl:when> + <xsl:otherwise> + <xsl:value-of select="$param.placement"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:text>.PP </xsl:text> + <xsl:call-template name="formal.object"> + <xsl:with-param name="placement" select="$placement"/> + </xsl:call-template> + <xsl:text> </xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="mediaobject"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="imageobject"> + <xsl:text>[IMAGE]</xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="textobject[parent::inlinemediaobject]"> + <xsl:text>[</xsl:text> + <xsl:value-of select="."/> + <xsl:text>]</xsl:text> +</xsl:template> + +<xsl:template match="textobject"> + <xsl:apply-templates/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="formal.object"> + <xsl:param name="placement" select="'before'"/> + <xsl:param name="class" select="local-name(.)"/> + + <xsl:choose> + <xsl:when test="$placement = 'before'"> + <xsl:call-template name="formal.object.heading"/> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + <xsl:call-template name="formal.object.heading"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="formal.object.heading"> + <xsl:param name="object" select="."/> + <xsl:param name="title"> + <xsl:apply-templates select="$object" mode="object.title.markup.textonly"/> + </xsl:param> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + + <xsl:text> </xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * suppress abstract --> +<xsl:template match="abstract"/> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/charmap.groff.xsl b/docs/xsl-generic/manpages/charmap.groff.xsl new file mode 100644 index 00000000..7587659f --- /dev/null +++ b/docs/xsl-generic/manpages/charmap.groff.xsl @@ -0,0 +1,5985 @@ +<?xml version="1.0" encoding="US-ASCII"?> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:u="http://docbook.sf.net/xmlns/unichar/1.0" + exclude-result-prefixes="u"> + +<!-- ******************************************************************** + $Id: charmap.groff.xsl 6528 2007-01-19 08:54:04Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:character-map name="groff"> + + <!-- * *************************************************************** --> + <!-- * Commentary --> + <!-- * *************************************************************** --> + <!-- * --> + <!-- * This file maps a selection of Unicode symbols and special --> + <!-- * characters (about 800) to corresponding groff escape sequences.--> + <!-- * --> + <!-- * Although the format of this file follows the "character map" --> + <!-- * format described in the XSLT 2.0 specification[1], the file can --> + <!-- * also be used with an appropriate XSLT 1.0 stylesheet and any --> + <!-- * XSLT 1.0 processor. --> + <!-- * --> + <!-- * [1] http://www.w3.org/TR/xslt20/#character-maps --> + <!-- * --> + <!-- * In order to make the character map more readable, and to make --> + <!-- * it possible to create subsets of it at run time, it uses the --> + <!-- * following "extension attributes" (in the "unichar" namespace): --> + <!-- * --> + <!-- * - u:name = ISO character name (e.g., "OHM SIGN") --> + <!-- * - u:entity = ISO entity name (e.g., "ohm") --> + <!-- * - u:block = Unicode block name (e.g., "Letterlike Symbols") --> + <!-- * - u:class = character class (e.g., "bullets") --> + <!-- * --> + <!-- * Use of such extension attributes is permitted by the XSLT 2.0 --> + <!-- * spec; see the "Extension Attributes" section[2]. --> + <!-- * --> + <!-- * [2] http://www.w3.org/TR/xslt20/#extension-attributes --> + <!-- * --> + <!-- * *************************************************************** --> + <!-- * Acknowledgements --> + <!-- * *************************************************************** --> + <!-- * The following references were consulted when selecting roff --> + <!-- * mappings and character information: --> + <!-- * --> + <!-- * - groff_char(7) man page[3] --> + <!-- * - groff info file[4]; in particular, the "Page Motions" node[5] --> + <!-- * - tables in "Character Sets" chapter of "XML In a Nutshell"[6] --> + <!-- * - Zvon Character Search[7] --> + <!-- * --> + <!-- * [3] http://www.linux.se/showMan.php?TITLE=groff_char&SECTION=7 --> + <!-- * [4] http://www.fifi.org/cgi-bin/info2www?(groff) --> + <!-- * [5] http://www.fifi.org/cgi-bin/info2www?(groff)Page+Motions --> + <!-- * [6] http://www.ibiblio.org/xml/books/xian2/ --> + <!-- * [7] http://zvon.org/other/charSearch/PHP/search.php --> + <!-- * --> + <!-- * The initial version of this file (before the "string" mappings --> + <!-- * were added) was generated by taking the "unichars.el" file from --> + <!-- * Norm Walsh's "xmlunicode.el"[8] elisp distro, and running a --> + <!-- * script on it to convert it to XML. --> + <!-- * --> + <!-- * [8] http://nwalsh.com/emacs/xmlchars/ --> + <!-- * --> + <!-- * The idea for implementing a character map in the DocBook Project --> + <!-- * manpages system was inspired by Steve Cheng's docbook2x[9]; --> + <!-- * in particular, its "utf8trans" utility and character-map system. --> + <!-- * --> + <!-- * [9] http://docbook2x.sourceforge.net/ --> + <!-- * --> + <!-- * ################################################################# --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Latin-1/ISO-8859-1 --> + <!-- * x00a0 to x00ff --> + <!-- * ***************************************************************** --> + + <!-- * A no-break space can be written two ways in roff; the difference, --> + <!-- * according to the "Page Motions" node in the groff info page, is: --> + <!-- * --> + <!-- * "\ " = --> + <!-- * An unbreakable and unpaddable (i.e. not expanded during filling) --> + <!-- * space. --> + <!-- * --> + <!-- * "\~" = --> + <!-- * An unbreakable space that stretches like a normal --> + <!-- * inter-word space when a line is adjusted." --> + <!-- * --> + <!-- * Unfortunately, roff seems to do some weird things with long --> + <!-- * lines that only have words separated by "\~" spaces, so it's --> + <!-- * safer just to stick with the "\ " space --> + <xsl:output-character + character=" " + u:name="NO-BREAK SPACE" + u:entity="nbsp" + string="\ " + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¡" + u:name="INVERTED EXCLAMATION MARK" + u:entity="iexcl" + string="\(r!" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¢" + u:name="CENT SIGN" + u:entity="cent" + string="\(ct" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="£" + u:name="POUND SIGN" + u:entity="pound" + string="\(Po" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¤" + u:name="CURRENCY SIGN" + u:entity="curren" + string="\(Cs" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¥" + u:name="YEN SIGN" + u:entity="yen" + string="\(Ye" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¦" + u:name="BROKEN BAR" + u:entity="brvbar" + string="\(bb" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="§" + u:name="SECTION SIGN" + u:entity="sect" + string="\(sc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¨" + u:name="DIAERESIS" + u:entity="Dot" + string="\(ad" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="©" + u:name="COPYRIGHT SIGN" + u:entity="copy" + string="\(co" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="ª" + u:name="FEMININE ORDINAL INDICATOR" + u:entity="ordf" + string="\(Of" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="«" + u:name="LEFT-POINTING DOUBLE ANGLE QUOTATION MARK" + u:entity="laquo" + string="\(Fo" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¬" + u:name="NOT SIGN" + u:entity="not" + string="\(no" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <!-- * groff_char(7) man page sayxsl: "the soft hyphen control character --> + <!-- * (prints as itself). groff never use this character for output --> + <!-- * (thus it is omitted in the table below); the input character 173 --> + <!-- * is onto \%." --> + <xsl:output-character + character="­" + u:name="SOFT HYPHEN" + u:entity="shy" + string="\%" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="®" + u:name="REGISTERED SIGN" + u:entity="reg" + string="\(rg" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¯" + u:name="MACRON" + u:entity="macr" + string="\(a-" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="°" + u:name="DEGREE SIGN" + u:entity="deg" + string="\(de" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="±" + u:name="PLUS-MINUS SIGN" + u:entity="plusmn" + string="\(+-" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="²" + u:name="SUPERSCRIPT TWO" + u:entity="sup2" + string="\(S2" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="³" + u:name="SUPERSCRIPT THREE" + u:entity="sup3" + string="\(S3" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="´" + u:name="ACUTE ACCENT" + u:entity="acute" + string="\(aa" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="µ" + u:name="MICRO SIGN" + u:entity="micro" + string="\(mc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¶" + u:name="PILCROW SIGN" + u:entity="para" + string="\(ps" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <!-- * according to groff_char(7), I think the escape string \(pc --> + <!-- * "periodcentered" could also be used for middot; not sure which --> + <!-- * is better, but "md" mnemonic is a better fit :-) --> + <xsl:output-character + character="·" + u:name="MIDDLE DOT" + u:entity="middot" + string="\(md" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¸" + u:name="CEDILLA" + u:entity="cedil" + string="\(ac" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¹" + u:name="SUPERSCRIPT ONE" + u:entity="sup1" + string="\(S1" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="º" + u:name="MASCULINE ORDINAL INDICATOR" + u:entity="ordm" + string="\(Om" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="»" + u:name="RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK" + u:entity="raquo" + string="\(Fc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¼" + u:name="VULGAR FRACTION ONE QUARTER" + u:entity="frac14" + string="\(14" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="½" + u:name="VULGAR FRACTION ONE HALF" + u:entity="frac12" + string="\(12" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¾" + u:name="VULGAR FRACTION THREE QUARTERS" + u:entity="frac34" + string="\(34" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¿" + u:name="INVERTED QUESTION MARK" + u:entity="iquest" + string="\(r?" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="À" + u:name="LATIN CAPITAL LETTER A WITH GRAVE" + u:entity="Agrave" + string="\(`A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Á" + u:name="LATIN CAPITAL LETTER A WITH ACUTE" + u:entity="Aacute" + string="\('A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Â" + u:name="LATIN CAPITAL LETTER A WITH CIRCUMFLEX" + u:entity="Acirc" + string="\(^A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ã" + u:name="LATIN CAPITAL LETTER A WITH TILDE" + u:entity="Atilde" + string="\(~A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ä" + u:name="LATIN CAPITAL LETTER A WITH DIAERESIS" + u:entity="Auml" + string="\(:A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Å" + u:name="LATIN CAPITAL LETTER A WITH RING ABOVE" + u:entity="Aring" + string="\(oA" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Æ" + u:name="LATIN CAPITAL LETTER AE" + u:entity="AElig" + string="\(AE" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ç" + u:name="LATIN CAPITAL LETTER C WITH CEDILLA" + u:entity="Ccedil" + string="\(,C" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="È" + u:name="LATIN CAPITAL LETTER E WITH GRAVE" + u:entity="Egrave" + string="\(`E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="É" + u:name="LATIN CAPITAL LETTER E WITH ACUTE" + u:entity="Eacute" + string="\('E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ê" + u:name="LATIN CAPITAL LETTER E WITH CIRCUMFLEX" + u:entity="Ecirc" + string="\(^E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ë" + u:name="LATIN CAPITAL LETTER E WITH DIAERESIS" + u:entity="Euml" + string="\(:E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ì" + u:name="LATIN CAPITAL LETTER I WITH GRAVE" + u:entity="Igrave" + string="\(`I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Í" + u:name="LATIN CAPITAL LETTER I WITH ACUTE" + u:entity="Iacute" + string="\('I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Î" + u:name="LATIN CAPITAL LETTER I WITH CIRCUMFLEX" + u:entity="Icirc" + string="\(^I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ï" + u:name="LATIN CAPITAL LETTER I WITH DIAERESIS" + u:entity="Iuml" + string="\(:I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ð" + u:name="LATIN CAPITAL LETTER ETH" + u:entity="ETH" + string="\(-D" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ñ" + u:name="LATIN CAPITAL LETTER N WITH TILDE" + u:entity="Ntilde" + string="\(~N" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ò" + u:name="LATIN CAPITAL LETTER O WITH GRAVE" + u:entity="Ograve" + string="\(`O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ó" + u:name="LATIN CAPITAL LETTER O WITH ACUTE" + u:entity="Oacute" + string="\('O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ô" + u:name="LATIN CAPITAL LETTER O WITH CIRCUMFLEX" + u:entity="Ocirc" + string="\(^O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Õ" + u:name="LATIN CAPITAL LETTER O WITH TILDE" + u:entity="Otilde" + string="\(~O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ö" + u:name="LATIN CAPITAL LETTER O WITH DIAERESIS" + u:entity="Ouml" + string="\(:O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="×" + u:name="MULTIPLICATION SIGN" + u:entity="times" + string="\(mu" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="Ø" + u:name="LATIN CAPITAL LETTER O WITH STROKE" + u:entity="Oslash" + string="\(/O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ù" + u:name="LATIN CAPITAL LETTER U WITH GRAVE" + u:entity="Ugrave" + string="\(`U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ú" + u:name="LATIN CAPITAL LETTER U WITH ACUTE" + u:entity="Uacute" + string="\('U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Û" + u:name="LATIN CAPITAL LETTER U WITH CIRCUMFLEX" + u:entity="Ucirc" + string="\(^U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ü" + u:name="LATIN CAPITAL LETTER U WITH DIAERESIS" + u:entity="Uuml" + string="\(:U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ý" + u:name="LATIN CAPITAL LETTER Y WITH ACUTE" + u:entity="Yacute" + string="\('Y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Þ" + u:name="LATIN CAPITAL LETTER THORN" + u:entity="THORN" + string="\(TP" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ß" + u:name="LATIN SMALL LETTER SHARP S" + u:entity="szlig" + string="\(ss" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="à" + u:name="LATIN SMALL LETTER A WITH GRAVE" + u:entity="agrave" + string="\(`a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="á" + u:name="LATIN SMALL LETTER A WITH ACUTE" + u:entity="aacute" + string="\('a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="â" + u:name="LATIN SMALL LETTER A WITH CIRCUMFLEX" + u:entity="acirc" + string="\(^a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ã" + u:name="LATIN SMALL LETTER A WITH TILDE" + u:entity="atilde" + string="\(~a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ä" + u:name="LATIN SMALL LETTER A WITH DIAERESIS" + u:entity="auml" + string="\(:a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="å" + u:name="LATIN SMALL LETTER A WITH RING ABOVE" + u:entity="aring" + string="\(oa" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="æ" + u:name="LATIN SMALL LETTER AE" + u:entity="aelig" + string="\(ae" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ç" + u:name="LATIN SMALL LETTER C WITH CEDILLA" + u:entity="ccedil" + string="\(,c" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="è" + u:name="LATIN SMALL LETTER E WITH GRAVE" + u:entity="egrave" + string="\(`e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="é" + u:name="LATIN SMALL LETTER E WITH ACUTE" + u:entity="eacute" + string="\('e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ê" + u:name="LATIN SMALL LETTER E WITH CIRCUMFLEX" + u:entity="ecirc" + string="\(^e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ë" + u:name="LATIN SMALL LETTER E WITH DIAERESIS" + u:entity="euml" + string="\(:e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ì" + u:name="LATIN SMALL LETTER I WITH GRAVE" + u:entity="igrave" + string="\(`i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="í" + u:name="LATIN SMALL LETTER I WITH ACUTE" + u:entity="iacute" + string="\('i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="î" + u:name="LATIN SMALL LETTER I WITH CIRCUMFLEX" + u:entity="icirc" + string="\(^i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ï" + u:name="LATIN SMALL LETTER I WITH DIAERESIS" + u:entity="iuml" + string="\(:i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ð" + u:name="LATIN SMALL LETTER ETH" + u:entity="eth" + string="\(Sd" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ñ" + u:name="LATIN SMALL LETTER N WITH TILDE" + u:entity="ntilde" + string="\(~n" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ò" + u:name="LATIN SMALL LETTER O WITH GRAVE" + u:entity="ograve" + string="\(`o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ó" + u:name="LATIN SMALL LETTER O WITH ACUTE" + u:entity="oacute" + string="\('o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ô" + u:name="LATIN SMALL LETTER O WITH CIRCUMFLEX" + u:entity="ocirc" + string="\(^o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="õ" + u:name="LATIN SMALL LETTER O WITH TILDE" + u:entity="otilde" + string="\(~o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ö" + u:name="LATIN SMALL LETTER O WITH DIAERESIS" + u:entity="ouml" + string="\(:o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="÷" + u:name="DIVISION SIGN" + u:entity="divide" + string="\(di" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="ø" + u:name="LATIN SMALL LETTER O WITH STROKE" + u:entity="oslash" + string="\(/o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ù" + u:name="LATIN SMALL LETTER U WITH GRAVE" + u:entity="ugrave" + string="\(`u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ú" + u:name="LATIN SMALL LETTER U WITH ACUTE" + u:entity="uacute" + string="\('u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="û" + u:name="LATIN SMALL LETTER U WITH CIRCUMFLEX" + u:entity="ucirc" + string="\(^u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ü" + u:name="LATIN SMALL LETTER U WITH DIAERESIS" + u:entity="uuml" + string="\(:u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ý" + u:name="LATIN SMALL LETTER Y WITH ACUTE" + u:entity="yacute" + string="\('y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="þ" + u:name="LATIN SMALL LETTER THORN" + u:entity="thorn" + string="\(Tp" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ÿ" + u:name="LATIN SMALL LETTER Y WITH DIAERESIS" + u:entity="yuml" + string="\(:y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <!-- * **************************************************************** --> + <!-- * End: Latin-1/ISO-8859-1 --> + <!-- * **************************************************************** --> + + <!-- * **************************************************************** --> + <!-- * Begin: --> + <!-- * --> + <!-- * - x0100 to x017f (Latin Extended-A) --> + <!-- * - x0180 to x023f (Latin Extended-B) --> + <!-- * - x0250 to x02ad (IPA Extensions) --> + <!-- * - x02b0 to x02ee (Spacing Modifier Letters) --> + <!-- * - x0300 to x036f (Combining Diacritical Marks) --> + <!-- * --> + <!-- * Other than the following exceptions, characters in these --> + <!-- * blocks don't have any roff equivalents --> + <!-- * **************************************************************** --> + + <xsl:output-character + character="ı" + u:name="LATIN SMALL LETTER DOTLESS I" + u:entity="inodot" + string="\(.i" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="IJ" + u:name="LATIN CAPITAL LIGATURE IJ" + u:entity="IJlig" + string="\(IJ" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ij" + u:name="LATIN SMALL LIGATURE IJ" + u:entity="ijlig" + string="\(ij" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="Ł" + u:name="LATIN CAPITAL LETTER L WITH STROKE" + u:entity="Lstrok" + string="\(/L" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ł" + u:name="LATIN SMALL LETTER L WITH STROKE" + u:entity="lstrok" + string="\(/l" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="Œ" + u:name="LATIN CAPITAL LIGATURE OE" + u:entity="OElig" + string="\(OE" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="œ" + u:name="LATIN SMALL LIGATURE OE" + u:entity="oelig" + string="\(oe" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ƒ" + u:name="LATIN SMALL LETTER F WITH HOOK" + u:entity="fnof" + string="\(Fn" + u:block="Latin Extended-B" + /> + <xsl:output-character + character="ˆ" + u:name="MODIFIER LETTER CIRCUMFLEX ACCENT" + u:entity="circ" + string="\(a^" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="ˇ" + u:name="CARON" + u:entity="caron" + string="\(ac" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="ˉ" + u:name="MODIFIER LETTER MACRON" + string="\(a-" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="˘" + u:name="BREVE" + u:entity="breve" + string="\(ab" + u:block="Spacing Modifier Letters" + /> +<!-- * there does not seem to by any roff equivalent for "dot above" --> +<!-- * <xsl:output-character --> +<!-- * character="˙" --> +<!-- * u:name="DOT ABOVE" --> +<!-- * u:entity="dot" --> +<!-- * /> --> + <xsl:output-character + character="˚" + u:name="RING ABOVE" + u:entity="ring" + string="\(ao" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="˛" + u:name="OGONEK" + u:entity="ogon" + string="\(ho" + u:block="Spacing Modifier Letters" + /> + <!-- groff_char(7) calls Unicode x02dd a "Hungarian umlaut" --> + <xsl:output-character + character="˝" + u:name="DOUBLE ACUTE ACCENT" + u:entity="dblac" + string='\(a"' + u:block="Spacing Modifier Letters" + /> + + <!-- * **************************************************************** --> + <!-- * End: --> + <!-- * - Latin Extended-A --> + <!-- * - Latin Extended-B --> + <!-- * - IPA Extensions --> + <!-- * - Spacing Modifier Letters --> + <!-- * - Combining Diacritical Marks --> + <!-- * **************************************************************** --> + + <!-- * **************************************************************** --> + <!-- * Begin: Greek and Coptic --> + <!-- * x0370 to x03ff --> + <!-- * **************************************************************** --> + + <xsl:output-character + character="Α" + u:name="GREEK CAPITAL LETTER ALPHA" + u:entity="Agr" + string="\(*A)" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Β" + u:name="GREEK CAPITAL LETTER BETA" + u:entity="Bgr" + string="\(*B" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Γ" + u:name="GREEK CAPITAL LETTER GAMMA" + u:entity="Gamma" + string="\(*G" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Δ" + u:name="GREEK CAPITAL LETTER DELTA" + u:entity="Delta" + string="\(*D" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ε" + u:name="GREEK CAPITAL LETTER EPSILON" + u:entity="Egr" + string="\(*E" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ζ" + u:name="GREEK CAPITAL LETTER ZETA" + u:entity="Zgr" + string="\(*Z" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Η" + u:name="GREEK CAPITAL LETTER ETA" + u:entity="EEgr" + string="\(*Y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Θ" + u:name="GREEK CAPITAL LETTER THETA" + u:entity="THgr" + string="\(*H" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ι" + u:name="GREEK CAPITAL LETTER IOTA" + u:entity="Igr" + string="\(*I" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Κ" + u:name="GREEK CAPITAL LETTER KAPPA" + u:entity="Kgr" + string="\(*K" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Λ" + u:name="GREEK CAPITAL LETTER LAMDA" + u:entity="Lambda" + string="\(*L" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Μ" + u:name="GREEK CAPITAL LETTER MU" + u:entity="Mgr" + string="\(*M" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ν" + u:name="GREEK CAPITAL LETTER NU" + u:entity="Ngr" + string="\(*N" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ξ" + u:name="GREEK CAPITAL LETTER XI" + u:entity="Xgr" + string="\(*C" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ο" + u:name="GREEK CAPITAL LETTER OMICRON" + u:entity="Ogr" + string="\(*O" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Π" + u:name="GREEK CAPITAL LETTER PI" + u:entity="Pgr" + string="\(*P" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ρ" + u:name="GREEK CAPITAL LETTER RHO" + u:entity="Rgr" + string="\(*R" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Σ" + u:name="GREEK CAPITAL LETTER SIGMA" + u:entity="Sgr" + string="\(*S" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Τ" + u:name="GREEK CAPITAL LETTER TAU" + u:entity="Tgr" + string="\(*T" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Υ" + u:name="GREEK CAPITAL LETTER UPSILON" + u:entity="Ugr" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Φ" + u:name="GREEK CAPITAL LETTER PHI" + u:entity="PHgr" + string="\(*F" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Χ" + u:name="GREEK CAPITAL LETTER CHI" + u:entity="KHgr" + string="\(*X" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ψ" + u:name="GREEK CAPITAL LETTER PSI" + u:entity="PSgr" + string="\(*Q" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ω" + u:name="GREEK CAPITAL LETTER OMEGA" + u:entity="OHgr" + string="\(*W" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ϊ" + u:name="GREEK CAPITAL LETTER IOTA WITH DIALYTIKA" + u:entity="Idigr" + string="\(*I" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ϋ" + u:name="GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA" + u:entity="Udigr" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ά" + u:name="GREEK SMALL LETTER ALPHA WITH TONOS" + u:entity="aacgr" + string="\(*a" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="έ" + u:name="GREEK SMALL LETTER EPSILON WITH TONOS" + u:entity="eacgr" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ή" + u:name="GREEK SMALL LETTER ETA WITH TONOS" + u:entity="eeacgr" + string="\(*y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ί" + u:name="GREEK SMALL LETTER IOTA WITH TONOS" + u:entity="iacgr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ΰ" + u:name="GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS" + u:entity="udiagr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="α" + u:name="GREEK SMALL LETTER ALPHA" + u:entity="agr" + string="\(*a" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="β" + u:name="GREEK SMALL LETTER BETA" + u:entity="beta" + string="\(*b" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="γ" + u:name="GREEK SMALL LETTER GAMMA" + u:entity="gamma" + string="\(*g" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="δ" + u:name="GREEK SMALL LETTER DELTA" + u:entity="delta" + string="\(*d" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ε" + u:name="GREEK SMALL LETTER EPSILON" + u:entity="epsi" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ζ" + u:name="GREEK SMALL LETTER ZETA" + u:entity="zeta" + string="\(*z" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="η" + u:name="GREEK SMALL LETTER ETA" + u:entity="eegr" + string="\(*y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="θ" + u:name="GREEK SMALL LETTER THETA" + u:entity="thetas" + string="\(*h" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ι" + u:name="GREEK SMALL LETTER IOTA" + u:entity="igr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="κ" + u:name="GREEK SMALL LETTER KAPPA" + u:entity="kappa" + string="\(*k" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="λ" + u:name="GREEK SMALL LETTER LAMDA" + u:entity="lambda" + string="\(*l" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="μ" + u:name="GREEK SMALL LETTER MU" + u:entity="mgr" + string="\(*m" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ν" + u:name="GREEK SMALL LETTER NU" + u:entity="ngr" + string="\(*n" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ξ" + u:name="GREEK SMALL LETTER XI" + u:entity="xgr" + string="\(*c" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ο" + u:name="GREEK SMALL LETTER OMICRON" + u:entity="ogr" + string="\(*o" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="π" + u:name="GREEK SMALL LETTER PI" + u:entity="pgr" + string="\(*p" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ρ" + u:name="GREEK SMALL LETTER RHO" + u:entity="rgr" + string="\(*r" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ς" + u:name="GREEK SMALL LETTER FINAL SIGMA" + u:entity="sfgr" + string="\(ts" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="σ" + u:name="GREEK SMALL LETTER SIGMA" + u:entity="sgr" + string="\(*s" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="τ" + u:name="GREEK SMALL LETTER TAU" + u:entity="tau" + string="\(*t" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="υ" + u:name="GREEK SMALL LETTER UPSILON" + u:entity="ugr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="φ" + u:name="GREEK SMALL LETTER PHI" + u:entity="phgr" + string="\(*f" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="χ" + u:name="GREEK SMALL LETTER CHI" + u:entity="chi" + string="\(*x" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ψ" + u:name="GREEK SMALL LETTER PSI" + u:entity="psgr" + string="\(*q" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ω" + u:name="GREEK SMALL LETTER OMEGA" + u:entity="ohgr" + string="\(*w" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϊ" + u:name="GREEK SMALL LETTER IOTA WITH DIALYTIKA" + u:entity="idigr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϋ" + u:name="GREEK SMALL LETTER UPSILON WITH DIALYTIKA" + u:entity="udigr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ό" + u:name="GREEK SMALL LETTER OMICRON WITH TONOS" + u:entity="oacgr" + string="\(*o" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ύ" + u:name="GREEK SMALL LETTER UPSILON WITH TONOS" + u:entity="uacgr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ώ" + u:name="GREEK SMALL LETTER OMEGA WITH TONOS" + u:entity="ohacgr" + string="\(*w" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϐ" + u:name="GREEK BETA SYMBOL" + string="\(*B" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϑ" + u:name="GREEK THETA SYMBOL" + u:entity="thetav" + string="\(+h" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϒ" + u:name="GREEK UPSILON WITH HOOK SYMBOL" + u:entity="Upsi" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϓ" + u:name="GREEK UPSILON WITH ACUTE AND HOOK SYMBOL" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϔ" + u:name="GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϕ" + u:name="GREEK PHI SYMBOL" + u:entity="phis" + string="\(+f" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϖ" + u:name="GREEK PI SYMBOL" + u:entity="piv" + string="\(+p" + u:block="Greek and Coptic" + /> + <!-- no mappings for remaining chars x03d7 to x03ef --> + <xsl:output-character + character="ϰ" + u:name="GREEK KAPPA SYMBOL" + u:entity="kappav" + string="(*k" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϱ" + u:name="GREEK RHO SYMBOL" + u:entity="rhov" + string="\(*r" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϲ" + u:name="GREEK LUNATE SIGMA SYMBOL" + string="\(*s" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϴ" + u:name="GREEK CAPITAL THETA SYMBOL" + string="\(*H" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϵ" + u:name="GREEK LUNATE EPSILON SYMBOL" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="϶" + u:name="GREEK REVERSED LUNATE EPSILON SYMBOL" + u:entity="bepsi" + string="\(*e" + u:block="Greek and Coptic" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Greek and Coptic --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Cyrillic --> + <!-- * x0400 to x04ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: General Punctuation --> + <!-- * x2000 to x206f --> + <!-- * ***************************************************************** --> + + <!-- * first, spaces of various widths --> + + <!-- * Note: There does not seem to be either a real em space or en space --> + <!-- * in roff; to approximate them, this character map assumes that in --> + <!-- * most fonts, an en space is about the same as the width of a digit --> + <!-- * (in roff, "\0"), so an em space (which by definition is --> + <!-- * equal to the width of two en spaces) is about the same as the width --> + <!-- * of two digits (thus, in roff, "\0\0") --> + + <xsl:output-character + character=" " + u:name="EN QUAD" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EM QUAD" + string="\0\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EN SPACE" + u:entity="ensp" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EM SPACE" + u:entity="emsp" + string="\0\0" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="THREE-PER-EM SPACE" + u:entity="emsp13" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="FOUR-PER-EM SPACE" + u:entity="emsp14" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="SIX-PER-EM SPACE" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * same as roff "digit" space --> + <xsl:output-character + character=" " + u:name="FIGURE SPACE" + u:entity="numsp" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * punctuation space in most fonts is actually closer to a normal --> + <!-- * space than it is to a thin space --> + <xsl:output-character + character=" " + u:name="PUNCTUATION SPACE" + u:entity="puncsp" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * Note: Not sure how best to deal with thin space, because the roff --> + <!-- * thin space, "\^", prints as a zero-width space in TTY --> + <!-- * output. However, it seems that, unlike a hair space, a thin space, --> + <!-- * at 1/12 of an em, is still recognizable to most people as a space, --> + <!-- * so treating it as zero-width seems wrong. So, for the sake of making --> + <!-- * TTY output look OK, just substitute with a normal space; but real --> + <!-- * roff escape is "\(\^" --> + <xsl:output-character + character=" " + u:name="THIN SPACE" + u:entity="thinsp" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * I don't think there's a standard definition of what a hair --> + <!-- * space is; some guides just say it's "less than 1/5 of an em" or --> + <!-- * that it's "narrower than a thin space"; seems like in practice, --> + <!-- * it's *a lot* narrower than a thin space, to the point where --> + <!-- * it's close to being a non-space, so here it's substituted with --> + <!-- * roff equivalent of a zero-width no-break space --> + <xsl:output-character + character=" " + u:name="HAIR SPACE" + u:entity="hairsp" + string="\&" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * map to roff "zero-width break point" --> + <xsl:output-character + character="​" + u:name="ZERO WIDTH SPACE" + string="\:" + u:block="General Punctuation" + u:class="spaces" + /> + + <!-- * x200c and x200d have special purposes in some Indic languages (I --> + <!-- * think); for the "correct" zero-width space, according to Unicode docs, --> + <!-- * use x2060, not x200c or x200d --> + <!-- * <xsl:output-character --> + <!-- * character="‌" --> + <!-- * u:name="ZERO WIDTH NON-JOINER" --> + <!-- * string="\:" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‍" --> + <!-- * u:name="ZERO WIDTH JOINER" --> + <!-- * string="\&" --> + <!-- * /> --> + <!-- * non-visible --> + <!-- * <xsl:output-character --> + <!-- * character="‎" --> + <!-- * u:name="LEFT-TO-RIGHT MARK" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‏" --> + <!-- * u:name="RIGHT-TO-LEFT MARK" --> + <!-- * /> --> + + <!-- * .................................................... --> + <!-- * next, hyphens and various dashes, bars, underscores --> + <xsl:output-character + character="‐" + u:name="HYPHEN" + u:entity="hyphen" + string="\(hy" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * although the groff docs do not make it clear, testing --> + <!-- * indicates that the only reliable way to make a non-breaking --> + <!-- * hyphen is to put just a backslash in front of it. --> + <!-- * --> + <!-- * based on testing, it also appears that no character is needed --> + <!-- * after the hyphen in order to make it non-breaking --> + <xsl:output-character + character="‑" + u:name="NON-BREAKING HYPHEN" + string="\-" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * roughly same width as en dash --> + <xsl:output-character + character="‒" + u:name="FIGURE DASH" + string="\(en" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="–" + u:name="EN DASH" + u:entity="ndash" + string="\(en" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="—" + u:name="EM DASH" + u:entity="mdash" + string="\(em" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * seems roughly same width as em dash --> + <xsl:output-character + character="―" + u:name="HORIZONTAL BAR" + u:entity="horbar" + string="\(em" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="‖" + u:name="DOUBLE VERTICAL LINE" + u:entity="Verbar" + string="\(bv\(bv" + u:block="General Punctuation" + /> + <!-- * no double-underscore in roff; so just make it a single --> + <!-- * underscore --> + <xsl:output-character + character="‗" + u:name="DOUBLE LOW LINE" + string="_" + u:block="General Punctuation" + /> + + <!-- * .................................................... --> + <!-- * various quotation marks --> + <xsl:output-character + character="‘" + u:name="LEFT SINGLE QUOTATION MARK" + u:entity="lsquo" + string="\(oq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="’" + u:name="RIGHT SINGLE QUOTATION MARK" + u:entity="rsquo" + string="\(cq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="‚" + u:name="SINGLE LOW-9 QUOTATION MARK" + u:entity="lsquor" + string="\(bq" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * no roff equiv; treat same as lsquo --> + <xsl:output-character + character="‛" + u:name="SINGLE HIGH-REVERSED-9 QUOTATION MARK" + string="\(oq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="“" + u:name="LEFT DOUBLE QUOTATION MARK" + u:entity="ldquo" + string="\(lq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="”" + u:name="RIGHT DOUBLE QUOTATION MARK" + u:entity="rdquo" + string="\(rq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="„" + u:name="DOUBLE LOW-9 QUOTATION MARK" + u:entity="ldquor" + string="\(Bq" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * no roff equiv; treat same as rdquo --> + <xsl:output-character + character="‟" + u:name="DOUBLE HIGH-REVERSED-9 QUOTATION MARK" + string="\(rq" + u:block="General Punctuation" + u:class="quotes" + /> + + <!-- * .................................................... --> + <!-- * various symbols --> + <xsl:output-character + character="†" + u:name="DAGGER" + u:entity="dagger" + string="\(dg" + u:block="General Punctuation_daggers" + /> + <xsl:output-character + character="‡" + u:name="DOUBLE DAGGER" + u:entity="Dagger" + string="\(dd" + u:block="General Punctuation_daggers" + /> + <xsl:output-character + character="•" + u:name="BULLET" + u:entity="bull" + string="\(bu" + u:block="General Punctuation" + u:class="bullets" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="‣" + u:name="TRIANGULAR BULLET" + string=">\&" + u:block="General Punctuation" + u:class="bullets" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="․" + u:name="ONE DOT LEADER" + string="\&." + u:block="General Punctuation_leaders" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="‥" + u:name="TWO DOT LEADER" + u:entity="nldr" + string="\&.." + u:block="General Punctuation_leaders" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="…" + u:name="HORIZONTAL ELLIPSIS" + u:entity="hellip" + string="\&..." + u:block="General Punctuation" + /> + <!-- what is "hyphenation point" used for? looks like middot to me... --> + <xsl:output-character + character="‧" + u:name="HYPHENATION POINT" + string="\(md" + u:block="General Punctuation" + /> + <!-- * Begin x2028 to x202e - no idea what to do with these --> + <!-- * <xsl:output-character --> + <!-- * character="
" --> + <!-- * u:name="LINE SEPARATOR" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="
" --> + <!-- * u:name="PARAGRAPH SEPARATOR" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‪" --> + <!-- * u:name="LEFT-TO-RIGHT EMBEDDING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‫" --> + <!-- * u:name="RIGHT-TO-LEFT EMBEDDING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‬" --> + <!-- * u:name="POP DIRECTIONAL FORMATTING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‭" --> + <!-- * u:name="LEFT-TO-RIGHT OVERRIDE" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‮" --> + <!-- * u:name="RIGHT-TO-LEFT OVERRIDE" --> + <!-- * /> --> + <!-- * End x2028 to x202e - no idea what to do with these --> + + <!-- * seems like "narrow" nbsp is basically the same as a no-break --> + <!-- * space --> + <xsl:output-character + character=" " + u:name="NARROW NO-BREAK SPACE" + string="\ " + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character="‰" + u:name="PER MILLE SIGN" + u:entity="permil" + string="\(%0" + u:block="General Punctuation" + /> + <!-- * no roff equiv; no idea what to do with it --> + <!-- * <xsl:output-character --> + <!-- * character="‱" --> + <!-- * u:name="PER TEN THOUSAND SIGN" --> + <!-- * /> --> + <xsl:output-character + character="′" + u:name="PRIME" + u:entity="prime" + string="\(fm" + u:block="General Punctuation" + u:class="primes" + /> + <xsl:output-character + character="″" + u:name="DOUBLE PRIME" + u:entity="Prime" + string="\(sd" + u:block="General Punctuation" + u:class="primes" + /> + <xsl:output-character + character="‴" + u:name="TRIPLE PRIME" + u:entity="tprime" + string="\(sd\(fm" + u:block="General Punctuation" + u:class="primes" + /> + <!-- * no idea for these --> + <!-- * <xsl:output-character --> + <!-- * character="‵" --> + <!-- * u:name="REVERSED PRIME" --> + <!-- * u:entity="bprime" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‶" --> + <!-- * u:name="REVERSED DOUBLE PRIME" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‷" --> + <!-- * u:name="REVERSED TRIPLE PRIME" --> + <!-- * /> --> + + <!-- * there is no low caret in roff --> + <xsl:output-character + character="‸" + u:name="CARET" + string="^" + u:block="General Punctuation" + /> + <xsl:output-character + character="‹" + u:name="SINGLE LEFT-POINTING ANGLE QUOTATION MARK" + string="\(fo" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="›" + u:name="SINGLE RIGHT-POINTING ANGLE QUOTATION MARK" + string="\(fc" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * not in roff --> + <xsl:output-character + character="※" + u:name="REFERENCE MARK" + string="*" + u:block="General Punctuation" + /> + <xsl:output-character + character="‼" + u:name="DOUBLE EXCLAMATION MARK" + string="!!" + u:block="General Punctuation" + /> + <xsl:output-character + character="‽" + u:name="INTERROBANG" + string="?!" + u:block="General Punctuation" + /> + <xsl:output-character + character="‾" + u:name="OVERLINE" + string="\(rn" + u:block="General Punctuation" + /> + <xsl:output-character + character="‿" + u:name="UNDERTIE" + string="\(ul" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁀" + u:name="CHARACTER TIE" + string="\(rn" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁁" + u:name="CARET INSERTION POINT" + u:entity="caret" + string="^" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁂" + u:name="ASTERISM" + string="*" + u:block="General Punctuation" + /> + <!-- * not in roff; just make bold hyphen --> + <xsl:output-character + character="⁃" + u:name="HYPHEN BULLET" + u:entity="hybull" + string="\fB-\fR" + u:block="General Punctuation" + u:class="bullets" + /> + <xsl:output-character + character="⁄" + u:name="FRACTION SLASH" + string="\(sl" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁅" + u:name="LEFT SQUARE BRACKET WITH QUILL" + string="[" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁆" + u:name="RIGHT SQUARE BRACKET WITH QUILL" + string="]" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁇" + u:name="DOUBLE QUESTION MARK" + string="??" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁈" + u:name="QUESTION EXCLAMATION MARK" + string="?!" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁉" + u:name="EXCLAMATION QUESTION MARK" + string="!?" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁊" + u:name="TIRONIAN SIGN ET" + string="7" + u:block="General Punctuation" + /> + <!-- * not in roff; just replace with un-reversed pilcrow --> + <xsl:output-character + character="⁋" + u:name="REVERSED PILCROW SIGN" + string="\(ps" + u:block="General Punctuation" + /> + <!-- * not in roff; just make regular bullet --> + <xsl:output-character + character="⁌" + u:name="BLACK LEFTWARDS BULLET" + string="\(bu" + u:block="General Punctuation" + /> + <!-- * not in roff; just make regular bullet --> + <xsl:output-character + character="⁍" + u:name="BLACK RIGHTWARDS BULLET" + string="\(bu" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁎" + u:name="LOW ASTERISK" + string="*" + u:block="General Punctuation" + /> + + <!-- * ............................................................... --> + <!-- * Remaining General Punctuation --> + <!-- * from x2050 to x206f --> + <!-- * only map a couple of these --> + <!-- * ............................................................... --> + + <!-- * basically same as a normal space --> + <xsl:output-character + character=" " + u:name="MEDIUM MATHEMATICAL SPACE" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * Regarding x2060 vs. xFEFF, the document "Unicode Standard Annex #14, --> + <!-- * Line Breaking Properties"[1] says: --> + <!-- * --> + <!-- * The word joiner character [x2060 a.k.a "WJ"] is the preferred --> + <!-- * choice for an invisible character to keep other characters --> + <!-- * together that would otherwise be split across the line at a direct --> + <!-- * break. The character FEFF has the same effect, but because it is --> + <!-- * also used in an unrelated way as a byte order mark, the use of the --> + <!-- * WJ as the preferred interword glue simplifies the handling of FEFF. --> + <!-- * --> + <!-- * [1] http://www.unicode.org/reports/tr14/ --> + <!-- * --> + <!-- * The groff docs seem ambiguous about whether \& is a joiner and --> + <!-- * prevents breaks, but, based on testing, seems like it does --> + <xsl:output-character + character="⁠" + u:name="WORD JOINER" + string="\&" + u:block="General Punctuation" + /> + + <!-- * ***************************************************************** --> + <!-- * End: General Punctuation --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Superscripts and Subscripts --> + <!-- * x2070 to x209f --> + <!-- * For superscripts, just do a^n thing --> + <!-- * For subscripts, just do a_n --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="⁰" + u:name="SUPERSCRIPT ZERO" + string="^0" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="ⁱ" + u:name="SUPERSCRIPT LATIN SMALL LETTER I" + string="^i" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁴" + u:name="SUPERSCRIPT FOUR" + string="^4" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁵" + u:name="SUPERSCRIPT FIVE" + string="^5" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁶" + u:name="SUPERSCRIPT SIX" + string="^6" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁷" + u:name="SUPERSCRIPT SEVEN" + string="^7" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁸" + u:name="SUPERSCRIPT EIGHT" + string="^8" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁹" + u:name="SUPERSCRIPT NINE" + string="^9" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁺" + u:name="SUPERSCRIPT PLUS SIGN" + string="^+" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁻" + u:name="SUPERSCRIPT MINUS" + string="^-" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁼" + u:name="SUPERSCRIPT EQUALS SIGN" + string="^=" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁽" + u:name="SUPERSCRIPT LEFT PARENTHESIS" + string="^(" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁾" + u:name="SUPERSCRIPT RIGHT PARENTHESIS" + string="^)" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="ⁿ" + u:name="SUPERSCRIPT LATIN SMALL LETTER N" + string="^n" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₀" + u:name="SUBSCRIPT ZERO" + string="_0" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₁" + u:name="SUBSCRIPT ONE" + string="_1" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₂" + u:name="SUBSCRIPT TWO" + string="_2" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₃" + u:name="SUBSCRIPT THREE" + string="_3" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₄" + u:name="SUBSCRIPT FOUR" + string="_4" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₅" + u:name="SUBSCRIPT FIVE" + string="_5" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₆" + u:name="SUBSCRIPT SIX" + string="_6" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₇" + u:name="SUBSCRIPT SEVEN" + string="_7" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₈" + u:name="SUBSCRIPT EIGHT" + string="_8" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₉" + u:name="SUBSCRIPT NINE" + string="_9" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₊" + u:name="SUBSCRIPT PLUS SIGN" + string="_+" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₋" + u:name="SUBSCRIPT MINUS" + string="_-" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₌" + u:name="SUBSCRIPT EQUALS SIGN" + string="_=" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₍" + u:name="SUBSCRIPT LEFT PARENTHESIS" + string="_(" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₎" + u:name="SUBSCRIPT RIGHT PARENTHESIS" + string="_)" + u:block="Superscripts and Subscripts" + /> + <!-- * ***************************************************************** --> + <!-- * End: Superscripts and Subscripts --> + <!-- * x2070 to x209f --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Currency Symbols --> + <!-- * x20a0 to x20b1 --> + <!-- * No mappings for any of these; just spell out --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="₠" + u:name="EURO-CURRENCY SIGN" + string="EUR" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₡" + u:name="COLON SIGN" + string="COLON" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₢" + u:name="CRUZEIRO SIGN" + string="CRUZEIRO" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₣" + u:name="FRENCH FRANC SIGN" + string="FRANC" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₤" + u:name="LIRA SIGN" + string="LIRA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₥" + u:name="MILL SIGN" + string="MILL" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₦" + u:name="NAIRA SIGN" + string="NAIRA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₧" + u:name="PESETA SIGN" + string="PESETA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₨" + u:name="RUPEE SIGN" + string="RUPEE" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₩" + u:name="WON SIGN" + string="WON" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₪" + u:name="NEW SHEQEL SIGN" + string="SHEQEL" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₫" + u:name="DONG SIGN" + string="DONG" + u:block="Currency Symbols" + /> + <xsl:output-character + character="€" + u:name="EURO SIGN" + string="EUR" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₭" + u:name="KIP SIGN" + string="KIP" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₮" + u:name="TUGRIK SIGN" + string="TUGRIK" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₯" + u:name="DRACHMA SIGN" + string="DRACHMA" + u:block="Currency Symbols" + /> + <!-- <xsl:output-character --> + <!-- character="₰" --> + <!-- u:name="GERMAN PENNY SIGN" --> + <!-- string="?" --> + <!-- u:block="Currency Symbols" --> + <!-- /> --> + <xsl:output-character + character="₱" + u:name="PESO SIGN" + string="PESO" + u:block="Currency Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Currency Symbols --> + <!-- * x20a0 to x20b1 --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Combining Diacritical Marks for Symbols --> + <!-- * x20d0 to x20ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Letterlike Symbols --> + <!-- * x2100 to x214b --> + <!-- * --> + <!-- * No mappings for any of these, and nothing appropriate for --> + <!-- * most of them; so, just spell out the ones that we can --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="℃" + u:name="DEGREE CELSIUS" + string="\(deC" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℅" + u:name="CARE OF" + u:entity="incare" + string="c/o" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℉" + u:name="DEGREE FAHRENHEIT" + string="\(deF" + u:block="Letterlike Symbols" + /> + <!-- roff Ifraktur --> + <xsl:output-character + character="ℑ" + u:name="BLACK-LETTER CAPITAL I" + string="\(Im" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℓ" + u:name="SCRIPT SMALL L" + u:entity="ell" + string="l" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="№" + u:name="NUMERO SIGN" + u:entity="numero" + string="No." + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℘" + u:name="SCRIPT CAPITAL P" + u:entity="weierp" + string="\(wp" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℜ" + u:name="BLACK-LETTER CAPITAL R" + u:entity="real" + string="\(Re" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℞" + u:name="PRESCRIPTION TAKE" + u:entity="rx" + string="Rx" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℠" + u:name="SERVICE MARK" + string="(SM)" + u:block="Letterlike Symbols" + /> + <!-- * We don't do "\(tm" for ™ because for console output, groff --> + <!-- * just renders that as "tm", without any preceding space, parens, --> + <!-- * or anything. So it just gets run into the preceding word; i.e.: --> + <!-- * --> + <!-- * Product™ -> Producttm --> + <!-- * --> + <!-- * That it probably not what most people would want. So we just --> + <!-- * render it as (TM) instead, Thus: --> + <!-- * --> + <!-- * Product™ -> Product(TM) --> + <xsl:output-character + character="™" + u:name="TRADE MARK SIGN" + u:entity="trade" + string="(TM)" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="Ω" + u:name="OHM SIGN" + u:entity="ohm" + string="\(*W" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="K" + u:name="KELVIN SIGN" + string="K" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="Å" + u:name="ANGSTROM SIGN" + u:entity="angst" + string="\(oA" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℵ" + u:name="ALEF SYMBOL" + u:entity="aleph" + string="\(Ah" + u:block="Letterlike Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Letterlike Symbols --> + <!-- * x2100 to x214b --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Number Forms --> + <!-- * x2150 to x218f --> + <!-- * --> + <!-- * No mappings for any of these, and nothing appropriate for most --> + <!-- * of them; so, just spell out the ones that we can --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="⅓" + u:name="VULGAR FRACTION ONE THIRD" + u:entity="frac13" + string="1/3" + u:block="Number Forms" + /> + <xsl:output-character + character="⅔" + u:name="VULGAR FRACTION TWO THIRDS" + u:entity="frac23" + string="2/3" + u:block="Number Forms" + /> + <xsl:output-character + character="⅕" + u:name="VULGAR FRACTION ONE FIFTH" + u:entity="frac15" + string="1/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅖" + u:name="VULGAR FRACTION TWO FIFTHS" + u:entity="frac25" + string="2/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅗" + u:name="VULGAR FRACTION THREE FIFTHS" + u:entity="frac35" + string="3/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅘" + u:name="VULGAR FRACTION FOUR FIFTHS" + u:entity="frac45" + string="4/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅙" + u:name="VULGAR FRACTION ONE SIXTH" + u:entity="frac16" + string="1/6" + u:block="Number Forms" + /> + <xsl:output-character + character="⅚" + u:name="VULGAR FRACTION FIVE SIXTHS" + u:entity="frac56" + string="5/6" + u:block="Number Forms" + /> + <xsl:output-character + character="⅛" + u:name="VULGAR FRACTION ONE EIGHTH" + u:entity="frac18" + string="1/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅜" + u:name="VULGAR FRACTION THREE EIGHTHS" + u:entity="frac38" + string="3/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅝" + u:name="VULGAR FRACTION FIVE EIGHTHS" + u:entity="frac58" + string="5/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅞" + u:name="VULGAR FRACTION SEVEN EIGHTHS" + u:entity="frac78" + string="7/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅟" + u:name="FRACTION NUMERATOR ONE" + string="1/" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅰ" + u:name="ROMAN NUMERAL ONE" + string="I" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅱ" + u:name="ROMAN NUMERAL TWO" + string="II" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅲ" + u:name="ROMAN NUMERAL THREE" + string="III" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅳ" + u:name="ROMAN NUMERAL FOUR" + string="IV" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅴ" + u:name="ROMAN NUMERAL FIVE" + string="V" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅵ" + u:name="ROMAN NUMERAL SIX" + string="VI" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅶ" + u:name="ROMAN NUMERAL SEVEN" + string="VII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅷ" + u:name="ROMAN NUMERAL EIGHT" + string="VIII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅸ" + u:name="ROMAN NUMERAL NINE" + string="IX" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅹ" + u:name="ROMAN NUMERAL TEN" + string="X" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅺ" + u:name="ROMAN NUMERAL ELEVEN" + string="XI" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅻ" + u:name="ROMAN NUMERAL TWELVE" + string="XII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅼ" + u:name="ROMAN NUMERAL FIFTY" + string="L" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅽ" + u:name="ROMAN NUMERAL ONE HUNDRED" + string="C" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅾ" + u:name="ROMAN NUMERAL FIVE HUNDRED" + string="D" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅿ" + u:name="ROMAN NUMERAL ONE THOUSAND" + string="M" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅰ" + u:name="SMALL ROMAN NUMERAL ONE" + string="i" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅱ" + u:name="SMALL ROMAN NUMERAL TWO" + string="ii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅲ" + u:name="SMALL ROMAN NUMERAL THREE" + string="iii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅳ" + u:name="SMALL ROMAN NUMERAL FOUR" + string="iv" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅴ" + u:name="SMALL ROMAN NUMERAL FIVE" + string="v" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅵ" + u:name="SMALL ROMAN NUMERAL SIX" + string="vi" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅶ" + u:name="SMALL ROMAN NUMERAL SEVEN" + string="vii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅷ" + u:name="SMALL ROMAN NUMERAL EIGHT" + string="viii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅸ" + u:name="SMALL ROMAN NUMERAL NINE" + string="ix" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅹ" + u:name="SMALL ROMAN NUMERAL TEN" + string="x" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅺ" + u:name="SMALL ROMAN NUMERAL ELEVEN" + string="xi" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅻ" + u:name="SMALL ROMAN NUMERAL TWELVE" + string="xii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅼ" + u:name="SMALL ROMAN NUMERAL FIFTY" + string="l" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅽ" + u:name="SMALL ROMAN NUMERAL ONE HUNDRED" + string="c" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅾ" + u:name="SMALL ROMAN NUMERAL FIVE HUNDRED" + string="d" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅿ" + u:name="SMALL ROMAN NUMERAL ONE THOUSAND" + string="m" + u:block="Number Forms" + /> + <xsl:output-character + character="ↀ" + u:name="ROMAN NUMERAL ONE THOUSAND C D" + string="CD" + u:block="Number Forms" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Number Forms --> + <!-- * x2150 to x218f --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Arrows --> + <!-- * x2190 to x21ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="←" + u:name="LEFTWARDS ARROW" + u:entity="larr" + string="\(<-" + u:block="Arrows" + /> + <xsl:output-character + character="↑" + u:name="UPWARDS ARROW" + u:entity="uarr" + string="\(ua" + u:block="Arrows" + /> + <xsl:output-character + character="→" + u:name="RIGHTWARDS ARROW" + u:entity="rarr" + string="\(->" + u:block="Arrows" + /> + <xsl:output-character + character="↓" + u:name="DOWNWARDS ARROW" + u:entity="darr" + string="\(da" + u:block="Arrows" + /> + <xsl:output-character + character="↔" + u:name="LEFT RIGHT ARROW" + u:entity="harr" + string="\(<>" + u:block="Arrows" + /> + <xsl:output-character + character="↕" + u:name="UP DOWN ARROW" + u:entity="varr" + string="\(va" + u:block="Arrows" + /> + <xsl:output-character + character="↵" + u:name="DOWNWARDS ARROW WITH CORNER LEFTWARDS" + u:entity="crarr" + string="\(CR" + u:block="Arrows" + /> + <xsl:output-character + character="⇐" + u:name="LEFTWARDS DOUBLE ARROW" + u:entity="lArr" + string="\(la" + u:block="Arrows" + /> + <xsl:output-character + character="⇑" + u:name="UPWARDS DOUBLE ARROW" + u:entity="uArr" + string="\(uA" + u:block="Arrows" + /> + <xsl:output-character + character="⇒" + u:name="RIGHTWARDS DOUBLE ARROW" + u:entity="rArr" + string="\(rA" + u:block="Arrows" + /> + <xsl:output-character + character="⇓" + u:name="DOWNWARDS DOUBLE ARROW" + u:entity="dArr" + string="\(dA" + u:block="Arrows" + /> + <xsl:output-character + character="⇔" + u:name="LEFT RIGHT DOUBLE ARROW" + u:entity="hArr" + string="\(hA" + u:block="Arrows" + /> + <!-- no roff equiv; render same as single arrow --> + <xsl:output-character + character="⇕" + u:name="UP DOWN DOUBLE ARROW" + u:entity="vArr" + string="\(va" + u:block="Arrows" + /> + + <!-- * ***************************************************************** --> + <!-- * Begin: Mathematical Operators --> + <!-- * x2200 to x22ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="∀" + u:name="FOR ALL" + u:entity="forall" + string="\(fa" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∁" + u:name="COMPLEMENT" + u:entity="comp" + string="C" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∂" + u:name="PARTIAL DIFFERENTIAL" + u:entity="part" + string="\(pd" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∃" + u:name="THERE EXISTS" + u:entity="exist" + string="\(te" + u:block="Mathematical Operators" + /> + <!-- * no roff equiv --> + <!-- * <xsl:output-character --> + <!-- * character="∄" --> + <!-- * u:name="THERE DOES NOT EXIST" --> + <!-- * u:entity="nexist" --> + <!-- * /> --> + <xsl:output-character + character="∅" + u:name="EMPTY SET" + u:entity="empty" + string="\(es" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∆" + u:name="INCREMENT" + string="\(*D" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∇" + u:name="NABLA" + u:entity="nabla" + string="\(gr" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∈" + u:name="ELEMENT OF" + u:entity="isin" + string="\(mo" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∉" + u:name="NOT AN ELEMENT OF" + u:entity="notin" + string="\(nm" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∊" + u:name="SMALL ELEMENT OF" + string="\(mo" + u:block="Mathematical Operators" + /> + + <xsl:output-character + character="∋" + u:name="CONTAINS AS MEMBER" + u:entity="ni" + string="\(st" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∌" --> + <!-- * u:name="DOES NOT CONTAIN AS MEMBER" --> + <!-- * /> --> + <xsl:output-character + character="∍" + u:name="SMALL CONTAINS AS MEMBER" + string="\(st" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∎" --> + <!-- * u:name="END OF PROOF" --> + <!-- * /> --> + <xsl:output-character + character="∏" + u:name="N-ARY PRODUCT" + u:entity="prod" + string="\(product" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∐" --> + <!-- * u:name="N-ARY COPRODUCT" --> + <!-- * u:entity="coprod" --> + <!-- * /> --> + <xsl:output-character + character="∑" + u:name="N-ARY SUMMATION" + u:entity="sum" + string="\(sum" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="−" + u:name="MINUS SIGN" + u:entity="minus" + string="\-" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∓" + u:name="MINUS-OR-PLUS SIGN" + u:entity="mnplus" + string="\(+-" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∔" --> + <!-- * u:name="DOT PLUS" --> + <!-- * u:entity="plusdo" --> + <!-- * /> --> + <xsl:output-character + character="∕" + u:name="DIVISION SLASH" + string="\(f/" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∖" + u:name="SET MINUS" + u:entity="setmn" + string="\e" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∗" + u:name="ASTERISK OPERATOR" + u:entity="lowast" + string="\(**" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∘" --> + <!-- * u:name="RING OPERATOR" --> + <!-- * u:entity="compfn" --> + <!-- * /> --> + <xsl:output-character + character="∙" + u:name="BULLET OPERATOR" + string="\(bu" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="√" + u:name="SQUARE ROOT" + u:entity="radic" + string="\(sr" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∛" --> + <!-- * u:name="CUBE ROOT" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="∜" --> + <!-- * u:name="FOURTH ROOT" --> + <!-- * /> --> + <xsl:output-character + character="∝" + u:name="PROPORTIONAL TO" + u:entity="prop" + string="\(pt" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∞" + u:name="INFINITY" + u:entity="infin" + string="\(if" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∟" --> + <!-- * u:name="RIGHT ANGLE" --> + <!-- * u:entity="ang90" --> + <!-- * /> --> + <xsl:output-character + character="∠" + u:name="ANGLE" + u:entity="ang" + string="\(/_" + u:block="Mathematical Operators" + /> + + <!-- * 0x2221 to 0x2226 not in roff; --> + <!-- * but fake a parallel sign with vert bars --> + + <xsl:output-character + character="∥" + u:name="PARALLEL TO" + u:entity="par" + string="\(bv\(bv" + u:block="Mathematical Operators" + /> + + <xsl:output-character + character="∧" + u:name="LOGICAL AND" + u:entity="and" + string="\(AN" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∨" + u:name="LOGICAL OR" + u:entity="or" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∩" + u:name="INTERSECTION" + u:entity="cap" + string="\(ca" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∪" + u:name="UNION" + u:entity="cup" + string="\(cu" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∫" + u:name="INTEGRAL" + u:entity="int" + string="\(is" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∬" + u:name="DOUBLE INTEGRAL" + string="\(is\(is" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∭" + u:name="TRIPLE INTEGRAL" + string="\(is\(is\(is" + u:block="Mathematical Operators" + /> + + <!-- * 0x222e to 0x2233 not in roff --> + + <xsl:output-character + character="∴" + u:name="THEREFORE" + u:entity="there4" + string="\(tf" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∵" --> + <!-- * u:name="BECAUSE" --> + <!-- * u:entity="becaus" --> + <!-- * /> --> + <xsl:output-character + character="∶" + u:name="RATIO" + string=":" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∷" + u:name="PROPORTION" + string="::" + u:block="Mathematical Operators" + /> + + <!-- * 0x2238 to 0x223b not in roff --> + + <xsl:output-character + character="∼" + u:name="TILDE OPERATOR" + u:entity="sim" + string="\(ti" + u:block="Mathematical Operators" + /> + + <!-- * 0x223d to 0x224b not in roff --> + + <xsl:output-character + character="≅" + u:name="APPROXIMATELY EQUAL TO" + u:entity="cong" + string="\(=~" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="≆" --> + <!-- * u:name="APPROXIMATELY BUT NOT ACTUALLY EQUAL TO" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="≇" --> + <!-- * u:name="NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO" --> + <!-- * u:entity="ncong" --> + <!-- * /> --> + + <xsl:output-character + character="≈" + u:name="ALMOST EQUAL TO" + u:entity="asymp" + string="\(~~" + u:block="Mathematical Operators" + /> + + <!-- * x2249 to x2253 not in roff --> + + <xsl:output-character + character="≔" + u:name="COLON EQUALS" + u:entity="colone" + string=":=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≕" + u:name="EQUALS COLON" + u:entity="ecolon" + string="=:" + u:block="Mathematical Operators" + /> + + <!-- * x2256 to x2255 not in roff --> + + <xsl:output-character + character="≟" + u:name="QUESTIONED EQUAL TO" + string="?=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≠" + u:name="NOT EQUAL TO" + u:entity="ne" + string="\(!=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≡" + u:name="IDENTICAL TO" + u:entity="equiv" + string="\(==" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="≢" --> + <!-- * u:name="NOT IDENTICAL TO" --> + <!-- * u:entity="nequiv" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="≣" --> + <!-- * u:name="STRICTLY EQUIVALENT TO" --> + <!-- * /> --> + <xsl:output-character + character="≤" + u:name="LESS-THAN OR EQUAL TO" + u:entity="le" + string="\(<=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≥" + u:name="GREATER-THAN OR EQUAL TO" + u:entity="ge" + string="\(>=" + u:block="Mathematical Operators" + /> + <!-- * x2266 to x2269 not in roff --> + + <xsl:output-character + character="≪" + u:name="MUCH LESS-THAN" + u:entity="Lt" + string="<<" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≫" + u:name="MUCH GREATER-THAN" + u:entity="Gt" + string=">>" + u:block="Mathematical Operators" + /> + <!-- * x226c to x2281 not in roff --> + + <xsl:output-character + character="⊂" + u:name="SUBSET OF" + u:entity="sub" + string="\(sb" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊃" + u:name="SUPERSET OF" + u:entity="sup" + string="\(sp" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊄" + u:name="NOT A SUBSET OF" + u:entity="nsub" + string="\(nb" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="⊅" --> + <!-- * u:name="NOT A SUPERSET OF" --> + <!-- * u:entity="nsup" --> + <!-- * /> --> + <xsl:output-character + character="⊆" + u:name="SUBSET OF OR EQUAL TO" + u:entity="sube" + string="\(ib" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊇" + u:name="SUPERSET OF OR EQUAL TO" + u:entity="supe" + string="\(ip" + u:block="Mathematical Operators" + /> + <!-- * x2288 to x2294 not in roff --> + + <xsl:output-character + character="⊕" + u:name="CIRCLED PLUS" + u:entity="oplus" + string="\(c+" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊖" + u:name="CIRCLED MINUS" + u:entity="ominus" + string="\(c*" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊗" + u:name="CIRCLED TIMES" + u:entity="otimes" + string="\(c*" + u:block="Mathematical Operators" + /> + + <!-- * x2298 to x22a4 not in roff --> + + <xsl:output-character + character="⊥" + u:name="UP TACK" + u:entity="bottom" + string="\(pp" + u:block="Mathematical Operators" + /> + + <!-- * x22a6 to x22bf not in roff --> + + <xsl:output-character + character="⋀" + u:name="N-ARY LOGICAL AND" + string="\(AN" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋁" + u:name="N-ARY LOGICAL OR" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋂" + u:name="N-ARY INTERSECTION" + string="\(ca" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋃" + u:name="N-ARY UNION" + string="\(cu" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="⋄" --> + <!-- * u:name="DIAMOND OPERATOR" --> + <!-- * u:entity="diam" --> + <!-- * /> --> + <xsl:output-character + character="⋅" + u:name="DOT OPERATOR" + u:entity="sdot" + string="\(md" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋆" + u:name="STAR OPERATOR" + u:entity="sstarf" + string="\(**" + u:block="Mathematical Operators" + /> + <!-- * x22c7 to x22cd not in roff --> + + <xsl:output-character + character="⋎" + u:name="CURLY LOGICAL OR" + u:entity="cuvee" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋏" + u:name="CURLY LOGICAL AND" + u:entity="cuwed" + string="\(AN" + u:block="Mathematical Operators" + /> + + <!-- * x22d0 to x22d7 not in roff --> + + <xsl:output-character + character="⋘" + u:name="VERY MUCH LESS-THAN" + u:entity="Ll" + string="<<<" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋙" + u:name="VERY MUCH GREATER-THAN" + u:entity="Gg" + string=">>>" + u:block="Mathematical Operators" + /> + + <!-- * x22da to x22ee not in roff --> + + <xsl:output-character + character="⋯" + u:name="MIDLINE HORIZONTAL ELLIPSIS" + string="\&..." + u:block="Mathematical Operators" + /> + + <!-- * x22fo to x22ff not in roff --> + + <!-- * ***************************************************************** --> + <!-- * End: Mathematical Operators --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Miscellaneous Technical --> + <!-- * x2300 to x23ff --> + <!-- * - do nothing except for angle brackets - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <xsl:output-character + character="〈" + u:name="LEFT-POINTING ANGLE BRACKET" + u:entity="lang" + string="\(la" + u:block="Miscellaneous Technical" + /> + <xsl:output-character + character="〉" + u:name="RIGHT-POINTING ANGLE BRACKET" + u:entity="rang" + string="\(ra" + u:block="Miscellaneous Technical" + /> + + <!-- * ***************************************************************** --> + <!-- * Begin: Control Pictures --> + <!-- * x2400 to x243f --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="␀" + u:name="SYMBOL FOR NULL" + string="NUL" + u:block="Control Pictures" + /> + <xsl:output-character + character="␁" + u:name="SYMBOL FOR START OF HEADING" + string="SOH" + u:block="Control Pictures" + /> + <xsl:output-character + character="␂" + u:name="SYMBOL FOR START OF TEXT" + string="STX" + u:block="Control Pictures" + /> + <xsl:output-character + character="␃" + u:name="SYMBOL FOR END OF TEXT" + string="ETX" + u:block="Control Pictures" + /> + <xsl:output-character + character="␄" + u:name="SYMBOL FOR END OF TRANSMISSION" + string="EOT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␅" + u:name="SYMBOL FOR ENQUIRY" + string="ENQ" + u:block="Control Pictures" + /> + <xsl:output-character + character="␆" + u:name="SYMBOL FOR ACKNOWLEDGE" + string="ACK" + u:block="Control Pictures" + /> + <xsl:output-character + character="␇" + u:name="SYMBOL FOR BELL" + string="BEL" + u:block="Control Pictures" + /> + <xsl:output-character + character="␈" + u:name="SYMBOL FOR BACKSPACE" + string="BS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␉" + u:name="SYMBOL FOR HORIZONTAL TABULATION" + string="HT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␊" + u:name="SYMBOL FOR LINE FEED" + string="LF" + u:block="Control Pictures" + /> + <xsl:output-character + character="␋" + u:name="SYMBOL FOR VERTICAL TABULATION" + string="VT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␌" + u:name="SYMBOL FOR FORM FEED" + string="FF" + u:block="Control Pictures" + /> + <xsl:output-character + character="␍" + u:name="SYMBOL FOR CARRIAGE RETURN" + string="CR" + u:block="Control Pictures" + /> + <xsl:output-character + character="␎" + u:name="SYMBOL FOR SHIFT OUT" + string="SO" + u:block="Control Pictures" + /> + <xsl:output-character + character="␏" + u:name="SYMBOL FOR SHIFT IN" + string="SI" + u:block="Control Pictures" + /> + <xsl:output-character + character="␐" + u:name="SYMBOL FOR DATA LINK ESCAPE" + string="DLE" + u:block="Control Pictures" + /> + <xsl:output-character + character="␑" + u:name="SYMBOL FOR DEVICE CONTROL ONE" + string="DC1" + u:block="Control Pictures" + /> + <xsl:output-character + character="␒" + u:name="SYMBOL FOR DEVICE CONTROL TWO" + string="DC2" + u:block="Control Pictures" + /> + <xsl:output-character + character="␓" + u:name="SYMBOL FOR DEVICE CONTROL THREE" + string="DC3" + u:block="Control Pictures" + /> + <xsl:output-character + character="␔" + u:name="SYMBOL FOR DEVICE CONTROL FOUR" + string="DC4" + u:block="Control Pictures" + /> + <xsl:output-character + character="␕" + u:name="SYMBOL FOR NEGATIVE ACKNOWLEDGE" + string="NAK" + u:block="Control Pictures" + /> + <xsl:output-character + character="␖" + u:name="SYMBOL FOR SYNCHRONOUS IDLE" + string="SYN" + u:block="Control Pictures" + /> + <xsl:output-character + character="␗" + u:name="SYMBOL FOR END OF TRANSMISSION BLOCK" + string="ETB" + u:block="Control Pictures" + /> + <xsl:output-character + character="␘" + u:name="SYMBOL FOR CANCEL" + string="CAN" + u:block="Control Pictures" + /> + <xsl:output-character + character="␙" + u:name="SYMBOL FOR END OF MEDIUM" + string="EM" + u:block="Control Pictures" + /> + <xsl:output-character + character="␚" + u:name="SYMBOL FOR SUBSTITUTE" + string="SUB" + u:block="Control Pictures" + /> + <xsl:output-character + character="␛" + u:name="SYMBOL FOR ESCAPE" + string="ESC" + u:block="Control Pictures" + /> + <xsl:output-character + character="␜" + u:name="SYMBOL FOR FILE SEPARATOR" + string="FS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␝" + u:name="SYMBOL FOR GROUP SEPARATOR" + string="GS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␞" + u:name="SYMBOL FOR RECORD SEPARATOR" + string="RS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␟" + u:name="SYMBOL FOR UNIT SEPARATOR" + string="US" + u:block="Control Pictures" + /> + <xsl:output-character + character="␠" + u:name="SYMBOL FOR SPACE" + string="SP" + u:block="Control Pictures" + /> + <xsl:output-character + character="␡" + u:name="SYMBOL FOR DELETE" + string="DEL" + u:block="Control Pictures" + /> + <!-- * no roff equivs for x2422 and x2423 --> + <!-- * <xsl:output-character --> + <!-- * character="␢" --> + <!-- * u:name="BLANK SYMBOL" --> + <!-- * string="?" --> + <!-- * u:block="Control Pictures" --> + <!-- * /> --> + <!-- * I think there should be a roff equiv for ␣, but as far as I --> + <!-- * know, there is not... --> + <!-- * <xsl:output-character --> + <!-- * character="␣" --> + <!-- * u:name="OPEN BOX" --> + <!-- * u:entity="blank" --> + <!-- * string="?" --> + <!-- * u:block="Control Pictures" --> + <!-- * /> --> + <xsl:output-character + character="␤" + u:name="SYMBOL FOR NEWLINE" + string="NL" + u:block="Control Pictures" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Control Pictures --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Optical Character Recognition --> + <!-- * x2440 to x24ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Enclosed Alphanumerics --> + <!-- * x2460 to x24ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="①" + u:name="CIRCLED DIGIT ONE" + string="1" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="②" + u:name="CIRCLED DIGIT TWO" + string="2" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="③" + u:name="CIRCLED DIGIT THREE" + string="3" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="④" + u:name="CIRCLED DIGIT FOUR" + string="4" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑤" + u:name="CIRCLED DIGIT FIVE" + string="5" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑥" + u:name="CIRCLED DIGIT SIX" + string="6" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑦" + u:name="CIRCLED DIGIT SEVEN" + string="7" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑧" + u:name="CIRCLED DIGIT EIGHT" + string="8" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑨" + u:name="CIRCLED DIGIT NINE" + string="9" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑩" + u:name="CIRCLED NUMBER TEN" + string="10" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑪" + u:name="CIRCLED NUMBER ELEVEN" + string="11" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑫" + u:name="CIRCLED NUMBER TWELVE" + string="12" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑬" + u:name="CIRCLED NUMBER THIRTEEN" + string="13" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑭" + u:name="CIRCLED NUMBER FOURTEEN" + string="14" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑮" + u:name="CIRCLED NUMBER FIFTEEN" + string="15" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑯" + u:name="CIRCLED NUMBER SIXTEEN" + string="16" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑰" + u:name="CIRCLED NUMBER SEVENTEEN" + string="17" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑱" + u:name="CIRCLED NUMBER EIGHTEEN" + string="18" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑲" + u:name="CIRCLED NUMBER NINETEEN" + string="19" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑳" + u:name="CIRCLED NUMBER TWENTY" + string="20" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑴" + u:name="PARENTHESIZED DIGIT ONE" + string="(1)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑵" + u:name="PARENTHESIZED DIGIT TWO" + string="(2)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑶" + u:name="PARENTHESIZED DIGIT THREE" + string="(3)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑷" + u:name="PARENTHESIZED DIGIT FOUR" + string="(4)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑸" + u:name="PARENTHESIZED DIGIT FIVE" + string="(5)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑹" + u:name="PARENTHESIZED DIGIT SIX" + string="(6)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑺" + u:name="PARENTHESIZED DIGIT SEVEN" + string="(7)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑻" + u:name="PARENTHESIZED DIGIT EIGHT" + string="(8)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑼" + u:name="PARENTHESIZED DIGIT NINE" + string="(9)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑽" + u:name="PARENTHESIZED NUMBER TEN" + string="(10)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑾" + u:name="PARENTHESIZED NUMBER ELEVEN" + string="(11)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑿" + u:name="PARENTHESIZED NUMBER TWELVE" + string="(12)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒀" + u:name="PARENTHESIZED NUMBER THIRTEEN" + string="(13)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒁" + u:name="PARENTHESIZED NUMBER FOURTEEN" + string="(14)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒂" + u:name="PARENTHESIZED NUMBER FIFTEEN" + string="(15)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒃" + u:name="PARENTHESIZED NUMBER SIXTEEN" + string="(16)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒄" + u:name="PARENTHESIZED NUMBER SEVENTEEN" + string="(17)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒅" + u:name="PARENTHESIZED NUMBER EIGHTEEN" + string="(18)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒆" + u:name="PARENTHESIZED NUMBER NINETEEN" + string="(19)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒇" + u:name="PARENTHESIZED NUMBER TWENTY" + string="(20)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒈" + u:name="DIGIT ONE FULL STOP" + string="1." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒉" + u:name="DIGIT TWO FULL STOP" + string="2." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒊" + u:name="DIGIT THREE FULL STOP" + string="3." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒋" + u:name="DIGIT FOUR FULL STOP" + string="4." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒌" + u:name="DIGIT FIVE FULL STOP" + string="5." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒍" + u:name="DIGIT SIX FULL STOP" + string="6." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒎" + u:name="DIGIT SEVEN FULL STOP" + string="7." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒏" + u:name="DIGIT EIGHT FULL STOP" + string="8." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒐" + u:name="DIGIT NINE FULL STOP" + string="9." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒑" + u:name="NUMBER TEN FULL STOP" + string="10." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒒" + u:name="NUMBER ELEVEN FULL STOP" + string="11." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒓" + u:name="NUMBER TWELVE FULL STOP" + string="12." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒔" + u:name="NUMBER THIRTEEN FULL STOP" + string="13." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒕" + u:name="NUMBER FOURTEEN FULL STOP" + string="14." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒖" + u:name="NUMBER FIFTEEN FULL STOP" + string="15." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒗" + u:name="NUMBER SIXTEEN FULL STOP" + string="16." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒘" + u:name="NUMBER SEVENTEEN FULL STOP" + string="17." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒙" + u:name="NUMBER EIGHTEEN FULL STOP" + string="18." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒚" + u:name="NUMBER NINETEEN FULL STOP" + string="19." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒛" + u:name="NUMBER TWENTY FULL STOP" + string="20." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒜" + u:name="PARENTHESIZED LATIN SMALL LETTER A" + string="(a)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒝" + u:name="PARENTHESIZED LATIN SMALL LETTER B" + string="(b)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒞" + u:name="PARENTHESIZED LATIN SMALL LETTER C" + string="(c)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒟" + u:name="PARENTHESIZED LATIN SMALL LETTER D" + string="(d)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒠" + u:name="PARENTHESIZED LATIN SMALL LETTER E" + string="(e)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒡" + u:name="PARENTHESIZED LATIN SMALL LETTER F" + string="(f)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒢" + u:name="PARENTHESIZED LATIN SMALL LETTER G" + string="(g)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒣" + u:name="PARENTHESIZED LATIN SMALL LETTER H" + string="(h)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒤" + u:name="PARENTHESIZED LATIN SMALL LETTER I" + string="(i)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒥" + u:name="PARENTHESIZED LATIN SMALL LETTER J" + string="(j)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒦" + u:name="PARENTHESIZED LATIN SMALL LETTER K" + string="(k)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒧" + u:name="PARENTHESIZED LATIN SMALL LETTER L" + string="(l)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒨" + u:name="PARENTHESIZED LATIN SMALL LETTER M" + string="(m)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒩" + u:name="PARENTHESIZED LATIN SMALL LETTER N" + string="(n)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒪" + u:name="PARENTHESIZED LATIN SMALL LETTER O" + string="(o)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒫" + u:name="PARENTHESIZED LATIN SMALL LETTER P" + string="(p)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒬" + u:name="PARENTHESIZED LATIN SMALL LETTER Q" + string="(q)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒭" + u:name="PARENTHESIZED LATIN SMALL LETTER R" + string="(r)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒮" + u:name="PARENTHESIZED LATIN SMALL LETTER S" + string="(s)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒯" + u:name="PARENTHESIZED LATIN SMALL LETTER T" + string="(t)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒰" + u:name="PARENTHESIZED LATIN SMALL LETTER U" + string="(u)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒱" + u:name="PARENTHESIZED LATIN SMALL LETTER V" + string="(v)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒲" + u:name="PARENTHESIZED LATIN SMALL LETTER W" + string="(w)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒳" + u:name="PARENTHESIZED LATIN SMALL LETTER X" + string="(x)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒴" + u:name="PARENTHESIZED LATIN SMALL LETTER Y" + string="(y)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒵" + u:name="PARENTHESIZED LATIN SMALL LETTER Z" + string="(z)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓐ" + u:name="CIRCLED LATIN CAPITAL LETTER A" + string="A" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓑ" + u:name="CIRCLED LATIN CAPITAL LETTER B" + string="B" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓒ" + u:name="CIRCLED LATIN CAPITAL LETTER C" + string="C" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓓ" + u:name="CIRCLED LATIN CAPITAL LETTER D" + string="D" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓔ" + u:name="CIRCLED LATIN CAPITAL LETTER E" + string="E" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓕ" + u:name="CIRCLED LATIN CAPITAL LETTER F" + string="F" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓖ" + u:name="CIRCLED LATIN CAPITAL LETTER G" + string="G" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓗ" + u:name="CIRCLED LATIN CAPITAL LETTER H" + string="H" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓘ" + u:name="CIRCLED LATIN CAPITAL LETTER I" + string="I" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓙ" + u:name="CIRCLED LATIN CAPITAL LETTER J" + string="J" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓚ" + u:name="CIRCLED LATIN CAPITAL LETTER K" + string="K" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓛ" + u:name="CIRCLED LATIN CAPITAL LETTER L" + string="L" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓜ" + u:name="CIRCLED LATIN CAPITAL LETTER M" + string="M" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓝ" + u:name="CIRCLED LATIN CAPITAL LETTER N" + string="N" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓞ" + u:name="CIRCLED LATIN CAPITAL LETTER O" + string="O" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓟ" + u:name="CIRCLED LATIN CAPITAL LETTER P" + string="P" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓠ" + u:name="CIRCLED LATIN CAPITAL LETTER Q" + string="Q" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓡ" + u:name="CIRCLED LATIN CAPITAL LETTER R" + string="R" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓢ" + u:name="CIRCLED LATIN CAPITAL LETTER S" + u:entity="oS" + string="S" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓣ" + u:name="CIRCLED LATIN CAPITAL LETTER T" + string="T" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓤ" + u:name="CIRCLED LATIN CAPITAL LETTER U" + string="U" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓥ" + u:name="CIRCLED LATIN CAPITAL LETTER V" + string="V" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓦ" + u:name="CIRCLED LATIN CAPITAL LETTER W" + string="W" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓧ" + u:name="CIRCLED LATIN CAPITAL LETTER X" + string="X" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓨ" + u:name="CIRCLED LATIN CAPITAL LETTER Y" + string="Y" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓩ" + u:name="CIRCLED LATIN CAPITAL LETTER Z" + string="Z" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓐ" + u:name="CIRCLED LATIN SMALL LETTER A" + string="a" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓑ" + u:name="CIRCLED LATIN SMALL LETTER B" + string="b" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓒ" + u:name="CIRCLED LATIN SMALL LETTER C" + string="c" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓓ" + u:name="CIRCLED LATIN SMALL LETTER D" + string="d" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓔ" + u:name="CIRCLED LATIN SMALL LETTER E" + string="e" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓕ" + u:name="CIRCLED LATIN SMALL LETTER F" + string="f" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓖ" + u:name="CIRCLED LATIN SMALL LETTER G" + string="g" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓗ" + u:name="CIRCLED LATIN SMALL LETTER H" + string="h" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓘ" + u:name="CIRCLED LATIN SMALL LETTER I" + string="i" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓙ" + u:name="CIRCLED LATIN SMALL LETTER J" + string="j" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓚ" + u:name="CIRCLED LATIN SMALL LETTER K" + string="k" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓛ" + u:name="CIRCLED LATIN SMALL LETTER L" + string="l" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓜ" + u:name="CIRCLED LATIN SMALL LETTER M" + string="m" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓝ" + u:name="CIRCLED LATIN SMALL LETTER N" + string="n" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓞ" + u:name="CIRCLED LATIN SMALL LETTER O" + string="o" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓟ" + u:name="CIRCLED LATIN SMALL LETTER P" + string="p" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓠ" + u:name="CIRCLED LATIN SMALL LETTER Q" + string="q" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓡ" + u:name="CIRCLED LATIN SMALL LETTER R" + string="r" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓢ" + u:name="CIRCLED LATIN SMALL LETTER S" + string="s" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓣ" + u:name="CIRCLED LATIN SMALL LETTER T" + string="t" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓤ" + u:name="CIRCLED LATIN SMALL LETTER U" + string="u" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓥ" + u:name="CIRCLED LATIN SMALL LETTER V" + string="b" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓦ" + u:name="CIRCLED LATIN SMALL LETTER W" + string="w" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓧ" + u:name="CIRCLED LATIN SMALL LETTER X" + string="x" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓨ" + u:name="CIRCLED LATIN SMALL LETTER Y" + string="y" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓩ" + u:name="CIRCLED LATIN SMALL LETTER Z" + string="z" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓪" + u:name="CIRCLED DIGIT ZERO" + string="0" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓫" + u:name="NEGATIVE CIRCLED NUMBER ELEVEN" + string="11" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓬" + u:name="NEGATIVE CIRCLED NUMBER TWELVE" + string="12" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓭" + u:name="NEGATIVE CIRCLED NUMBER THIRTEEN" + string="13" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓮" + u:name="NEGATIVE CIRCLED NUMBER FOURTEEN" + string="14" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓯" + u:name="NEGATIVE CIRCLED NUMBER FIFTEEN" + string="15" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓰" + u:name="NEGATIVE CIRCLED NUMBER SIXTEEN" + string="16" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓱" + u:name="NEGATIVE CIRCLED NUMBER SEVENTEEN" + string="17" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓲" + u:name="NEGATIVE CIRCLED NUMBER EIGHTEEN" + string="18" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓳" + u:name="NEGATIVE CIRCLED NUMBER NINETEEN" + string="19" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓴" + u:name="NEGATIVE CIRCLED NUMBER TWENTY" + string="20" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓵" + u:name="DOUBLE CIRCLED DIGIT ONE" + string="1" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓶" + u:name="DOUBLE CIRCLED DIGIT TWO" + string="2" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓷" + u:name="DOUBLE CIRCLED DIGIT THREE" + string="3" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓸" + u:name="DOUBLE CIRCLED DIGIT FOUR" + string="4" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓹" + u:name="DOUBLE CIRCLED DIGIT FIVE" + string="5" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓺" + u:name="DOUBLE CIRCLED DIGIT SIX" + string="6" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓻" + u:name="DOUBLE CIRCLED DIGIT SEVEN" + string="7" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓼" + u:name="DOUBLE CIRCLED DIGIT EIGHT" + string="8" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓽" + u:name="DOUBLE CIRCLED DIGIT NINE" + string="9" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓾" + u:name="DOUBLE CIRCLED NUMBER TEN" + string="10" + u:block="Enclosed Alphanumerics" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Enclosed Alphanumerics --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Box Drawing --> + <!-- * x2500 to x257f --> + <!-- * Block Elements --> + <!-- * x2580 to x259f --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Geometric Shapes --> + <!-- * x25a0 to x25f7 --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="□" + u:name="WHITE SQUARE" + u:entity="squ" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▢" + u:name="WHITE SQUARE WITH ROUNDED CORNERS" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▫" + u:name="WHITE SMALL SQUARE" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="►" + u:name="BLACK RIGHT-POINTING POINTER" + string="\fB>\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▻" + u:name="WHITE RIGHT-POINTING POINTER" + string=">" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▼" + u:name="BLACK DOWN-POINTING TRIANGLE" + string="\fBv\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▽" + u:name="WHITE DOWN-POINTING TRIANGLE" + u:entity="xdtri" + string="v" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◄" + u:name="BLACK LEFT-POINTING POINTER" + string="\fB<\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◅" + u:name="WHITE LEFT-POINTING POINTER" + string="<" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◆" + u:name="BLACK DIAMOND" + string="\(DI" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◇" + u:name="WHITE DIAMOND" + string="\(lz" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◊" + u:name="LOZENGE" + u:entity="loz" + string="\(lz" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="○" + u:name="WHITE CIRCLE" + u:entity="cir" + string="\(ci" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="●" + u:name="BLACK CIRCLE" + string="\(bu" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◦" + u:name="WHITE BULLET" + string="\(ci" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◯" + u:name="LARGE CIRCLE" + u:entity="xcirc" + string="\(ci" + u:block="Geometric Shapes" + /> + <!-- * ***************************************************************** --> + <!-- * End: Geometric Shapes --> + <!-- * x25a0 to x25f7 --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Miscellaneous Symbols --> + <!-- * x2600 to x26ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="☚" + u:name="BLACK LEFT POINTING INDEX" + string="\(lh" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☛" + u:name="BLACK RIGHT POINTING INDEX" + string="\(rh)" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☜" + u:name="WHITE LEFT POINTING INDEX" + string="\(lh" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☞" + u:name="WHITE RIGHT POINTING INDEX" + string="\(rh)" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♠" + u:name="BLACK SPADE SUIT" + u:entity="spades" + string="\(SP" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♡" + u:name="WHITE HEART SUIT" + string="\(HE" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♢" + u:name="WHITE DIAMOND SUIT" + string="\(DI" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♣" + u:name="BLACK CLUB SUIT" + u:entity="clubs" + string="\(CL" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♤" + u:name="WHITE SPADE SUIT" + string="\(SP" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♥" + u:name="BLACK HEART SUIT" + u:entity="hearts" + string="\(HE" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♦" + u:name="BLACK DIAMOND SUIT" + u:entity="diams" + string="\(DI" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♧" + u:name="WHITE CLUB SUIT" + string="\(CL" + u:block="Miscellaneous Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Miscellaneous Symbols --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Dingbats --> + <!-- * x2700 to x27be --> + <!-- * No roff equiv for most of these; just map to something close --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="✓" + u:name="CHECK MARK" + u:entity="check" + string="\(OK" + u:block="Dingbats" + /> + <xsl:output-character + character="✔" + u:name="HEAVY CHECK MARK" + string="\fB\(OK\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✕" + u:name="MULTIPLICATION X" + string="\(mu" + u:block="Dingbats" + /> + <xsl:output-character + character="✖" + u:name="HEAVY MULTIPLICATION X" + string="\fB\(mu\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✗" + u:name="BALLOT X" + u:entity="cross" + string="\(mu" + u:block="Dingbats" + /> + <xsl:output-character + character="✘" + u:name="HEAVY BALLOT X" + string="\fB\(mu\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✙" + u:name="OUTLINED GREEK CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✚" + u:name="HEAVY GREEK CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✛" + u:name="OPEN CENTRE CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✜" + u:name="HEAVY OPEN CENTRE CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✝" + u:name="LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✞" + u:name="SHADOWED WHITE LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✟" + u:name="OUTLINED LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✠" + u:name="MALTESE CROSS" + u:entity="malt" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✡" + u:name="STAR OF DAVID" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✢" + u:name="FOUR TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✣" + u:name="FOUR BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✤" + u:name="HEAVY FOUR BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✥" + u:name="FOUR CLUB-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✦" + u:name="BLACK FOUR POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✧" + u:name="WHITE FOUR POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✩" + u:name="STRESS OUTLINED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✪" + u:name="CIRCLED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✫" + u:name="OPEN CENTRE BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✬" + u:name="BLACK CENTRE WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✭" + u:name="OUTLINED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✮" + u:name="HEAVY OUTLINED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✯" + u:name="PINWHEEL STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✰" + u:name="SHADOWED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✱" + u:name="HEAVY ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✲" + u:name="OPEN CENTRE ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✳" + u:name="EIGHT SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✴" + u:name="EIGHT POINTED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✵" + u:name="EIGHT POINTED PINWHEEL STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✶" + u:name="SIX POINTED BLACK STAR" + u:entity="sext" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✷" + u:name="EIGHT POINTED RECTILINEAR BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✸" + u:name="HEAVY EIGHT POINTED RECTILINEAR BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✹" + u:name="TWELVE POINTED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✺" + u:name="SIXTEEN POINTED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✻" + u:name="TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✼" + u:name="OPEN CENTRE TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✽" + u:name="HEAVY TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✾" + u:name="SIX PETALLED BLACK AND WHITE FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✿" + u:name="BLACK FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❀" + u:name="WHITE FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❁" + u:name="EIGHT PETALLED OUTLINED BLACK FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❂" + u:name="CIRCLED OPEN CENTRE EIGHT POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❃" + u:name="HEAVY TEARDROP-SPOKED PINWHEEL ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❄" + u:name="SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❅" + u:name="TIGHT TRIFOLIATE SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❆" + u:name="HEAVY CHEVRON SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❇" + u:name="SPARKLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❈" + u:name="HEAVY SPARKLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❉" + u:name="BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❊" + u:name="EIGHT TEARDROP-SPOKED PROPELLER ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❋" + u:name="HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❍" + u:name="SHADOWED WHITE CIRCLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❏" + u:name="LOWER RIGHT DROP-SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❐" + u:name="UPPER RIGHT DROP-SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❑" + u:name="LOWER RIGHT SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❒" + u:name="UPPER RIGHT SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❖" + u:name="BLACK DIAMOND MINUS WHITE X" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❘" + u:name="LIGHT VERTICAL BAR" + string="\(bv" + u:block="Dingbats" + /> + <xsl:output-character + character="❙" + u:name="MEDIUM VERTICAL BAR" + string="\fB\(bv\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❚" + u:name="HEAVY VERTICAL BAR" + string="\fB\(bv\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❛" + u:name="HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT" + string="\fB\(oq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❜" + u:name="HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT" + string="\fB\(cq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❝" + u:name="HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT" + string="\fB\(lq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❞" + u:name="HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT" + string="\fB\(rq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❡" + u:name="CURVED STEM PARAGRAPH SIGN ORNAMENT" + string="\(ps" + u:block="Dingbats" + /> + <xsl:output-character + character="❢" + u:name="HEAVY EXCLAMATION MARK ORNAMENT" + string="\fB!\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❣" + u:name="HEAVY HEART EXCLAMATION MARK ORNAMENT" + string="\fB!\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❤" + u:name="HEAVY BLACK HEART" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❥" + u:name="ROTATED HEAVY BLACK HEART BULLET" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❦" + u:name="FLORAL HEART" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❧" + u:name="ROTATED FLORAL HEART BULLET" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❶" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="❷" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="❸" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="❹" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="❺" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="❻" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="❼" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="❽" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="❾" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="❿" + u:name="DINGBAT NEGATIVE CIRCLED NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➀" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="➁" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="➂" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="➃" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="➄" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="➅" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="➆" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="➇" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="➈" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="➉" + u:name="DINGBAT CIRCLED SANS-SERIF NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➊" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="➋" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="➌" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="➍" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="➎" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="➏" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="➐" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="➑" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="➒" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="➓" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➔" + u:name="HEAVY WIDE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➙" + u:name="HEAVY RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➛" + u:name="DRAFTING POINT RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➜" + u:name="HEAVY ROUND-TIPPED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➝" + u:name="TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➞" + u:name="HEAVY TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➟" + u:name="DASHED TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➠" + u:name="HEAVY DASHED TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➡" + u:name="BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➢" + u:name="THREE-D TOP-LIGHTED RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➣" + u:name="THREE-D BOTTOM-LIGHTED RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➤" + u:name="BLACK RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➧" + u:name="SQUAT BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➨" + u:name="HEAVY CONCAVE-POINTED BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➩" + u:name="RIGHT-SHADED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➪" + u:name="LEFT-SHADED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➫" + u:name="BACK-TILTED SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➬" + u:name="FRONT-TILTED SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➭" + u:name="HEAVY LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➮" + u:name="HEAVY UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➯" + u:name="NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➱" + u:name="NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➲" + u:name="CIRCLED HEAVY WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➳" + u:name="WHITE-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➴" + u:name="BLACK-FEATHERED SOUTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➵" + u:name="BLACK-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➶" + u:name="BLACK-FEATHERED NORTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➷" + u:name="HEAVY BLACK-FEATHERED SOUTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➸" + u:name="HEAVY BLACK-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➹" + u:name="HEAVY BLACK-FEATHERED NORTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➺" + u:name="TEARDROP-BARBED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➻" + u:name="HEAVY TEARDROP-SHANKED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➼" + u:name="WEDGE-TAILED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➽" + u:name="HEAVY WEDGE-TAILED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➾" + u:name="OPEN-OUTLINED RIGHTWARDS ARROW" + string="\fR\(rA\fB" + u:block="Dingbats" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Dingbats --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Miscellaneous Mathematical Symbols --> + <!-- * x27c0 to x27ef --> + <!-- * Supplemental Arrows --> + <!-- * x27f0 to x297f --> + <!-- * Miscellaneous Mathematical Symbols --> + <!-- * x2980 to x29ff --> + <!-- * Supplemental Mathematical Operators --> + <!-- * x2a00 to x2aff --> + <!-- * - no nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Alphabetic Presentation Forms --> + <!-- * xfb00 to xfb04 --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="ff" + u:name="LATIN SMALL LIGATURE FF" + u:entity="fflig" + string="\(ff" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="fi" + u:name="LATIN SMALL LIGATURE FI" + u:entity="filig" + string="\(fi" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="fl" + u:name="LATIN SMALL LIGATURE FL" + u:entity="fllig" + string="\(fl" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="ffi" + u:name="LATIN SMALL LIGATURE FFI" + u:entity="ffilig" + string="\(Fi" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="ffl" + u:name="LATIN SMALL LIGATURE FFL" + u:entity="ffllig" + string="\(Fl" + u:block="Alphabetic Presentation Forms" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Alphabetic Presentation Forms --> + <!-- * ***************************************************************** --> + + <!-- * ================================================================= --> + + <!-- * Regarding x2060 vs. xFEFF, the document "Unicode Standard Annex #14, --> + <!-- * Line Breaking Properties"[1] says: --> + <!-- * --> + <!-- * The word joiner character [x2060 a.k.a "WJ"] is the preferred --> + <!-- * choice for an invisible character to keep other characters --> + <!-- * together that would otherwise be split across the line at a direct --> + <!-- * break. The character FEFF has the same effect, but because it is --> + <!-- * also used in an unrelated way as a byte order mark, the use of the --> + <!-- * WJ as the preferred interword glue simplifies the handling of FEFF. --> + <!-- * --> + <!-- * [1] http://www.unicode.org/reports/tr14/ --> + <!-- * --> + <!-- * We include it here anyway & map to the roff zero-width no-break --> + <xsl:output-character + character="" + u:name="ZERO WIDTH NO-BREAK SPACE" + string="\&" + u:block="Arabic Presentation Forms-B" + /> +</xsl:character-map> +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/docbook.xsl b/docs/xsl-generic/manpages/docbook.xsl new file mode 100644 index 00000000..87c7ade8 --- /dev/null +++ b/docs/xsl-generic/manpages/docbook.xsl @@ -0,0 +1,293 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + exclude-result-prefixes="exsl" + version='1.0'> + + <xsl:import href="../html/docbook.xsl"/> + <xsl:import href="../html/manifest.xsl"/> + <!-- * html-synop.xsl file is generated by build --> + <xsl:import href="html-synop.xsl"/> + <xsl:output method="text" + encoding="UTF-8" + indent="no"/> + <!-- ******************************************************************** + $Id: docbook.xsl 7153 2007-07-26 14:08:55Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <!-- ==================================================================== --> + + <xsl:include href="../common/refentry.xsl"/> + <xsl:include href="../common/charmap.xsl"/> + <xsl:include href="param.xsl"/> + <xsl:include href="utility.xsl"/> + <xsl:include href="info.xsl"/> + <xsl:include href="other.xsl"/> + <xsl:include href="refentry.xsl"/> + <xsl:include href="block.xsl"/> + <xsl:include href="inline.xsl"/> + <xsl:include href="synop.xsl"/> + <xsl:include href="lists.xsl"/> + <xsl:include href="endnotes.xsl"/> + <xsl:include href="table.xsl"/> + + <!-- * we rename the following just to avoid using params with "man" --> + <!-- * prefixes in the table.xsl stylesheet (because that stylesheet --> + <!-- * can potentially be reused for more than just man output) --> + <xsl:param name="tbl.font.headings" select="$man.font.table.headings"/> + <xsl:param name="tbl.font.title" select="$man.font.table.title"/> + + <!-- ==================================================================== --> + + <xsl:template match="/"> + <!-- * Get a title for current doc so that we let the user --> + <!-- * know what document we are processing at this point. --> + <xsl:variable name="doc.title"> + <xsl:call-template name="get.doc.title"/> + </xsl:variable> + <xsl:choose> + <!-- * when we find a namespaced document, strip the --> + <!-- * namespace and then continue processing it. --> + <xsl:when test="//self::db:*"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text>namesp. cut</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>stripped namespace before processing</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:variable name="stripns"> + <xsl:apply-templates mode="stripNS"/> + </xsl:variable> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text>namesp. cut</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>processing stripped document</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:apply-templates select="exsl:node-set($stripns)"/> + </xsl:when> + <xsl:when test="//*[local-name() = 'refentry']"> + <!-- * Check to see if we have any refentry children in this --> + <!-- * document; if so, process them. The reason we use --> + <!-- * local-name()=refentry (instead of just //refentry) to to --> + <!-- * check for refentry children is because this stylsheet is --> + <!-- * also post-processed by the stylesheet build to create the --> + <!-- * manpages/profile-docbook.xsl, and the refentry child check --> + <!-- * in the profile-docbook.xsl stylesheet won't work if we do --> + <!-- * a simple //refentry check. --> + <xsl:apply-templates select="//refentry"/> + <!-- * if $man.output.manifest.enabled is non-zero, --> + <!-- * generate a manifest file --> + <xsl:if test="not($man.output.manifest.enabled = 0)"> + <xsl:call-template name="generate.manifest"> + <xsl:with-param name="filename"> + <xsl:choose> + <xsl:when test="not($man.output.manifest.filename = '')"> + <!-- * If a name for the manifest file is specified, --> + <!-- * use that name. --> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, if user has unset --> + <!-- * $man.output.manifest.filename, default to --> + <!-- * using "MAN.MANIFEST" as the filename. Because --> + <!-- * $man.output.manifest.enabled is non-zero and --> + <!-- * so we must have a filename in order to --> + <!-- * generate the manifest. --> + <xsl:text>MAN.MANIFEST</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, the document does not contain any --> + <!-- * refentry elements, so log/emit message and stop. --> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Erro</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text> no refentry</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>No refentry elements found</xsl:text> + <xsl:if test="$doc.title != ''"> + <xsl:text> in "</xsl:text> + <xsl:choose> + <xsl:when test="string-length($doc.title) > 30"> + <xsl:value-of select="substring($doc.title,1,30)"/> + <xsl:text>...</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$doc.title"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>"</xsl:text> + </xsl:if> + <xsl:text>.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template match="refentry"> + <xsl:param name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:param> + <!-- * Just use the first refname found as the "name" of the man --> + <!-- * page (which may different from the "title"...) --> + <xsl:variable name="first.refname" select="refnamediv[1]/refname[1]"/> + + <xsl:call-template name="root.messages"> + <xsl:with-param name="refname" select="$first.refname"/> + </xsl:call-template> + + <!-- * Because there are several times when we need to check *info of --> + <!-- * each refentry and its ancestors, we get those and store the --> + <!-- * data from them as a node-set in memory. --> + + <!-- * Make a node-set with contents of *info --> + <xsl:variable name="get.info" + select="ancestor-or-self::*/*[substring(local-name(), + string-length(local-name()) - 3) = 'info']" + /> + <xsl:variable name="info" select="exsl:node-set($get.info)"/> + + <!-- * The get.refentry.metadata template is in --> + <!-- * ../common/refentry.xsl. It looks for metadata in $info --> + <!-- * and in various other places and then puts it into a form --> + <!-- * that's easier for us to digest. --> + <xsl:variable name="get.refentry.metadata"> + <xsl:call-template name="get.refentry.metadata"> + <xsl:with-param name="refname" select="$first.refname"/> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="prefs" select="$refentry.metadata.prefs"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="refentry.metadata" select="exsl:node-set($get.refentry.metadata)"/> + + <!-- * Assemble the various parts into a complete page, then store into --> + <!-- * $manpage.contents so that we can manipluate them further. --> + <xsl:variable name="manpage.contents"> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * top.comment = commented-out section at top of roff source --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="top.comment"> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="date" select="$refentry.metadata/date"/> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="manual" select="$refentry.metadata/manual"/> + <xsl:with-param name="source" select="$refentry.metadata/source"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * TH.title.line = title line in header/footer of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="TH.title.line"> + <!-- * .TH TITLE section extra1 extra2 extra3 --> + <!-- * --> + <!-- * According to the man(7) man page: --> + <!-- * --> + <!-- * extra1 = date, "the date of the last revision" --> + <!-- * extra2 = source, "the source of the command" --> + <!-- * extra3 = manual, "the title of the manual --> + <!-- * (e.g., Linux Programmer's Manual)" --> + <!-- * --> + <!-- * So, we end up with: --> + <!-- * --> + <!-- * .TH TITLE section date source manual --> + <!-- * --> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="extra1" select="$refentry.metadata/date"/> + <xsl:with-param name="extra2" select="$refentry.metadata/source"/> + <xsl:with-param name="extra3" select="$refentry.metadata/manual"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Set default hyphenation, justification, indentation, and --> + <!-- * line-breaking --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="set.default.formatting"/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Main body of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:apply-templates/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * AUTHOR section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.authors.section.enabled = 0)"> + <xsl:call-template name="author.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * COPYRIGHT section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.copyright.section.enabled = 0)"> + <xsl:call-template name="copyright.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * NOTES list (only if user wants endnotes numbered and/or listed) --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="$man.endnotes.list.enabled != 0 or + $man.endnotes.are.numbered != 0"> + <xsl:call-template name="endnotes.list"/> + </xsl:if> + </xsl:variable> <!-- * end of manpage.contents --> + + <!-- * Prepare the page contents for final output, then store in --> + <!-- * $manpage.contents.prepared so the we can pass it on to the --> + <!-- * write.text.chunk() function --> + <xsl:variable name="manpage.contents.prepared"> + <!-- * "Preparing" the page contents involves, at a minimum, --> + <!-- * doubling any backslashes found (so they aren't interpreted --> + <!-- * as roff escapes). --> + <!-- * --> + <!-- * If $charmap.enabled is true, "preparing" the page contents also --> + <!-- * involves applying a character map to convert Unicode symbols and --> + <!-- * special characters into corresponding roff escape sequences. --> + <xsl:call-template name="prepare.manpage.contents"> + <xsl:with-param name="content" select="$manpage.contents"/> + </xsl:call-template> + </xsl:variable> + + <!-- * Write the prepared page contents to disk to create --> + <!-- * the final man page. --> + <xsl:call-template name="write.man.file"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + <xsl:with-param name="content" select="$manpage.contents.prepared"/> + </xsl:call-template> + + <!-- * Generate "stub" (alias) pages (if any needed) --> + <xsl:call-template name="write.stubs"> + <xsl:with-param name="first.refname" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/endnotes.xsl b/docs/xsl-generic/manpages/endnotes.xsl new file mode 100644 index 00000000..f1c7480c --- /dev/null +++ b/docs/xsl-generic/manpages/endnotes.xsl @@ -0,0 +1,535 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + exclude-result-prefixes="db ng exsl xlink" + version='1.0'> + +<!-- ******************************************************************** + $Id: endnotes.xsl 7254 2007-08-18 23:59:53Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> +<!-- * --> +<!-- * The templates in this file handle elements whose contents can't --> +<!-- * be displayed completely within the main text flow in output, but --> +<!-- * instead need to be displayed "out of line". Those elements are: --> +<!-- * --> +<!-- * - elements providing annotative text (annotation|alt|footnote) --> +<!-- * - elements pointing at external resources (ulink, link, and --> +<!-- * any elements with xlink:href attributes; and imagedata, --> +<!-- * audiodata, and videodata - which (using their fileref --> +<!-- * attribute) reference external files --> +<!-- * --> +<!-- * Within this stylesheet, the above are collectively referred to as --> +<!-- * a "notesources". This stylesheet handles those notesources in --> +<!-- * this way: --> +<!-- * --> +<!-- * 1. Constructs a numbered in-memory index of all unique "earmarks“ --> +<!-- * of all notesources in the document. For each link, the --> +<!-- * earmark is the value of its url or xlink:href attribute; for --> +<!-- * each imagedata|audiodata|videodata: the value of its fileref --> +<!-- * attribute; for each annotative element: its content. --> +<!-- * --> +<!-- * Notesources with the same earmark are assigned the same --> +<!-- * number. --> +<!-- * --> +<!-- * By design, that index excludes any element whose whose string --> +<!-- * value is identical to value of its url xlink:href attribute). --> +<!-- * --> +<!-- * 2. Puts a numbered marker inline to mark the place where the --> +<!-- * notesource occurs in the main text flow. --> +<!-- * --> +<!-- * 3. Generates a numbered endnotes list (titled NOTES in English) --> +<!-- * at the end of the man page, with the contents of each --> +<!-- * notesource. --> +<!-- * --> +<!-- * Note that table footnotes are not listed in the endnotes list, --> +<!-- * and are not handled by this stylesheet (they are instead handled --> +<!-- * by the table.xsl stylesheet). --> +<!-- * --> +<!-- * Also, we don't get notesources in *info sections or Refmeta or --> +<!-- * Refnamediv or Indexterm, because, in manpages output, contents of --> +<!-- * those are either suppressed or are displayed out of document --> +<!-- * order - for example, the Info/Author content gets moved to the --> +<!-- * end of the page. So, if we were to number notesources in the --> +<!-- * Author content, it would "throw off" the numbering at the --> +<!-- * beginning of the main text flow. --> +<!-- * --> +<!-- * And for the record, one reason we don't use xsl:key to index the --> +<!-- * earmarks is that we need to get and check the sets of --> +<!-- * earmarks for uniqueness per-Refentry (not per-document). --> +<!-- * --> +<!-- * FIXME: as --> +<!-- * with "repeat" URLS, alt instances that have the same string value --> +<!-- * as preceding ones (likely to occur for repeat acroynyms and --> +<!-- * abbreviations) should be listed only once in the endnotes list, --> +<!-- * and numbered accordingly inline; split man.indent.width into --> +<!-- * man.indent.width.value (default 4) and man.indent.width.units --> +<!-- * (default n); also, if the first child of notesource is some block --> +<!-- * content other than a (non-formal) paragraph, the current code --> +<!-- * will probably end up generating a blank line after the --> +<!-- * corresponding number in the endnotes list... we should probably --> +<!-- * try to instead display the title of that block content there (if --> +<!-- * there is one: e.g., the list title, admonition title, etc.) --> + +<!-- ==================================================================== --> + +<xsl:template name="get.all.earmark.indexes.in.current.document"> + <!-- * Here we create a tree to hold indexes of all earmarks in --> + <!-- * the current document. If the current document contains --> + <!-- * multiple refentry instances, then this tree will contain --> + <!-- * multiple indexes. --> + <xsl:if test="$man.endnotes.are.numbered != 0"> + <!-- * Only create earmark indexes if user wants numbered endnotes --> + <xsl:for-each select="//refentry"> + <earmark.index> + <xsl:attribute name="idref"> + <xsl:value-of select="generate-id()"/> + </xsl:attribute> + <xsl:for-each + select=".//*[self::*[@xlink:href] + or self::ulink + or self::imagedata + or self::audiodata + or self::videodata + or self::footnote[not(ancestor::table)] + or self::annotation + or self::alt] + [(node() + or self::imagedata + or self::audiodata + or self::videodata + ) + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and not(. = @url) + and not(. = @xlink:href) + and not(@url = + preceding::ulink[node() + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@url) + and not(@xlink:href = + preceding::*[@xlink:href][node() + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@xlink:href) + and not(@fileref = + preceding::*[@fileref][ + not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@fileref)]"> + <earmark> + <xsl:attribute name="id"> + <xsl:value-of select="generate-id()"/> + </xsl:attribute> + <xsl:attribute name="number"> + <xsl:value-of select="position()"/> + </xsl:attribute> + <xsl:if test="@url|@xlink:href|@fileref"> + <!-- * Only add a uri attribute if the notesource is --> + <!-- * a link or an element that references an external --> + <!-- * (an imagedata, audiodata, or videodata element) --> + <xsl:attribute name="uri"> + <xsl:value-of select="@url|@xlink:href|@fileref"/> + </xsl:attribute> + </xsl:if> + <xsl:copy> + <xsl:copy-of select="node()"/> + </xsl:copy> + </earmark> + </xsl:for-each> + </earmark.index> + </xsl:for-each> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*[@xlink:href]|ulink + |imagedata|audiodata|videodata + |footnote[not(ancestor::table)] + |annotation|alt"> + <xsl:variable name="all.earmark.indexes.in.current.document.rtf"> + <xsl:call-template name="get.all.earmark.indexes.in.current.document"/> + </xsl:variable> + <xsl:variable name="all.earmark.indexes.in.current.document" + select="exsl:node-set($all.earmark.indexes.in.current.document.rtf)"/> + <xsl:variable name="all.earmarks.in.current.refentry.rtf"> + <!-- * get the set of all earmarks for the ancestor Refentry of --> + <!-- * this notesource --> + <xsl:copy-of + select="$all.earmark.indexes.in.current.document/earmark.index + [@idref = + generate-id(current()/ancestor::refentry)]/earmark"/> + </xsl:variable> + <xsl:variable name="all.earmarks.in.current.refentry" + select="exsl:node-set($all.earmarks.in.current.refentry.rtf)"/> + + <!-- * identify the earmark for the current element --> + <xsl:variable name="earmark"> + <xsl:choose> + <xsl:when test="@url|@xlink:href"> + <xsl:value-of select="@url|@xlink:href"/> + </xsl:when> + <xsl:when test="@fileref"> + <xsl:value-of select="@fileref"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="generate-id()"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="notesource.number"> + <!-- * Get the number for this notesource --> + <!-- * --> + <!-- * If this is an imagedata, audiodata, or videodata element --> + <!-- * OR if it's a non-empty element AND its string value is not --> + <!-- * equal to the value of its url or xlink:href attribute (if --> + <!-- * it has one) AND user wants endnotes numbered, only then --> + <!-- * do we output a number for it --> + <xsl:if test="(self::imagedata or + self::audiodata or + self::videodata or + (node() + and not(. = @url) + and not(. = @xlink:href)) + ) + and $man.endnotes.are.numbered != 0"> + <!-- * To select the number for this notesource, we --> + <!-- * check the index of all earmarks for the current refentry --> + <!-- * and find the number of the indexed earmark which matches --> + <!-- * this notesource's earmark. --> + <!-- * Note that multiple notesources may share the same --> + <!-- * numbered earmark; in that case, they get the same number. --> + <!-- * --> + <xsl:choose> + <xsl:when test="self::ulink or + self::*[@xlink:href] or + self::imagedata or + self::audiodata or + self::videodata"> + <xsl:value-of select="$all.earmarks.in.current.refentry/earmark[@uri = $earmark]/@number"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$all.earmarks.in.current.refentry/earmark[@id = $earmark]/@number"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:variable> + + <xsl:variable name="notesource.contents"> + <xsl:choose> + <!-- * check to see if the element is empty or not --> + <xsl:when test="node()"> + <!-- * this is a non-empty node, so process its contents --> + <xsl:apply-templates/> + <xsl:if test="../footnote or ../annotation"> + <!-- * if this element is a footnote or annotation, we need to --> + <!-- * do some further checking on it, so we can emit warnings --> + <!-- * about potential problems --> + <xsl:for-each select="node()"> + <xsl:if test="local-name() != 'para' and local-name() !=''"> + <!-- * for each node we find as a child of a footnote or --> + <!-- * annotation, if it's not a para or a text node, emit a --> + <!-- * warning... because in manpages output, we can't render --> + <!-- * block-level child content of an endnote properly unless --> + <!-- * it's wrapped in a para that has some "prefatory" text --> + <xsl:variable name="parent-name" select="local-name(..)"/> + <xsl:variable name="refname" select="ancestor::refentry/refnamediv[1]/refname[1]"/> + <xsl:variable name="endnote-number"> + <xsl:call-template name="pad-string"> + <!-- * endnote number may be 2 digits, so pad it with a space --> + <!-- * if we have only 1 digit --> + <xsl:with-param name="padVar" select="concat('#',$notesource.number)"/> + <xsl:with-param name="length" select="3"/> + </xsl:call-template> + </xsl:variable> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Bad: </xsl:text> + <xsl:value-of select="$parent-name"/> + <!-- * figure out which occurance of this element type this --> + <!-- * instance is and output a number in square brackets so --> + <!-- * that end-user can know which element to fix --> + <xsl:text>[</xsl:text> + <xsl:value-of select="count(preceding::*[local-name() = $parent-name]) + 1"/> + <xsl:text>]</xsl:text> + <xsl:text> in source</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Has: </xsl:text> + <xsl:value-of select="$parent-name"/> + <xsl:text>/</xsl:text> + <xsl:value-of select="local-name(.)"/> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Fix: </xsl:text> + <xsl:value-of select="$parent-name"/> + <xsl:text>/</xsl:text> + <xsl:text>para/</xsl:text> + <xsl:value-of select="local-name(.)"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:for-each> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise this is an empty link or an empty imagedata, --> + <!-- * audiodata, or videodata element, so we just get the --> + <!-- * value of its url, xlink:href, or fileref attribute. --> + <xsl:if test="$man.hyphenate.urls = 0 + and $man.break.after.slash = 0"> + <!-- * Add hyphenation suppression in URL output only if --> + <!-- * break.after.slash is also non-zero --> + <xsl:call-template name="suppress.hyphenation"/> + <xsl:text>\%</xsl:text> + </xsl:if> + <xsl:value-of select="$earmark"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="self::ulink or self::*[@xlink:href]"> + <!-- * This is a hyperlink, so we need to decide how to format --> + <!-- * the inline contents of the link (to underline or not). --> + <xsl:choose> + <!-- * if user wants links underlined, underline (ital) it --> + <xsl:when test="$man.links.are.underlined != 0"> + <xsl:variable name="link.wrapper"> + <xsl:value-of select="$notesource.contents"/> + </xsl:variable> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="exsl:node-set($link.wrapper)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * user doesn't want links underlined, so just display content --> + <xsl:value-of select="$notesource.contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <xsl:if test="$notesource.number != ''"> + <!-- * Format the number by placing it in square brackets. FIXME: --> + <!-- * This formatting should probably be made user-configurable, --> + <!-- * to allow something other than just square brackets; e.g., --> + <!-- * Angle brackets<10> or Braces{10} --> + <xsl:text>\&[</xsl:text> + <xsl:value-of select="$notesource.number"/> + <xsl:text>]</xsl:text> + <!-- * Note that the reason for the \& before the opening bracket --> + <!-- * is to prevent any possible linebreak from being introduced --> + <!-- * between the opening bracket and the following text. --> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="endnotes.list"> + <!-- We have stored earmark indexes for all refentry instances in the --> + <!-- current document, with the ID for each index being the same ID as --> + <!-- its corresponding refentry; so we now need to get the ID for the --> + <!-- current refentry so we can grab its corresponding earmark index --> + <xsl:variable name="current.refentry.id"> + <xsl:value-of select="generate-id(.)"/> + </xsl:variable> + + <xsl:variable name="endnotes.rtf"> + <xsl:variable name="all.earmark.indexes.in.current.document.rtf"> + <xsl:call-template name="get.all.earmark.indexes.in.current.document"/> + </xsl:variable> + <xsl:variable name="all.earmark.indexes.in.current.document" + select="exsl:node-set($all.earmark.indexes.in.current.document.rtf)"/> + <xsl:copy-of + select="$all.earmark.indexes.in.current.document/earmark.index + [@idref = $current.refentry.id]/earmark"/> + </xsl:variable> + + <xsl:variable name="endnotes" select="exsl:node-set($endnotes.rtf)"/> + + <!-- * check to see if we have actually found any content to use as --> + <!-- * endnotes; if we have, we generate the endnotes list, if not, --> + <!-- * we do nothing --> + <xsl:if test="$endnotes/node()"> + <xsl:call-template name="format.endnotes.list"> + <xsl:with-param name="endnotes" select="$endnotes"/> + </xsl:call-template> + </xsl:if> + +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="format.endnotes.list"> + <xsl:param name="endnotes"/> + <xsl:call-template name="mark.subheading"/> + + <!-- * ======= make the endnotes-list section heading ============= --> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <!-- * if user has specified a heading, use that --> + <xsl:when test="$man.endnotes.list.heading != ''"> + <xsl:value-of select="$man.endnotes.list.heading"/> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, get localized heading from gentext --> + <!-- * (in English, NOTES) --> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Notes'"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + + <!-- * ================ process each earmark ====================== --> + <xsl:for-each select="$endnotes/earmark"> + <!-- * make paragraph with hanging indent, and starting with a --> + <!-- * number in the form " 1." (padded to $man.indent.width - 1) --> + <xsl:text>.IP</xsl:text> + <xsl:text> "</xsl:text> + <xsl:variable name="endnote.number"> + <xsl:value-of select="@number"/> + <xsl:text>.</xsl:text> + </xsl:variable> + <xsl:call-template name="pad-string"> + <xsl:with-param name="padVar" select="$endnote.number"/> + <!-- FIXME: the following assumes that $man.indent.width is in --> + <!-- en's; also, this should probably use $list.indent instead --> + <xsl:with-param name="length" select="$man.indent.width - 1"/> + </xsl:call-template> + <xsl:text>"</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + + <!-- * ========================================================= --> + <!-- * print the notesource/endnote contents --> + <!-- * ========================================================= --> + <xsl:choose> + <xsl:when test="*/node()"> + <!-- * if the earmark has non-empty child content, then --> + <!-- * its corresponding notesource is either a link or --> + <!-- * an instance of annotative text, so we want to --> + <!-- * display that content --> + <xsl:choose> + <xsl:when test="*/node()[name(.)!='']"> + <!-- * if node is not text only, then process it as-is --> + <xsl:apply-templates select="*/node()"/> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise node is text-only, so normalize it --> + <xsl:value-of select="normalize-space(*/node())"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, this earmark has empty content, --> + <!-- * which means its corresponding notesources is an --> + <!-- * imagedata, audiodata, or videodata instance; in --> + <!-- * that case, we use the value of the notesoures's --> + <!-- * @fileref attribute (which is stored in the --> + <!-- * earmark uri attribute) as the "contents" for --> + <!-- * this endnote/notesource --> + <xsl:value-of select="@uri"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text> </xsl:text> + + <!-- * ========================================================= --> + <!-- * print the URL for links --> + <!-- * ========================================================= --> + <!-- * In addition to the notesource contents, if the --> + <!-- * notesource is a link, we display the URL for the link. --> + <!-- * But for notesources that are imagedata, audiodata, or --> + <!-- * videodata instances, we don't want to (re)display the --> + <!-- * URL for those here, because for those elements, the --> + <!-- * notesource contents are the URL (the value of the --> + <!-- * @fileref attribute), and we have already rendered them. --> + <!-- * --> + <!-- * We know an earmark is a link if it has non-empty child --> + <!-- * content and a uri attribute; so we check for that --> + <!-- * condition here. --> + <xsl:if test="*/node() and @uri"> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <!-- * Add hyphenation suppression in URL output only if --> + <!-- * $break.after.slash is also non-zero --> + <xsl:if test="$man.hyphenate.urls = 0 + and $man.break.after.slash = 0"> + <xsl:call-template name="suppress.hyphenation"/> + <xsl:text>\%</xsl:text> + </xsl:if> + <xsl:value-of select="@uri"/> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + + </xsl:for-each> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/html-synop.xsl b/docs/xsl-generic/manpages/html-synop.xsl new file mode 100644 index 00000000..a6182a01 --- /dev/null +++ b/docs/xsl-generic/manpages/html-synop.xsl @@ -0,0 +1,1605 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<!-- ******************************************************************** + $Id: synop.xsl 7250 2007-08-18 10:19:00Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<!-- synopsis is in verbatim --> + +<!-- ==================================================================== --> + +<xsl:template match="cmdsynopsis"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:if test="..//processing-instruction('dbcmdlist')"> + <!-- * Placing a dbcmdlist PI as a child of a particular element --> + <!-- * creates a hyperlinked list of all cmdsynopsis instances --> + <!-- * that are descendants of that element; so for any --> + <!-- * cmdsynopsis that is a descendant of an element containing --> + <!-- * a dbcmdlist PI, we need to output an a@id instance so that --> + <!-- * we will have something to link to --> + <xsl:call-template name="anchor"> + <xsl:with-param name="conditional" select="0"/> + </xsl:call-template> + </xsl:if> + <xsl:apply-templates/> + </p> + </div> +</xsl:template> + +<xsl:template match="cmdsynopsis/command"> + <xsl:text> +. +</xsl:text> + <xsl:call-template name="inline.monoseq"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="cmdsynopsis/command[1]" priority="2"> + <xsl:call-template name="inline.monoseq"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="group|arg" name="group-or-arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:variable name="sepchar"> + <xsl:choose> + <xsl:when test="ancestor-or-self::*/@sepchar"> + <xsl:value-of select="ancestor-or-self::*/@sepchar"/> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="preceding-sibling::*"> + <xsl:value-of select="$sepchar"/> + </xsl:if> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:value-of select="$arg.choice.plain.open.str"/> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.open.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.open.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates/> + <xsl:choose> + <xsl:when test="$rep='repeat'"> + <xsl:value-of select="$arg.rep.repeat.str"/> + </xsl:when> + <xsl:when test="$rep='norepeat'"> + <xsl:value-of select="$arg.rep.norepeat.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.rep.def.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:value-of select="$arg.choice.plain.close.str"/> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.close.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.close.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.close.str"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="group/arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:if test="preceding-sibling::*"> + <xsl:value-of select="$arg.or.sep"/> + </xsl:if> + <xsl:call-template name="group-or-arg"/> +</xsl:template> + +<xsl:template match="sbr"> + <xsl:text> +. +</xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="synopfragmentref"> + <xsl:variable name="target" select="key('id',@linkend)"/> + <xsl:variable name="snum"> + <xsl:apply-templates select="$target" mode="synopfragment.number"/> + </xsl:variable> + <i> + <a href="#{@linkend}"> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + </a> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + </i> +</xsl:template> + +<xsl:template match="synopfragment" mode="synopfragment.number"> + <xsl:number format="1"/> +</xsl:template> + +<xsl:template match="synopfragment"> + <xsl:variable name="snum"> + <xsl:apply-templates select="." mode="synopfragment.number"/> + </xsl:variable> + <p> + <xsl:variable name="id"> + <xsl:call-template name="object.id"/> + </xsl:variable> + <a name="{$id}"> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + </a> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + </p> +</xsl:template> + +<xsl:template match="funcsynopsis"> + <xsl:if test="..//processing-instruction('dbfunclist')"> + <!-- * Placing a dbfunclist PI as a child of a particular element --> + <!-- * creates a hyperlinked list of all funcsynopsis instances that --> + <!-- * are descendants of that element; so for any funcsynopsis that is --> + <!-- * a descendant of an element containing a dbfunclist PI, we need --> + <!-- * to output an a@id instance so that we will have something to --> + <!-- * link to --> + <xsl:call-template name="anchor"> + <xsl:with-param name="conditional" select="0"/> + </xsl:call-template> + </xsl:if> + <xsl:call-template name="informal.object"/> +</xsl:template> + +<xsl:template match="funcsynopsisinfo"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates/> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype --> +<!-- + +funcprototype ::= (funcdef, + (void|varargs|paramdef+)) + +funcdef ::= (#PCDATA|type|replaceable|function)* + +paramdef ::= (#PCDATA|type|replaceable|parameter|funcparams)* +--> + +<xsl:template match="funcprototype"> + <xsl:variable name="html-style"> + <xsl:call-template name="pi.dbhtml_funcsynopsis-style"> + <xsl:with-param name="node" select="ancestor::funcsynopsis/descendant-or-self::*"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="style"> + <xsl:choose> + <xsl:when test="$html-style != ''"> + <xsl:value-of select="$html-style"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$funcsynopsis.style"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + +<!-- + <xsl:variable name="tabular-p" + select="$funcsynopsis.tabular.threshold > 0 + and string-length(.) > $funcsynopsis.tabular.threshold"/> +--> + + <xsl:variable name="tabular-p" select="true()"/> + + <xsl:choose> + <xsl:when test="$style = 'kr' and $tabular-p"> + <xsl:apply-templates select="." mode="kr-tabular"/> + </xsl:when> + <xsl:when test="$style = 'kr'"> + <xsl:apply-templates select="." mode="kr-nontabular"/> + </xsl:when> + <xsl:when test="$style = 'ansi' and $tabular-p"> + <xsl:apply-templates select="." mode="ansi-tabular"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="ansi-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: kr, non-tabular --> + +<xsl:template match="funcprototype" mode="kr-nontabular"> + <p> + <xsl:apply-templates mode="kr-nontabular"/> + <xsl:if test="paramdef"> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="paramdef" mode="kr-funcsynopsis-mode"/> + </xsl:if> + </p> +</xsl:template> + +<xsl:template match="funcdef" mode="kr-nontabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="kr-nontabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="kr-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="kr-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="kr-nontabular"> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="varargs" mode="kr-nontabular"> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-nontabular"> + <xsl:apply-templates select="parameter" mode="kr-nontabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-nontabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-nontabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-funcsynopsis-mode"> + <xsl:if test="preceding-sibling::paramdef"><xsl:text> +. +</xsl:text></xsl:if> + <code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-funcsynopsis-mode"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="kr-funcsynopsis-mode"> + <code>(</code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: kr, tabular --> + +<xsl:template match="funcprototype" mode="kr-tabular"> + <table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0" style="padding-bottom: 1em"> + <tr> + <td> + <xsl:apply-templates select="funcdef" mode="kr-tabular"/> + </td> + <xsl:apply-templates select="(void|varargs|paramdef)[1]" mode="kr-tabular"/> + </tr> + <xsl:for-each select="(void|varargs|paramdef)[preceding-sibling::*[not(self::funcdef)]]"> + <tr> + <td> </td> + <xsl:apply-templates select="." mode="kr-tabular"/> + </tr> + </xsl:for-each> + </table> + <xsl:if test="paramdef"> + <table border="0" summary="Function argument synopsis" cellspacing="0" cellpadding="0"> + <xsl:if test="following-sibling::funcprototype"> + <xsl:attribute name="style">padding-bottom: 1em</xsl:attribute> + </xsl:if> + <xsl:apply-templates select="paramdef" mode="kr-tabular-funcsynopsis-mode"/> + </table> + </xsl:if> +</xsl:template> + +<xsl:template match="funcdef" mode="kr-tabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="kr-tabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="kr-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="kr-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-tabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="kr-tabular"> + <td> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="varargs" mode="kr-tabular"> + <td> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-tabular"> + <td> + <xsl:apply-templates select="parameter" mode="kr-tabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-tabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-tabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-tabular-funcsynopsis-mode"> + <xsl:variable name="type"> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:when> + <xsl:when test="normalize-space(parameter/preceding-sibling::node()[not(self::parameter)]) != ''"> + <xsl:copy-of select="parameter/preceding-sibling::node()[not(self::parameter)]"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <tr> + <xsl:choose> + <xsl:when test="$type != '' and funcparams"> + <td> + <code> + <xsl:copy-of select="$type"/> + </code> + <xsl:text> </xsl:text> + </td> + <td> + <code> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type/following-sibling::*" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:otherwise> + </xsl:choose> + </code> + </td> + </xsl:when> + + <xsl:when test="funcparams"> + <td colspan="2"> + <code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </code> + </td> + </xsl:when> + + <xsl:otherwise> + <td> + <code> + <xsl:apply-templates select="parameter/preceding-sibling::node()[not(self::parameter)]" mode="kr-tabular-funcsynopsis-mode"/> + </code> + <xsl:text> </xsl:text> + </td> + <td> + <code> + <xsl:apply-templates select="parameter" mode="kr-tabular"/> + <xsl:apply-templates select="parameter/following-sibling::*[not(self::parameter)]" mode="kr-tabular-funcsynopsis-mode"/> + <xsl:text>;</xsl:text> + </code> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-tabular-funcsynopsis-mode"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="kr-tabular-funcsynopsis-mode"> + <code>(</code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: ansi, non-tabular --> + +<xsl:template match="funcprototype" mode="ansi-nontabular"> + <p> + <xsl:apply-templates mode="ansi-nontabular"/> + </p> +</xsl:template> + +<xsl:template match="funcdef" mode="ansi-nontabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="ansi-nontabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="ansi-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="ansi-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="ansi-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="ansi-nontabular"> + <code>void)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="varargs" mode="ansi-nontabular"> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef" mode="ansi-nontabular"> + <xsl:apply-templates mode="ansi-nontabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="ansi-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="ansi-nontabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="ansi-nontabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="ansi-nontabular"> + <code>(</code> + <xsl:apply-templates mode="ansi-nontabular"/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: ansi, tabular --> + +<xsl:template match="funcprototype" mode="ansi-tabular"> + <table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0"> + <xsl:if test="following-sibling::funcprototype"> + <xsl:attribute name="style">padding-bottom: 1em</xsl:attribute> + </xsl:if> + <tr> + <td> + <xsl:apply-templates select="funcdef" mode="ansi-tabular"/> + </td> + <xsl:apply-templates select="(void|varargs|paramdef)[1]" mode="ansi-tabular"/> + </tr> + <xsl:for-each select="(void|varargs|paramdef)[preceding-sibling::*[not(self::funcdef)]]"> + <tr> + <td> </td> + <xsl:apply-templates select="." mode="ansi-tabular"/> + </tr> + </xsl:for-each> + </table> +</xsl:template> + +<xsl:template match="funcdef" mode="ansi-tabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="ansi-tabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="ansi-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="ansi-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-tabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="ansi-tabular"> + <td> + <code>void)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="varargs" mode="ansi-tabular"> + <td> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef" mode="ansi-tabular"> + <xsl:variable name="type"> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type" mode="ansi-tabular"/> + </xsl:when> + <xsl:when test="normalize-space(parameter/preceding-sibling::node()[not(self::parameter)]) != ''"> + <xsl:copy-of select="parameter/preceding-sibling::node()[not(self::parameter)]"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$type != '' and funcparams"> + <td> + <xsl:copy-of select="$type"/> + <xsl:text> </xsl:text> + </td> + <td> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type/following-sibling::*" mode="ansi-tabular"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*" mode="ansi-tabular"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + </xsl:when> + <xsl:otherwise> + <td> + <xsl:apply-templates select="parameter/preceding-sibling::node()[not(self::parameter)]" mode="ansi-tabular"/> + <xsl:text> </xsl:text> + </td> + <td> + <xsl:apply-templates select="parameter" mode="ansi-tabular"/> + <xsl:apply-templates select="parameter/following-sibling::*[not(self::parameter)]" mode="ansi-tabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="ansi-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="ansi-tabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="ansi-tabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="ansi-tabular"> + <code>(</code> + <xsl:apply-templates/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> + +<xsl:variable name="default-classsynopsis-language">java</xsl:variable> + +<xsl:template match="classsynopsis |fieldsynopsis |methodsynopsis |constructorsynopsis |destructorsynopsis"> + <xsl:param name="language"> + <xsl:choose> + <xsl:when test="@language"> + <xsl:value-of select="@language"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$default-classsynopsis-language"/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + + <xsl:choose> + <xsl:when test="$language='java' or $language='Java'"> + <xsl:apply-templates select="." mode="java"/> + </xsl:when> + <xsl:when test="$language='perl' or $language='Perl'"> + <xsl:apply-templates select="." mode="perl"/> + </xsl:when> + <xsl:when test="$language='idl' or $language='IDL'"> + <xsl:apply-templates select="." mode="idl"/> + </xsl:when> + <xsl:when test="$language='cpp' or $language='c++' or $language='C++'"> + <xsl:apply-templates select="." mode="cpp"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized language on </xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text>: </xsl:text> + <xsl:value-of select="$language"/> + </xsl:message> + <xsl:apply-templates select="."> + <xsl:with-param name="language" select="$default-classsynopsis-language"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="synop-break"> + <xsl:if test="parent::classsynopsis or (following-sibling::fieldsynopsis |following-sibling::methodsynopsis |following-sibling::constructorsynopsis |following-sibling::destructorsynopsis)"> + <xsl:text> +. +</xsl:text> + </xsl:if> +</xsl:template> + + +<!-- ===== Java ======================================================== --> + +<xsl:template match="classsynopsis" mode="java"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="ooclass[1]" mode="java"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text> extends</xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="java"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text>implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="java"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text>throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="java"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="java"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="java"> + <xsl:apply-templates mode="java"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="java"> + <xsl:choose> + <xsl:when test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="java"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="java"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="java"> + <xsl:param name="indent">0</xsl:param> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>,</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:if test="$indent > 0"> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string"> </xsl:with-param> + <xsl:with-param name="count" select="$indent + 1"/> + </xsl:call-template> + </xsl:if> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template mode="java" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="decl"> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="java"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="java"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="java"/> + </xsl:variable> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$decl"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="java"> + <xsl:with-param name="indent" select="string-length($decl)"/> + </xsl:apply-templates> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    throws </xsl:text> + <xsl:apply-templates select="exceptionname" mode="java"/> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="java"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== C++ ========================================================= --> + +<xsl:template match="classsynopsis" mode="cpp"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="ooclass[1]" mode="cpp"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>: </xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="cpp"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text> implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="cpp"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text> throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="cpp"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="cpp"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="cpp"> + <xsl:apply-templates mode="cpp"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="cpp"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="cpp"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="cpp"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="cpp"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template mode="cpp" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="cpp"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="cpp"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="cpp"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="cpp"/> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    throws </xsl:text> + <xsl:apply-templates select="exceptionname" mode="cpp"/> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="cpp"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== IDL ========================================================= --> + +<xsl:template match="classsynopsis" mode="idl"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>interface </xsl:text> + <xsl:apply-templates select="ooclass[1]" mode="idl"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>: </xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="idl"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text> implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="idl"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text> throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="idl"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="idl"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="idl"> + <xsl:apply-templates mode="idl"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="idl"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="idl"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="idl"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="idl"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template mode="idl" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="idl"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="idl"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="idl"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="idl"/> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    raises(</xsl:text> + <xsl:apply-templates select="exceptionname" mode="idl"/> + <xsl:text>)</xsl:text> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="idl"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== Perl ======================================================== --> + +<xsl:template match="classsynopsis" mode="perl"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>package </xsl:text> + <xsl:apply-templates select="ooclass[1]" mode="perl"/> + <xsl:text>;</xsl:text> + <xsl:text> +. +</xsl:text> + + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>@ISA = (</xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="perl"/> + <xsl:text>);</xsl:text> + <xsl:text> +. +</xsl:text> + </xsl:if> + + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="perl"/> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="perl"> + <xsl:apply-templates mode="perl"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="perl"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="perl"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="perl"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="perl"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template mode="perl" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>sub </xsl:text> + + <xsl:apply-templates select="methodname" mode="perl"/> + <xsl:text> { ... };</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * DocBook 5 allows linking elements (link, olink, and xref) --> +<!-- * within the OO *synopsis elements (classsynopsis, fieldsynopsis, --> +<!-- * methodsynopsis, constructorsynopsis, destructorsynopsis) and --> +<!-- * their children. So we need to have mode="java|cpp|idl|perl" --> +<!-- * per-mode matches for those linking elements in order for them --> +<!-- * to be processed as expected. --> + +<xsl:template match="link|olink|xref" mode="java"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="cpp"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="idl"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="perl"> + <xsl:apply-templates select="."/> +</xsl:template> + +</xsl:stylesheet> + diff --git a/docs/xsl-generic/manpages/info.xsl b/docs/xsl-generic/manpages/info.xsl new file mode 100644 index 00000000..36dded00 --- /dev/null +++ b/docs/xsl-generic/manpages/info.xsl @@ -0,0 +1,630 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:date="http://exslt.org/dates-and-times" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="date exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: info.xsl 7087 2007-07-19 07:20:38Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <xsl:variable name="blurb-indent"> + <xsl:choose> + <xsl:when test="not($man.indent.blurbs = 0)"> + <xsl:value-of select="$man.indent.width"/> + </xsl:when> + <xsl:when test="not($man.indent.refsect = 0)"> + <!-- * "zq" is the name of a register we set for --> + <!-- * preserving the original default indent value --> + <!-- * when $man.indent.refsect is non-zero; --> + <!-- * "u" is a roff unit specifier --> + <xsl:text>\n(zqu</xsl:text> + </xsl:when> + <xsl:otherwise/> <!-- * otherwise, just leave it empty --> + </xsl:choose> + </xsl:variable> + + <!-- ================================================================== --> + <!-- * About the $info param used in this stylesheet --> + <!-- * --> + <!-- * The $info param is a "master info" node set that contains --> + <!-- * the entire contents of the *info child of the current --> + <!-- * Refentry, plus the entire contents of the *info children of --> + <!-- * all ancestors of the current Refentry, in document order. --> + <!-- * --> + <!-- * We try to find a "best match" for selecting content from --> + <!-- * $infor; we look through it in reverse document order until we --> + <!-- * can find something usable. --> + <!-- * --> + <!-- * Specifically what the basic metadata-gathering XPath expression --> + <!-- * in this stylesheet does is: --> + <!-- * --> + <!-- * 1. Look through the entire "master info" node set.--> + <!-- * 2. Get the last node in the set that contains, for --> + <!-- * example, an Author element. That amounts to being the --> + <!-- * closest *info node to the Refentry - either its *info --> + <!-- * child, or the *info node of its closest ancestor that --> + <!-- * contains an Author. --> + + <!-- ================================================================== --> + <!-- * Get user "refentry metadata" preferences --> + <!-- ================================================================== --> + <!-- * The DocBook XSL stylesheets include several user-configurable --> + <!-- * global stylesheet parameters for controlling refentry metadata --> + <!-- * gathering. Those parameters are not read directly by the other --> + <!-- * refentry metadata-gathering templates. Instead, they are read --> + <!-- * only by the get.refentry.metadata.prefs template, which --> + <!-- * assembles them into a structure that is then passed to the --> + <!-- * other refentry metadata-gathering template. --> + + <xsl:variable name="get.refentry.metadata.prefs"> + <!-- * get.refentry.metadata.prefs is in common/refentry.xsl --> + <xsl:call-template name="get.refentry.metadata.prefs"/> + </xsl:variable> + + <xsl:variable name="refentry.metadata.prefs" + select="exsl:node-set($get.refentry.metadata.prefs)"/> + + <!-- * ============================================================== --> + <!-- * Get content for Author metadata field. --> + <!-- * ============================================================== --> + + <!-- * The make.roff.metatada.author template and metadata.author --> + <!-- * mode are used only for populating the Author field in the --> + <!-- * metadata "top comment" we embed in roff source of each page. --> + + <xsl:template name="make.roff.metadata.author"> + <xsl:param name="info"/> + <xsl:choose> + <xsl:when test="$info//author"> + <xsl:apply-templates + select="(($info[//author])[last()]//author)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//corpauthor"> + <xsl:apply-templates + select="(($info[//corpauthor])[last()]//corpauthor)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//editor"> + <xsl:apply-templates + select="(($info[//editor])[last()]//editor)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//corpcredit"> + <xsl:apply-templates + select="(($info[//corpcredit])[last()]//corpcredit)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//othercredit"> + <xsl:apply-templates + select="(($info[//othercredit])[last()]//othercredit)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//collab"> + <xsl:apply-templates + select="(($info[//collab])[last()]//collab)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//orgname"> + <xsl:apply-templates + select="(($info[//orgname])[last()]//orgname)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//publishername"> + <xsl:apply-templates + select="(($info[//publishername])[last()]//publishername)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no author info found --> + </xsl:choose> + </xsl:template> + + <xsl:template match="author|editor|othercredit|collab" mode="metadata.author"> + <xsl:choose> + <xsl:when test="collabname"> + <!-- * If this node is a Collab, then it should have a --> + <!-- * Collabname child, so get that. --> + <xsl:variable name="contents"> + <xsl:apply-templates select="collabname"/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, this node is not a Collab, but instead --> + <!-- * an author|editor|othercredit, which must have a name --> + <!-- * of some kind; so get that name --> + <xsl:call-template name="person.name.normalized"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test=".//email|address/otheraddr/ulink"> + <xsl:text> </xsl:text> + <!-- * For each attribution found, use only the first e-mail --> + <!-- * address or ulink value found --> + <xsl:apply-templates select="(.//email|address/otheraddr/ulink)[1]" + mode="metadata.author"/> + </xsl:if> + </xsl:template> + + <xsl:template match="email|address/otheraddr/ulink" mode="metadata.author"> + <xsl:text><</xsl:text> + <xsl:choose> + <xsl:when test="self::email"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:when test="self::ulink"> + <xsl:variable name="contents"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + </xsl:choose> + <xsl:text>></xsl:text> + </xsl:template> + + <xsl:template match="corpauthor|corpcredit|orgname|publishername" mode="metadata.author"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Assemble the AUTHOR/AUTHORS section --> + <!-- * ============================================================== --> + + <xsl:template name="author.section"> + <xsl:param name="info"/> + <!-- * The $info param is a "master info" node set that contains --> + <!-- * the entires contents of the *info child of the current --> + <!-- * Refentry, plus the entire contents of the *info children of --> + <!-- * all ancestors of the current Refentry, in document order. --> + <xsl:choose> + <xsl:when test="$info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit|$info/orgname| + $info/publishername|$info/publisher"> + <xsl:variable name="authorcount"> + <xsl:value-of + select="count( + $info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit)"> + </xsl:value-of> + </xsl:variable> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="make.authorsecttitle"> + <xsl:with-param name="authorcount" select="$authorcount"/> + </xsl:call-template> + <!-- * Now output all the actual author, editor, etc. content --> + <xsl:for-each + select="$info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit|$info/orgname| + $info/publishername|$info/publisher"> + <xsl:apply-templates select="." mode="authorsect"/> + </xsl:for-each> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no author info found --> + </xsl:choose> + </xsl:template> + + <xsl:template name="make.authorsecttitle"> + <!-- * If we have exactly one attributable person/entity, then output --> + <!-- * localized gentext for 'Author'; otherwise, output 'Authors'. --> + <xsl:param name="authorcount"/> + <xsl:param name="authorsecttitle"> + <xsl:choose> + <xsl:when test="$authorcount = 1"> + <xsl:text>Author</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>Authors</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="$authorsecttitle"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + </xsl:template> + + <xsl:template match="author|editor|othercredit" mode="authorsect"> + <xsl:variable name="person-name"> + <xsl:call-template name="person.name.normalized"/> + </xsl:variable> + <!-- * If we have a person-name or email or ulink content, then --> + <!-- * output name and email or ulink content on the same line --> + <xsl:choose> + <xsl:when test="not($person-name = '') or .//email or address/otheraddr/ulink"> + <xsl:text>.PP </xsl:text> + <!-- * Display person name in bold --> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($person-name)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>.br </xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * Display affiliation(s) on separate lines --> + <xsl:apply-templates select="affiliation" mode="authorsect"/> + <!-- * Display direct-child addresses on separate lines --> + <xsl:apply-templates select="address" mode="authorsect"/> + <!-- * Call template for handling various attribution possibilities --> + <xsl:call-template name="attribution"/> + </xsl:template> + + <xsl:template match="collab" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="collabname"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <xsl:text> </xsl:text> + <!-- * Display affilition(s) on separate lines --> + <xsl:apply-templates select="affiliation" mode="authorsect"/> + </xsl:template> + + <xsl:template match="corpauthor|corpcredit|orgname|publishername" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:if test="self::publishername"> + <!-- * Display localized "Publisher" gentext --> + <xsl:call-template name="publisher.attribution"/> + </xsl:if> + </xsl:template> + + <xsl:template match="publisher" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="publishername"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <!-- * Display addresses on separate lines --> + <xsl:apply-templates select="address" mode="authorsect"/> + <!-- * Display localized "Publisher" literal --> + <xsl:call-template name="publisher.attribution"/> + </xsl:template> + + <xsl:template name="publisher.attribution"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Publisher'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:template> + + <xsl:template match="email|address/otheraddr/ulink" mode="authorsect"> + <xsl:choose> + <xsl:when test="preceding-sibling::*[descendant-or-self::email] + or preceding-sibling::address/otheraddr/ulink + or ancestor::address[preceding-sibling::*[descendant-or-self::email]] + or ancestor::address[preceding-sibling::address/otheraddr/ulink]"> + <!-- * This is not the first instance, so do nothing. --> + </xsl:when> + <xsl:otherwise> + <!-- * This is first instances of an e-mail address or ulink, --> + <!-- * so put a space before it. --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * Note that the reason for the \& character after the opening --> + <!-- * angle bracket and before the closing angle bracket is to --> + <!-- * prevent groff from inserting a linebreak at those points and --> + <!-- * outputting a hyphen character where the break occurs --> + <xsl:text><\&</xsl:text> + <xsl:choose> + <xsl:when test="self::email"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:when test="self::ulink"> + <xsl:variable name="contents"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + </xsl:choose> + <xsl:text>\&></xsl:text> + <xsl:choose> + <xsl:when test="not(following-sibling::*[descendant-or-self::email] + or following-sibling::address/otheraddr/ulink + or ancestor::address[following-sibling::*[descendant-or-self::email]] + or ancestor::address[following-sibling::address/otheraddr/ulink])"> + <!-- * This is the final instance, so do nothing. --> + </xsl:when> + <xsl:otherwise> + <!-- * Separate multiple e-mail addresses or ulinks with a comma --> + <xsl:text>, </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="affiliation" mode="authorsect"> + <!-- * Get the string value of the contents of this Affiliation. If the --> + <!-- * affiliation only contains an Address child whose only content is --> + <!-- * an email address or ulink, then these contents will end up empty. --> + <xsl:variable name="contents"> + <xsl:apply-templates mode="authorsect"/> + </xsl:variable> + <!-- * If contents are actually empty except for an email address --> + <!-- * or ulink, then output nothing. --> + <xsl:if test="$contents != ''"> + <xsl:text>.br </xsl:text> + <xsl:for-each select="shortaffil|jobtitle|orgname|orgdiv|address"> + <!-- * only display output of nodes other than email or ulink --> + <xsl:apply-templates select="node()[not(self::email) and not(self::otheraddr/ulink)]"/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * only add comma if the node has a child node other than --> + <!-- * an email address or ulink --> + <xsl:if test="child::node()[not(self::email) and not(self::otheraddr/ulink)]"> + <xsl:text>, </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * put a line break after every Affiliation instance except --> + <!-- * the last one in the set --> + <xsl:text>.br </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:template> + + <xsl:template match="address" mode="authorsect"> + <xsl:variable name="contents" + select="normalize-space(node()[not(self::email) + and not(self::otheraddr/ulink)])"/> + <!-- * If this contents of this Address do not contain anything except --> + <!-- * an email address or ulink, then output nothing. --> + <xsl:if test="$contents != ''"> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + <!--* Skip email and ulink descendants of Address (rendered elsewhere) --> + <xsl:apply-templates select="node()[not(self::email) and not(self::otheraddr/ulink)]"/> + </xsl:if> + </xsl:template> + + <xsl:template name="attribution"> + <!-- * Determine appropriate attribution for a particular person's role. --> + <xsl:choose> + <!-- * if we have a *blurb or contrib, just use that --> + <xsl:when test="contrib|personblurb|authorblurb"> + <xsl:apply-templates select="contrib|personblurb|authorblurb" mode="authorsect"/> + <xsl:text> </xsl:text> + </xsl:when> + <!-- * If we have no *blurb or contrib, but this is an Author or --> + <!-- * Editor, then render the corresponding localized gentext --> + <xsl:when test="self::author"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Author'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <xsl:when test="self::editor"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Editor'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <!-- * If we have no *blurb or contrib, but this is an Othercredit, --> + <!-- * check value of Class attribute and use corresponding gentext. --> + <xsl:when test="self::othercredit"> + <xsl:choose> + <xsl:when test="@class and @class != 'other'"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="@class"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * We have an Othercredit, but not usable value for the Class --> + <!-- * attribute, so nothing to show, do nothing --> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- * We have no *blurb or contrib or anything else we can use to --> + <!-- * display appropriate attribution for this person, so do nothing --> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="personblurb|authorblurb" mode="authorsect"> + <xsl:call-template name="mark.up.blurb.or.contrib"/> + <!-- * yeah, it's possible for a *blurb to have a "title" --> + <xsl:apply-templates select="title"/> + <xsl:apply-templates select="*[not(self::title)]"/> + </xsl:template> + + <xsl:template match="personblurb/title|authorblurb/title"> + <!-- * always render period after title --> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text>.</xsl:text> + <!-- * render space after Title+period if the title is followed --> + <!-- * by something element content --> + <xsl:if test="following-sibling::*[name() != '']"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="contrib" mode="authorsect"> + <xsl:call-template name="mark.up.blurb.or.contrib"/> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template name="mark.up.blurb.or.contrib"> + <xsl:choose> + <!-- * If this *blurb has a sibling "name" element of some kind, then --> + <!-- * we are already outputting the name content, and we need to --> + <!-- * indent the *blurb content after that. --> + <xsl:when + test="../personname|../surname|../firstname + |../othername|../lineage|../honorific + |../affiliation|../email|../address"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, we have no "name" content, so don't indent; --> + <!-- * instead, decide if we need a .PP or just a .br --> + <xsl:choose> + <xsl:when test="not(preceding-sibling::*)"> + <!-- * if this *blurb or contrib has no preceding --> + <!-- * siblings, then we need to start a new paragraph --> + <xsl:text>.PP</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, this has no preceding siblings, so --> + <!-- * just put a linebreak --> + <xsl:text>.br</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + <xsl:text> </xsl:text> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Assemble the COPYRIGHT section --> + <!-- * ============================================================== --> + <!-- * The COPYRIGHT section is output only if a copyright or --> + <!-- * legalnotice is found. It contains the copyright contents --> + <!-- * followed by the legalnotice contents. --> + <xsl:template name="copyright.section"> + <xsl:param name="info"/> + <xsl:choose> + <xsl:when test="$info//copyright|$info//legalnotice"> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key">Copyright</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + <!-- * the copyright mode="titlepage.mode" template is --> + <!-- * imported from the HTML stylesheets --> + <xsl:for-each select=" + (($info[//copyright])[last()]//copyright) + | (($info[//legalnotice])[last()]//legalnotice)"> + <xsl:choose> + <xsl:when test="local-name(.) = 'copyright'"> + <xsl:variable name="contents"> + <xsl:apply-templates select="." mode="titlepage.mode"/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="titlepage.mode"/> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no copyright or legalnotice found --> + </xsl:choose> + </xsl:template> + + <xsl:template match="legalnotice"> + <xsl:apply-templates/> + </xsl:template> + + <!-- * ============================================================== --> + + <!-- * suppress refmeta and all *info (we grab what we need from them --> + <!-- * elsewhere) --> + + <xsl:template match="refmeta"/> + + <xsl:template match="info|refentryinfo|referenceinfo|refsynopsisdivinfo + |refsectioninfo|refsect1info|refsect2info|refsect3info + |setinfo|bookinfo|articleinfo|chapterinfo|sectioninfo + |sect1info|sect2info|sect3info|sect4info|sect5info + |partinfo|prefaceinfo|appendixinfo|docinfo"/> + + <!-- ============================================================== --> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/inline.xsl b/docs/xsl-generic/manpages/inline.xsl new file mode 100644 index 00000000..a88369c8 --- /dev/null +++ b/docs/xsl-generic/manpages/inline.xsl @@ -0,0 +1,190 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + version='1.0'> + +<!-- ******************************************************************** + $Id: inline.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="replaceable|varname"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="option|userinput|envar|errorcode|constant|markup"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="classname"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="command"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="type[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="function[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="parameter[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="filename"> + <!-- * add hyphenation suppression in Filename output only if --> + <!-- * break.after.slash is also non-zero --> + <xsl:if test="$man.hyphenate.filenames = 0 and + $man.break.after.slash = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="emphasis"> + <xsl:choose> + <xsl:when test=" + @role = 'bold' or + @role = 'strong' or + @remap = 'B'"> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="optional"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + <xsl:apply-templates/> + <xsl:value-of select="$arg.choice.opt.close.str"/> +</xsl:template> + +<xsl:template name="do-citerefentry"> + <xsl:param name="refentrytitle" select="''"/> + <xsl:param name="manvolnum" select="''"/> + <xsl:variable name="title"> + <xsl:value-of select="$refentrytitle"/> + </xsl:variable> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text>(</xsl:text> + <xsl:value-of select="$manvolnum"/> + <xsl:text>)</xsl:text> +</xsl:template> + +<xsl:template match="citerefentry"> + <xsl:call-template name="do-citerefentry"> + <xsl:with-param name="refentrytitle" select="refentrytitle"/> + <xsl:with-param name="manvolnum" select="manvolnum"/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="trademark|productname"> + <xsl:apply-templates/> + <xsl:choose> + <!-- * Just use true Unicode chars for copyright, trademark, etc., --> + <!-- * symbols (by default, we later automatically translate them --> + <!-- * with the apply-string-subst-map template, or with the --> + <!-- * default character map, if man.charmap.enabled is true). --> + <xsl:when test="@class = 'copyright'"> + <xsl:text>©</xsl:text> + </xsl:when> + <xsl:when test="@class = 'registered'"> + <xsl:text>®</xsl:text> + </xsl:when> + <xsl:when test="@class = 'service'"> + <xsl:text>℠</xsl:text> + </xsl:when> + <xsl:when test="@class = 'trade'"> + <xsl:text>™</xsl:text> + </xsl:when> + <!-- * for Trademark element, render a trademark symbol by default --> + <!-- * even if no "class" value is specified --> + <xsl:when test="self::trademark" > + <xsl:text>™</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise we have a Productname with no value for the --> + <!-- * "class" attribute, so don't render any symbol by default --> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- * span seems to sneak through into output sometimes, possibly due --> +<!-- * to failed Olink processing; so we need to catch it --> +<xsl:template match="span"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="inlinemediaobject"> + <xsl:apply-templates/> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/lists.xsl b/docs/xsl-generic/manpages/lists.xsl new file mode 100644 index 00000000..4c18c80c --- /dev/null +++ b/docs/xsl-generic/manpages/lists.xsl @@ -0,0 +1,368 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: lists.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:variable name="list-indent"> + <xsl:choose> + <xsl:when test="not($man.indent.lists = 0)"> + <xsl:value-of select="$man.indent.width"/> + </xsl:when> + <xsl:when test="not($man.indent.refsect = 0)"> + <!-- * "zq" is the name of a register we set for --> + <!-- * preserving the original default indent value --> + <!-- * when $man.indent.refsect is non-zero; --> + <!-- * "u" is a roff unit specifier --> + <xsl:text>\n(zqu</xsl:text> + </xsl:when> + <xsl:otherwise/> <!-- * otherwise, just leave it empty --> + </xsl:choose> +</xsl:variable> + +<!-- ================================================================== --> + +<xsl:template match="para[ancestor::listitem or ancestor::step or ancestor::glossdef]| + simpara[ancestor::listitem or ancestor::step or ancestor::glossdef]| + remark[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:call-template name="mixed-block"/> + <xsl:text> </xsl:text> + <xsl:if test="following-sibling::*[1][ + self::para or + self::simpara or + self::remark + ]"> + <!-- * Make sure multiple paragraphs within a list item don't --> + <!-- * merge together. --> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="variablelist|glosslist"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="varlistentry|glossentry"> + <xsl:text>.PP </xsl:text> + <xsl:for-each select="term|glossterm"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * if we have multiple terms in the same varlistentry, generate --> + <!-- * a separator (", " by default) and/or an additional line --> + <!-- * break after each one except the last --> + <!-- * --> + <!-- * note that it is not valid to have multiple glossterms --> + <!-- * within a glossentry, so this logic never gets exercised --> + <!-- * for glossterms (every glossterm is always the last in --> + <!-- * its parent glossentry) --> + <xsl:value-of select="$variablelist.term.separator"/> + <xsl:if test="not($variablelist.term.break.after = '0')"> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="varlistentry/term"/> +<xsl:template match="glossentry/glossterm"/> + +<xsl:template match="variablelist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + glosslist[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:apply-templates/> + <xsl:if test="following-sibling::node() or + parent::para[following-sibling::node()] or + parent::simpara[following-sibling::node()] or + parent::remark[following-sibling::node()]"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="varlistentry/listitem|glossentry/glossdef"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="itemizedlist/listitem"> + <!-- * We output a real bullet here (rather than, "\(bu", --> + <!-- * the roff bullet) because, when we do character-map --> + <!-- * processing before final output, the character-map will --> + <!-- * handle conversion of the • to "\(bu" for us --> + <xsl:text> </xsl:text> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:text>\h'-</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text>'</xsl:text> + <xsl:text>•</xsl:text> + <xsl:text>\h'+</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent - 1"/> + <xsl:text>'</xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="orderedlist/listitem|procedure/step"> + <xsl:text> </xsl:text> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:text>\h'-</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text>'</xsl:text> + <xsl:if test="count(preceding-sibling::listitem) < 9"> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:number format="1."/> + <xsl:text>\h'+</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent - 2"/> + <xsl:text>'</xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="itemizedlist|orderedlist|procedure"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <!-- * DocBook allows just about any block content to appear in --> + <!-- * lists before the actual list items, so we need to get that --> + <!-- * content (if any) before getting the list items --> + <xsl:apply-templates + select="*[not(self::listitem) and not(self::title)]"/> + <xsl:apply-templates select="listitem"/> + <!-- * If this list is a child of para and has content following --> + <!-- * it, within the same para, then add a blank line and move --> + <!-- * the left margin back to where it was --> + <xsl:if test="parent::para and following-sibling::node()"> + <xsl:text>.sp </xsl:text> + <xsl:text>.RE </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="itemizedlist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + orderedlist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + procedure[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:if test="following-sibling::node() or + parent::para[following-sibling::node()] or + parent::simpara[following-sibling::node()] or + parent::remark[following-sibling::node()]"> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ================================================================== --> + +<!-- * for simplelist type="inline", render it as a comma-separated list --> +<xsl:template match="simplelist[@type='inline']"> + + <!-- * if dbchoice PI exists, use that to determine the choice separator --> + <!-- * (that is, equivalent of "and" or "or" in current locale), or literal --> + <!-- * value of "choice" otherwise --> + <xsl:variable name="localized-choice-separator"> + <xsl:choose> + <xsl:when test="processing-instruction('dbchoice')"> + <xsl:call-template name="select.choice.separator"/> + </xsl:when> + <xsl:otherwise> + <!-- * empty --> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:for-each select="member"> + <xsl:apply-templates/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <xsl:text>, </xsl:text> + <xsl:if test="position() = last() - 1"> + <xsl:if test="$localized-choice-separator != ''"> + <xsl:value-of select="$localized-choice-separator"/> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> +</xsl:template> + +<!-- * if simplelist type is not inline, render it as a one-column vertical --> +<!-- * list (ignoring the values of the type and columns attributes) --> +<xsl:template match="simplelist"> + <xsl:for-each select="member"> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + </xsl:for-each> +</xsl:template> + +<!-- ================================================================== --> + +<!-- * We output Segmentedlist as a table, using tbl(1) markup. There --> +<!-- * is no option for outputting it in manpages in "list" form. --> +<xsl:template match="segmentedlist"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:text>.\" line length increase to cope w/ tbl weirdness </xsl:text> + <xsl:text>.ll +(\n(LLu * 62u / 100u) </xsl:text> + <!-- * .TS = "Table Start" --> + <xsl:text>.TS </xsl:text> + <!-- * first output the table "format" spec, which tells tbl(1) how --> + <!-- * how to format each row and column. --> + <xsl:for-each select=".//segtitle"> + <!-- * l = "left", which hard-codes left-alignment for tabular --> + <!-- * output of all segmentedlist content --> + <xsl:text>l</xsl:text> + </xsl:for-each> + <!-- * last line of table format section must end with a dot --> + <xsl:text>. </xsl:text> + <!-- * optionally suppress output of segtitle --> + <xsl:choose> + <xsl:when test="$man.segtitle.suppress != 0"> + <!-- * non-zero = "suppress", so do nothing --> + </xsl:when> + <xsl:otherwise> + <!-- * "0" = "do not suppress", so output the segtitle(s) --> + <xsl:apply-templates select=".//segtitle" mode="table-title"/> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates/> + <!-- * .TE = "Table End" --> + <xsl:text>.TE </xsl:text> + <xsl:text>.\" line length decrease back to previous value </xsl:text> + <xsl:text>.ll -(\n(LLu * 62u / 100u) </xsl:text> + <!-- * put a blank line of space below the table --> + <xsl:text>.sp </xsl:text> +</xsl:template> + +<xsl:template match="segmentedlist/segtitle" mode="table-title"> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * tbl(1) treats tab characters as delimiters between --> + <!-- * cells; so we need to output a tab after each except --> + <!-- * segtitle except the last one --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="segmentedlist/seglistitem"> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="segmentedlist/seglistitem/seg"> + <!-- * the “T{" and “T}” stuff are delimiters to tell tbl(1) that --> + <!-- * the delimited contents are "text blocks" that groff(1) --> + <!-- * needs to process --> + <xsl:text>T{ </xsl:text> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> T}</xsl:text> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * tbl(1) treats tab characters as delimiters between --> + <!-- * cells; so we need to output a tab after each except --> + <!-- * segtitle except the last one --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/other.xsl b/docs/xsl-generic/manpages/other.xsl new file mode 100644 index 00000000..3a3afee9 --- /dev/null +++ b/docs/xsl-generic/manpages/other.xsl @@ -0,0 +1,674 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: other.xsl 6863 2007-06-23 09:08:06Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- * This file contains named templates related to things other than --> +<!-- * just assembling the actual text of the main text flow of each man --> +<!-- * page. This "other" stuff currently amounts to these steps: --> +<!-- * --> +<!-- * - get contents of the "map" used to convert special characters --> +<!-- * - output boilerplate messages --> +<!-- * - escape backslash, dot, dash, and apostrophe characters --> +<!-- * - convert non-breaking spaces --> +<!-- * - add a comment to top part of roff source of each page --> +<!-- * - make a .TH title line (for controlling page header/footer) --> +<!-- * - set hyphenation, alignment, indent & line-breaking defaults --> +<!-- * - "prepare" the complete man page contents for final output --> +<!-- * - write the actual man file to the filesystem --> +<!-- * - write any "stub" pages to the filesystem --> +<!-- * --> +<!-- * The templates in this file are actually called only once per --> +<!-- * each Refentry; they are just in a separate file for the purpose --> +<!-- * of keeping things modular. --> + +<!-- ==================================================================== --> + +<xsl:preserve-space elements="*"/> + +<xsl:strip-space elements=" +abstract affiliation anchor answer appendix area areaset areaspec +artheader article audiodata audioobject author authorblurb authorgroup +beginpage bibliodiv biblioentry bibliography biblioset blockquote book +bookbiblio bookinfo callout calloutlist caption caution chapter +citerefentry cmdsynopsis co collab colophon colspec confgroup +copyright dedication docinfo editor entrytbl epigraph equation +example figure footnote footnoteref formalpara funcprototype +funcsynopsis glossary glossdef glossdiv glossentry glosslist graphicco +group highlights imagedata imageobject imageobjectco important index +indexdiv indexentry indexterm informalequation informalexample +informalfigure informaltable inlineequation inlinemediaobject +itemizedlist itermset keycombo keywordset legalnotice listitem lot +mediaobject mediaobjectco menuchoice msg msgentry msgexplan msginfo +msgmain msgrel msgset msgsub msgtext note objectinfo +orderedlist othercredit part partintro preface printhistory procedure +programlistingco publisher qandadiv qandaentry qandaset question +refentry reference refmeta refnamediv refsection refsect1 refsect1info refsect2 +refsect2info refsect3 refsect3info refsynopsisdiv refsynopsisdivinfo +revhistory revision row sbr screenco screenshot sect1 sect1info sect2 +sect2info sect3 sect3info sect4 sect4info sect5 sect5info section +sectioninfo seglistitem segmentedlist seriesinfo set setindex setinfo +shortcut sidebar simplelist simplesect spanspec step subject +subjectset substeps synopfragment table tbody textobject tfoot tgroup +thead tip toc tocchap toclevel1 toclevel2 toclevel3 toclevel4 +toclevel5 tocpart varargs variablelist varlistentry videodata +videoobject void warning subjectset + +classsynopsis +constructorsynopsis +destructorsynopsis +fieldsynopsis +methodparam +methodsynopsis +ooclass +ooexception +oointerface +simplemsgentry +manvolnum + +db:abstract db:affiliation db:anchor db:answer db:appendix db:area db:areaset db:areaspec +db:artheader db:article db:audiodata db:audioobject db:author db:authorblurb db:authorgroup +db:beginpage db:bibliodiv db:biblioentry db:bibliography db:biblioset db:blockquote db:book +db:bookbiblio db:bookinfo db:callout db:calloutlist db:caption db:caution db:chapter +db:citerefentry db:cmdsynopsis db:co db:collab db:colophon db:colspec db:confgroup +db:copyright db:dedication db:docinfo db:editor db:entrytbl db:epigraph db:equation +db:example db:figure db:footnote db:footnoteref db:formalpara db:funcprototype +db:funcsynopsis db:glossary db:glossdef db:glossdiv db:glossentry db:glosslist db:graphicco +db:group db:highlights db:imagedata db:imageobject db:imageobjectco db:important db:index +db:indexdiv db:indexentry db:indexterm db:informalequation db:informalexample +db:informalfigure db:informaltable db:inlineequation db:inlinemediaobject +db:itemizedlist db:itermset db:keycombo db:keywordset db:legalnotice db:listitem db:lot +db:mediaobject db:mediaobjectco db:menuchoice db:msg db:msgentry db:msgexplan db:msginfo +db:msgmain db:msgrel db:msgset db:msgsub db:msgtext db:note db:objectinfo +db:orderedlist db:othercredit db:part db:partintro db:preface db:printhistory db:procedure +db:programlistingco db:publisher db:qandadiv db:qandaentry db:qandaset db:question +db:refentry db:reference db:refmeta db:refnamediv db:refsection db:refsect1 db:refsect1info +db:refsect2 +db:refsect2info db:refsect3 db:refsect3info db:refsynopsisdiv db:refsynopsisdivinfo +db:revhistory db:revision db:row db:sbr db:screenco db:screenshot db:sect1 db:sect1info db:sect2 +db:sect2info db:sect3 db:sect3info db:sect4 db:sect4info db:sect5 db:sect5info db:section +db:sectioninfo db:seglistitem db:segmentedlist db:seriesinfo db:set db:setindex db:setinfo +db:shortcut db:sidebar db:simplelist db:simplesect db:spanspec db:step db:subject +db:subjectset db:substeps db:synopfragment db:table db:tbody db:textobject db:tfoot db:tgroup +db:thead db:tip db:toc db:tocchap db:toclevel1 db:toclevel2 db:toclevel3 db:toclevel4 +db:toclevel5 db:tocpart db:varargs db:variablelist db:varlistentry db:videodata +db:videoobject db:void db:warning db:subjectset + +db:classsynopsis +db:constructorsynopsis +db:destructorsynopsis +db:fieldsynopsis +db:methodparam +db:methodsynopsis +db:ooclass +db:ooexception +db:oointerface +db:simplemsgentry +db:manvolnum +"/> + +<!-- ==================================================================== --> +<!-- * Get character map contents --> +<!-- ==================================================================== --> + + <xsl:variable name="man.charmap.contents"> + <xsl:if test="$man.charmap.enabled != 0"> + <xsl:call-template name="read-character-map"> + <xsl:with-param name="use.subset" select="$man.charmap.use.subset"/> + <xsl:with-param name="subset.profile" select="$man.charmap.subset.profile"/> + <xsl:with-param name="uri"> + <xsl:choose> + <xsl:when test="$man.charmap.uri != ''"> + <xsl:value-of select="$man.charmap.uri"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="'../manpages/charmap.groff.xsl'"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:variable> + +<!-- ==================================================================== --> + +<xsl:template name="root.messages"> + <xsl:param name="refname"/> + <!-- redefine this any way you'd like to output messages --> + <!-- DO NOT OUTPUT ANYTHING FROM THIS TEMPLATE --> + <!-- Example: + <xsl:if test="//foo"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>limitation</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Output for foo element is not yet supported.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + --> +</xsl:template> + +<!-- ==================================================================== --> +<!-- * Escape roff special chars --> +<!-- ==================================================================== --> + +<!-- ******************************************************************** --> +<!-- * --> +<!-- * The backslash, dot, dash, and apostrophe (\, ., -, ') characters --> +<!-- * have special meaning for roff, so before we do any other --> +<!-- * processing, we must escape those characters where they appear in --> +<!-- * the source content. --> +<!-- * --> +<!-- * Here we also deal with replacing U+00a0 (non-breaking space) with --> +<!-- * its roff equivalent --> +<!-- * --> +<!-- ******************************************************************** --> + +<xsl:template match="//refentry//text()"> + <xsl:call-template name="escape.roff.specials"> + <xsl:with-param name="content"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.roff.specials"> + <xsl:param name="content"/> + <xsl:call-template name="convert.nobreak-space"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.apostrophe"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.dash"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.dot"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.backslash"> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.backslash"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">\</xsl:with-param> + <!-- * we use "\e" instead of "\\" because the groff docs say --> + <!-- * that's the correct thing to do; also because testing --> + <!-- * shows that "\\" doesn't always work as expected; for --> + <!-- * example, "\\" within a table seems to mess things up --> + <xsl:with-param name="replacement">\e</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.dot"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">.</xsl:with-param> + <xsl:with-param name="replacement">\.</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.dash"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">-</xsl:with-param> + <xsl:with-param name="replacement">\-</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.apostrophe"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">'</xsl:with-param> + <xsl:with-param name="replacement">\'</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="convert.nobreak-space"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target"> </xsl:with-param> + <!-- * A no-break space can be written two ways in roff; the --> + <!-- * difference, according to the "Page Motions" node in the --> + <!-- * groff info page, is: --> + <!-- * --> + <!-- * "\ " = --> + <!-- * An unbreakable and unpaddable (i.e. not expanded --> + <!-- * during filling) space. --> + <!-- * --> + <!-- * "\~" = --> + <!-- * An unbreakable space that stretches like a normal --> + <!-- * inter-word space when a line is adjusted." --> + <!-- * --> + <!-- * Unfortunately, roff seems to do some weird things with --> + <!-- * long lines that only have words separated by "\~" --> + <!-- * spaces, so it's safer just to stick with the "\ " space --> + <xsl:with-param name="replacement">\ </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * top.comment generates a comment containing metadata for the man --> +<!-- * page; for example, Author, Generator, and Date information --> + + <xsl:template name="top.comment"> + <xsl:param name="info"/> + <xsl:param name="date"/> + <xsl:param name="title"/> + <xsl:param name="manual"/> + <xsl:param name="source"/> + <xsl:text>.\" Title: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$title"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Author: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content"> + <xsl:call-template name="make.roff.metadata.author"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Generator: DocBook </xsl:text> + <xsl:value-of select="$DistroTitle"/> + <xsl:text> v</xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$VERSION"/> + </xsl:call-template> + <xsl:text> <http://docbook.sf.net/></xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.\" Date: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$date"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Manual: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$manual"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Source: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$source"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\"</xsl:text> + <xsl:text> </xsl:text> + </xsl:template> + +<!-- ==================================================================== --> + + <xsl:template name="TH.title.line"> + + <!-- * The exact way that .TH contents are displayed is system- --> + <!-- * dependent; it varies somewhat between OSes and roff --> + <!-- * versions. Below is a description of how Linux systems with --> + <!-- * a modern groff seem to render .TH contents. --> + <!-- * --> + <!-- * title(section) extra3 title(section) <- page header --> + <!-- * extra2 extra1 title(section) <- page footer--> + <!-- * --> + <!-- * Or, using the names with which the man(7) man page refers --> + <!-- * to the various fields: --> + <!-- * --> + <!-- * title(section) manual title(section) <- page header --> + <!-- * source date title(section) <- page footer--> + <!-- * --> + <!-- * Note that while extra1, extra2, and extra3 are all (nominally) --> + <!-- * optional, in practice almost all pages include an "extra1" --> + <!-- * field, which is, universally, a date (in some form), and it is --> + <!-- * always rendered in the same place (the middle footer position) --> + <!-- * --> + <!-- * Here are a couple of examples of real-world man pages that --> + <!-- * have somewhat useful page headers/footers: --> + <!-- * --> + <!-- * gtk-options(7) GTK+ User's Manual gtk-options(7) --> + <!-- * GTK+ 1.2 2003-10-20 gtk-options(7) --> + <!-- * --> + <!-- * svgalib(7) Svgalib User Manual svgalib(7) --> + <!-- * Svgalib 1.4.1 16 December 1999 svgalib(7) --> + <!-- * --> + <xsl:param name="title"/> + <xsl:param name="section"/> + <xsl:param name="extra1"/> + <xsl:param name="extra2"/> + <xsl:param name="extra3"/> + + <xsl:call-template name="mark.subheading"/> + <!-- * Note that we generate quotes around _every_ field in the --> + <!-- * .TH title line, including the "title" and "section" --> + <!-- * fields. That is because we use the contents of those "as --> + <!-- * is", unchanged from the DocBook source; and DTD-based --> + <!-- * validation does not provide a way to constrain them to be --> + <!-- * "space free" --> + <xsl:text>.TH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <xsl:when test="$man.th.title.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($title, 1, $man.th.title.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($title)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" "</xsl:text> + <xsl:value-of select="normalize-space($section)"/> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra1.suppress = 0"> + <!-- * there is no max.length for the extra1 field; the reason --> + <!-- * is, it is almost always a date, and it is not possible --> + <!-- * to truncate dates without changing their meaning --> + <xsl:value-of select="normalize-space($extra1)"/> + </xsl:if> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra2.suppress = 0"> + <xsl:choose> + <!-- * if max.length is non-empty, use value to truncate field --> + <xsl:when test="$man.th.extra2.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($extra2, 1, $man.th.extra2.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($extra2)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra3.suppress = 0"> + <xsl:choose> + <!-- * if max.length is non-empty, use value to truncate field --> + <xsl:when test="$man.th.extra3.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($extra3, 1, $man.th.extra3.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($extra3)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template name="set.default.formatting"> + <!-- * Set default hyphenation, justification, indentation and --> + <!-- * line-breaking --> + <!-- * --> + <!-- * If the value of man.hypenate is zero (the default), then --> + <!-- * disable hyphenation (".nh" = "no hyphenation") --> + <xsl:if test="$man.hyphenate = 0"> + <xsl:text>.\" disable hyphenation </xsl:text> + <xsl:text>.nh </xsl:text> + </xsl:if> + <!-- * If the value of man.justify is zero (the default), then --> + <!-- * disable justification (".ad l" means "adjust to left only") --> + <xsl:if test="$man.justify = 0"> + <xsl:text>.\" disable justification</xsl:text> + <xsl:text> (adjust text to left margin only) </xsl:text> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <xsl:if test="not($man.indent.refsect = 0)"> + <xsl:text>.\" store initial "default indentation value" </xsl:text> + <xsl:text>.nr zq \n(IN </xsl:text> + <xsl:text>.\" adjust default indentation </xsl:text> + <xsl:text>.nr IN </xsl:text> + <xsl:value-of select="$man.indent.width"/> + <xsl:text> </xsl:text> + <xsl:text>.\" adjust indentation of SS headings </xsl:text> + <xsl:text>.nr SN \n(IN </xsl:text> + </xsl:if> + <!-- * Unless the value of man.break.after.slash is zero (the --> + <!-- * default), tell groff that it is OK to break a line --> + <!-- * after a slash when needed. --> + <xsl:if test="$man.break.after.slash != 0"> + <xsl:text>.\" enable line breaks after slashes </xsl:text> + <xsl:text>.cflags 4 / </xsl:text> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The prepare.manpage.contents template is called after all --> + <!-- * other processing has been done, before serializing the --> + <!-- * result of all the other processing. It basically works on --> + <!-- * the result as one big string. --> + <xsl:template name="prepare.manpage.contents"> + <xsl:param name="content" select="''"/> + + <!-- * If user has provided a "local" string-substitution map to --> + <!-- * be applied /before/ the standard string-substitution map, --> + <!-- * apply it. --> + <xsl:variable name="pre.adjusted.content"> + <xsl:choose> + <xsl:when test="$man.string.subst.map.local.pre"> + <!-- * normalized value of man.string.subst.map.local.pre --> + <!-- * is non-empty, so get contents of map and apply them --> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map.local.pre)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * value of man.string.subst.map.local.pre is empty, --> + <!-- * so just copy original contents --> + <xsl:value-of select="$content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- * Apply standard string-substitution map. The main purpose --> + <!-- * of this map is to escape certain characters that have --> + <!-- * special meaning in roff, and to replace certain characters --> + <!-- * used within the stylesheet internally to represent roff --> + <!-- * markup characters. --> + <xsl:variable name="adjusted.content"> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$pre.adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map)/*"/> + </xsl:call-template> + </xsl:variable> + + <!-- * If user has provided a "local" string-substitution map to --> + <!-- * be applied /after/ the standard string-substitution map, --> + <!-- * apply it. --> + <xsl:variable name="post.adjusted.content"> + <xsl:choose> + <xsl:when test="$man.string.subst.map.local.post"> + <!-- * normalized value of man.string.subst.map.local.post --> + <!-- * is non-empty, so get contents of map and apply them --> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map.local.post)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * value of man.string.subst.map.local.post is empty, --> + <!-- * so just copy original contents --> + <xsl:value-of select="$adjusted.content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- * Optionally, apply a character map to replace Unicode --> + <!-- * symbols and special characters. --> + <xsl:choose> + <xsl:when test="$man.charmap.enabled != 0"> + <xsl:call-template name="apply-character-map"> + <xsl:with-param name="content" select="$post.adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.charmap.contents)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * if we reach here, value of $man.charmap.enabled is zero, --> + <!-- * so we just pass the adjusted contents through "as is" --> + <xsl:value-of select="$adjusted.content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="write.man.file"> + <xsl:param name="name"/> + <xsl:param name="section"/> + <xsl:param name="lang"/> + <xsl:param name="content"/> + <xsl:param name="filename"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="$name"/> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + </xsl:param> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="suppress-context-node-name" select="1"/> + <xsl:with-param name="quiet" select="$man.output.quietly"/> + <xsl:with-param + name="message-prolog" + >Note: </xsl:with-param> + <xsl:with-param name="encoding" select="$man.output.encoding"/> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + </xsl:template> + + <!-- ============================================================== --> + + <!-- * A "stub" is sort of alias for another file, intended to be read --> + <!-- * and expanded by soelim(1); it's simply a file whose complete --> + <!-- * contents are just a single line of the following form: --> + <!-- * --> + <!-- * .so manX/realname.X --> + <!-- * --> + <!-- * "realname" is a name of another man-page file. That .so line is --> + <!-- * basically a roff "include" statement. When the man command finds --> + <!-- * it, it calls soelim(1) and includes and displays the contents of --> + <!-- * the manX/realqname.X file. --> + <!-- * --> + <!-- * If a refentry has multiple refnames, we generate a "stub" page for --> + <!-- * each refname found, except for the first one. --> + <xsl:template name="write.stubs"> + <xsl:param name="first.refname"/> + <xsl:param name="section"/> + <xsl:param name="lang"/> + <xsl:for-each select="refnamediv/refname"> + <xsl:if test=". != $first.refname"> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name"> + <xsl:apply-templates/> + </xsl:with-param> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + </xsl:with-param> + <xsl:with-param name="quiet" select="$man.output.quietly"/> + <xsl:with-param name="suppress-context-node-name" select="1"/> + <xsl:with-param name="message-prolog">Note: </xsl:with-param> + <xsl:with-param name="message-epilog"> (soelim stub)</xsl:with-param> + <xsl:with-param name="content"> + <xsl:value-of select="concat('.so man', $section, '/')"/> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$section"/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:for-each> + </xsl:template> + + <!-- ============================================================== --> + + <!-- * A manifest file is useful for doing "make clean" during --> + <!-- * builds and for other purposes. When we make the manifest --> + <!-- * file, we need to include in it a filename for each man-page --> + <!-- * generated, including any "stub" pages. --> + <xsl:template name="generate.manifest"> + <xsl:variable name="filelist"> + <xsl:for-each select="//refentry"> + <!-- * all refname instances in a Refentry inherit their section --> + <!-- * numbers from the parent Refentry; so we only need to get --> + <!-- * the section once per Refentry, not once per Refname --> + <xsl:variable name="section"> + <xsl:call-template name="get.refentry.section"> + <xsl:with-param name="quiet" select="1"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:variable> + <xsl:for-each select="refnamediv/refname"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="."/> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:for-each> + </xsl:for-each> + </xsl:variable> + + <!-- * we write the manifest file once per document, not once per --> + <!-- * Refentry --> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename"> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:with-param> + <xsl:with-param name="quiet" select="1"/> + <xsl:with-param name="message-prolog">Note: </xsl:with-param> + <xsl:with-param name="message-epilog"> (manifest file)</xsl:with-param> + <xsl:with-param name="content"> + <xsl:value-of select="$filelist"/> + </xsl:with-param> + </xsl:call-template> + <xsl:if test="$man.output.quietly = 0"> + <xsl:message><xsl:text> </xsl:text></xsl:message> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/param.xsl b/docs/xsl-generic/manpages/param.xsl new file mode 100644 index 00000000..fcdc3bc6 --- /dev/null +++ b/docs/xsl-generic/manpages/param.xsl @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="ASCII"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<!-- This file is generated from param.xweb --> + +<!-- ******************************************************************** + $Id: param.xweb 7112 2007-07-22 12:19:19Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:param name="man.authors.section.enabled">1</xsl:param> +<xsl:param name="man.break.after.slash">0</xsl:param> +<xsl:param name="man.charmap.enabled" select="1"/> +<xsl:param name="man.charmap.subset.profile"> +@*[local-name() = 'block'] = 'Miscellaneous Technical' or +(@*[local-name() = 'block'] = 'C1 Controls And Latin-1 Supplement (Latin-1 Supplement)' and + @*[local-name() = 'class'] = 'symbols' +) or +(@*[local-name() = 'block'] = 'General Punctuation' and + (@*[local-name() = 'class'] = 'spaces' or + @*[local-name() = 'class'] = 'dashes' or + @*[local-name() = 'class'] = 'quotes' or + @*[local-name() = 'class'] = 'bullets' + ) +) or +@*[local-name() = 'name'] = 'HORIZONTAL ELLIPSIS' or +@*[local-name() = 'name'] = 'WORD JOINER' or +@*[local-name() = 'name'] = 'SERVICE MARK' or +@*[local-name() = 'name'] = 'TRADE MARK SIGN' or +@*[local-name() = 'name'] = 'ZERO WIDTH NO-BREAK SPACE' +</xsl:param> +<xsl:param name="man.charmap.uri"/> +<xsl:param name="man.charmap.use.subset" select="1"/> +<xsl:param name="man.copyright.section.enabled">1</xsl:param> +<xsl:param name="man.endnotes.are.numbered">1</xsl:param> +<xsl:param name="man.endnotes.list.enabled">1</xsl:param> +<xsl:param name="man.endnotes.list.heading"/> + <xsl:param name="man.font.funcprototype">BI</xsl:param> + <xsl:param name="man.font.funcsynopsisinfo">B</xsl:param> + <xsl:param name="man.font.table.headings">B</xsl:param> + <xsl:param name="man.font.table.title">B</xsl:param> +<xsl:param name="man.hyphenate.computer.inlines">0</xsl:param> +<xsl:param name="man.hyphenate.filenames">0</xsl:param> +<xsl:param name="man.hyphenate">0</xsl:param> +<xsl:param name="man.hyphenate.urls">0</xsl:param> +<xsl:param name="man.indent.blurbs" select="1"/> +<xsl:param name="man.indent.lists" select="1"/> +<xsl:param name="man.indent.refsect" select="0"/> +<xsl:param name="man.indent.verbatims" select="1"/> +<xsl:param name="man.indent.width">4</xsl:param> +<xsl:param name="man.justify">0</xsl:param> +<xsl:param name="man.links.are.underlined">1</xsl:param> +<xsl:param name="man.output.base.dir">man/</xsl:param> +<xsl:param name="man.output.encoding">UTF-8</xsl:param> +<xsl:param name="man.output.in.separate.dir" select="0"/> +<xsl:param name="man.output.lang.in.name.enabled" select="0"/> +<xsl:param name="man.output.manifest.enabled" select="0"/> +<xsl:param name="man.output.manifest.filename">MAN.MANIFEST</xsl:param> +<xsl:param name="man.output.quietly" select="0"/> +<xsl:param name="man.output.subdirs.enabled" select="1"/> +<xsl:param name="man.segtitle.suppress" select="0"/> +<xsl:param name="man.string.subst.map"> + + <!-- * remove no-break marker at beginning of line (stylesheet artifact) --> + <substitution oldstring="▒▀" newstring="▒"/> + <!-- * replace U+2580 no-break marker (stylesheet-added) w/ no-break space --> + <substitution oldstring="▀" newstring="\ "/> + + <!-- ==================================================================== --> + + <!-- * squeeze multiple newlines before a roff request --> + <substitution oldstring=" ." newstring=" ."/> + <!-- * remove any .sp instances that directly precede a .PP --> + <substitution oldstring=".sp .PP" newstring=".PP"/> + <!-- * remove any .sp instances that directly follow a .PP --> + <substitution oldstring=".PP .sp" newstring=".PP"/> + <!-- * squeeze multiple newlines after start of no-fill (verbatim) env. --> + <substitution oldstring=".nf " newstring=".nf "/> + <!-- * squeeze multiple newlines after REstoring margin --> + <substitution oldstring=".RE " newstring=".RE "/> + <!-- * U+2591 is a marker we add before and after every Parameter in --> + <!-- * Funcprototype output --> + <substitution oldstring="░" newstring=" "/> + <!-- * U+2592 is a marker we add for the newline before output of <sbr>; --> + <substitution oldstring="▒" newstring=" "/> + <!-- * --> + <!-- * Now deal with some other characters that are added by the --> + <!-- * stylesheets during processing. --> + <!-- * --> + <!-- * bullet --> + <substitution oldstring="•" newstring="\(bu"/> + <!-- * left double quote --> + <substitution oldstring="“" newstring="\(lq"/> + <!-- * right double quote --> + <substitution oldstring="”" newstring="\(rq"/> + <!-- * left single quote --> + <substitution oldstring="‘" newstring="\(oq"/> + <!-- * right single quote --> + <substitution oldstring="’" newstring="\(cq"/> + <!-- * copyright sign --> + <substitution oldstring="©" newstring="\(co"/> + <!-- * registered sign --> + <substitution oldstring="®" newstring="\(rg"/> + <!-- * ...servicemark... --> + <!-- * There is no groff equivalent for it. --> + <substitution oldstring="℠" newstring="(SM)"/> + <!-- * ...trademark... --> + <!-- * We don't do "\(tm" because for console output, --> + <!-- * groff just renders that as "tm"; that is: --> + <!-- * --> + <!-- * Product™ -> Producttm --> + <!-- * --> + <!-- * So we just make it to "(TM)" instead; thus: --> + <!-- * --> + <!-- * Product™ -> Product(TM) --> + <substitution oldstring="™" newstring="(TM)"/> + +</xsl:param> +<xsl:param name="man.string.subst.map.local.post"/> + <xsl:param name="man.string.subst.map.local.pre"/> +<xsl:param name="man.subheading.divider.enabled">0</xsl:param> +<xsl:param name="man.subheading.divider">========================================================================</xsl:param> +<xsl:param name="man.table.footnotes.divider">----</xsl:param> +<xsl:param name="man.th.extra1.suppress">0</xsl:param> +<xsl:param name="man.th.extra2.max.length">30</xsl:param> +<xsl:param name="man.th.extra2.suppress">0</xsl:param> +<xsl:param name="man.th.extra3.max.length">30</xsl:param> +<xsl:param name="man.th.extra3.suppress">0</xsl:param> +<xsl:param name="man.th.title.max.length">20</xsl:param> +<xsl:param name="refentry.date.profile.enabled">0</xsl:param> +<xsl:param name="refentry.date.profile"> + (($info[//date])[last()]/date)[1]| + (($info[//pubdate])[last()]/pubdate)[1] +</xsl:param> +<xsl:param name="refentry.manual.fallback.profile"> +refmeta/refmiscinfo[1]/node()</xsl:param> +<xsl:param name="refentry.manual.profile.enabled">0</xsl:param> +<xsl:param name="refentry.manual.profile"> + (($info[//title])[last()]/title)[1]| + ../title/node() +</xsl:param> +<xsl:param name="refentry.meta.get.quietly" select="0"/> +<xsl:param name="refentry.source.fallback.profile"> +refmeta/refmiscinfo[1]/node()</xsl:param> +<xsl:param name="refentry.source.name.profile.enabled">0</xsl:param> +<xsl:param name="refentry.source.name.profile"> + (($info[//productname])[last()]/productname)[1]| + (($info[//corpname])[last()]/corpname)[1]| + (($info[//corpcredit])[last()]/corpcredit)[1]| + (($info[//corpauthor])[last()]/corpauthor)[1]| + (($info[//orgname])[last()]/orgname)[1]| + (($info[//publishername])[last()]/publishername)[1] +</xsl:param> +<xsl:param name="refentry.source.name.suppress">0</xsl:param> +<xsl:param name="refentry.version.profile.enabled">0</xsl:param> +<xsl:param name="refentry.version.profile"> + (($info[//productnumber])[last()]/productnumber)[1]| + (($info[//edition])[last()]/edition)[1]| + (($info[//releaseinfo])[last()]/releaseinfo)[1] +</xsl:param> +<xsl:param name="refentry.version.suppress">0</xsl:param> +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/profile-docbook.xsl b/docs/xsl-generic/manpages/profile-docbook.xsl new file mode 100644 index 00000000..ec4759e0 --- /dev/null +++ b/docs/xsl-generic/manpages/profile-docbook.xsl @@ -0,0 +1,259 @@ +<?xml version="1.0" encoding="US-ASCII"?> +<!--This file was created automatically by xsl2profile--> +<!--from the DocBook XSL stylesheets.--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:ng="http://docbook.org/docbook-ng" xmlns:db="http://docbook.org/ns/docbook" xmlns:exslt="http://exslt.org/common" exslt:dummy="dummy" ng:dummy="dummy" db:dummy="dummy" extension-element-prefixes="exslt" exclude-result-prefixes="exsl exslt" version="1.0"> + + <xsl:import href="../html/docbook.xsl"/> + <xsl:import href="../html/manifest.xsl"/> + <!-- * html-synop.xsl file is generated by build --> + <xsl:import href="html-synop.xsl"/> + <xsl:output method="text" encoding="UTF-8" indent="no"/> + <!-- ******************************************************************** + $Id: docbook.xsl 7153 2007-07-26 14:08:55Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <!-- ==================================================================== --> + + <xsl:include href="../common/refentry.xsl"/> + <xsl:include href="../common/charmap.xsl"/> + <xsl:include href="param.xsl"/> + <xsl:include href="utility.xsl"/> + <xsl:include href="info.xsl"/> + <xsl:include href="other.xsl"/> + <xsl:include href="refentry.xsl"/> + <xsl:include href="block.xsl"/> + <xsl:include href="inline.xsl"/> + <xsl:include href="synop.xsl"/> + <xsl:include href="lists.xsl"/> + <xsl:include href="endnotes.xsl"/> + <xsl:include href="table.xsl"/> + + <!-- * we rename the following just to avoid using params with "man" --> + <!-- * prefixes in the table.xsl stylesheet (because that stylesheet --> + <!-- * can potentially be reused for more than just man output) --> + <xsl:param name="tbl.font.headings" select="$man.font.table.headings"/> + <xsl:param name="tbl.font.title" select="$man.font.table.title"/> + + <!-- ==================================================================== --> + + <xslo:include xmlns:xslo="http://www.w3.org/1999/XSL/Transform" href="../profiling/profile-mode.xsl"/><xslo:variable xmlns:xslo="http://www.w3.org/1999/XSL/Transform" name="profiled-content"><xslo:choose><xslo:when test="*/self::ng:* or */self::db:*"><xslo:message>Note: namesp. cut : stripped namespace before processing</xslo:message><xslo:variable name="stripped-content"><xslo:apply-templates select="/" mode="stripNS"/></xslo:variable><xslo:message>Note: namesp. cut : processing stripped document</xslo:message><xslo:apply-templates select="exslt:node-set($stripped-content)" mode="profile"/></xslo:when><xslo:otherwise><xslo:apply-templates select="/" mode="profile"/></xslo:otherwise></xslo:choose></xslo:variable><xslo:variable xmlns:xslo="http://www.w3.org/1999/XSL/Transform" name="profiled-nodes" select="exslt:node-set($profiled-content)"/><xsl:template match="/"> + <!-- * Get a title for current doc so that we let the user --> + <!-- * know what document we are processing at this point. --> + <xsl:variable name="doc.title"> + <xsl:call-template name="get.doc.title"/> + </xsl:variable> + <xsl:choose> + <!-- * when we find a namespaced document, strip the --> + <!-- * namespace and then continue processing it. --> + <xsl:when test="false()"/> + <xsl:when test="//*[local-name() = 'refentry']"> + <!-- * Check to see if we have any refentry children in this --> + <!-- * document; if so, process them. The reason we use --> + <!-- * local-name()=refentry (instead of just //refentry) to to --> + <!-- * check for refentry children is because this stylsheet is --> + <!-- * also post-processed by the stylesheet build to create the --> + <!-- * manpages/profile-docbook.xsl, and the refentry child check --> + <!-- * in the profile-docbook.xsl stylesheet won't work if we do --> + <!-- * a simple //refentry check. --> + <xsl:apply-templates select="$profiled-nodes//refentry"/> + <!-- * if $man.output.manifest.enabled is non-zero, --> + <!-- * generate a manifest file --> + <xsl:if test="not($man.output.manifest.enabled = 0)"> + <xsl:call-template name="generate.manifest"> + <xsl:with-param name="filename"> + <xsl:choose> + <xsl:when test="not($man.output.manifest.filename = '')"> + <!-- * If a name for the manifest file is specified, --> + <!-- * use that name. --> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, if user has unset --> + <!-- * $man.output.manifest.filename, default to --> + <!-- * using "MAN.MANIFEST" as the filename. Because --> + <!-- * $man.output.manifest.enabled is non-zero and --> + <!-- * so we must have a filename in order to --> + <!-- * generate the manifest. --> + <xsl:text>MAN.MANIFEST</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, the document does not contain any --> + <!-- * refentry elements, so log/emit message and stop. --> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Erro</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text> no refentry</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>No refentry elements found</xsl:text> + <xsl:if test="$doc.title != ''"> + <xsl:text> in "</xsl:text> + <xsl:choose> + <xsl:when test="string-length($doc.title) > 30"> + <xsl:value-of select="substring($doc.title,1,30)"/> + <xsl:text>...</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$doc.title"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>"</xsl:text> + </xsl:if> + <xsl:text>.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template match="refentry"> + <xsl:param name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:param> + <!-- * Just use the first refname found as the "name" of the man --> + <!-- * page (which may different from the "title"...) --> + <xsl:variable name="first.refname" select="refnamediv[1]/refname[1]"/> + + <xsl:call-template name="root.messages"> + <xsl:with-param name="refname" select="$first.refname"/> + </xsl:call-template> + + <!-- * Because there are several times when we need to check *info of --> + <!-- * each refentry and its ancestors, we get those and store the --> + <!-- * data from them as a node-set in memory. --> + + <!-- * Make a node-set with contents of *info --> + <xsl:variable name="get.info" select="ancestor-or-self::*/*[substring(local-name(), string-length(local-name()) - 3) = 'info']"/> + <xsl:variable name="info" select="exsl:node-set($get.info)"/> + + <!-- * The get.refentry.metadata template is in --> + <!-- * ../common/refentry.xsl. It looks for metadata in $info --> + <!-- * and in various other places and then puts it into a form --> + <!-- * that's easier for us to digest. --> + <xsl:variable name="get.refentry.metadata"> + <xsl:call-template name="get.refentry.metadata"> + <xsl:with-param name="refname" select="$first.refname"/> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="prefs" select="$refentry.metadata.prefs"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="refentry.metadata" select="exsl:node-set($get.refentry.metadata)"/> + + <!-- * Assemble the various parts into a complete page, then store into --> + <!-- * $manpage.contents so that we can manipluate them further. --> + <xsl:variable name="manpage.contents"> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * top.comment = commented-out section at top of roff source --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="top.comment"> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="date" select="$refentry.metadata/date"/> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="manual" select="$refentry.metadata/manual"/> + <xsl:with-param name="source" select="$refentry.metadata/source"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * TH.title.line = title line in header/footer of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="TH.title.line"> + <!-- * .TH TITLE section extra1 extra2 extra3 --> + <!-- * --> + <!-- * According to the man(7) man page: --> + <!-- * --> + <!-- * extra1 = date, "the date of the last revision" --> + <!-- * extra2 = source, "the source of the command" --> + <!-- * extra3 = manual, "the title of the manual --> + <!-- * (e.g., Linux Programmer's Manual)" --> + <!-- * --> + <!-- * So, we end up with: --> + <!-- * --> + <!-- * .TH TITLE section date source manual --> + <!-- * --> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="extra1" select="$refentry.metadata/date"/> + <xsl:with-param name="extra2" select="$refentry.metadata/source"/> + <xsl:with-param name="extra3" select="$refentry.metadata/manual"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Set default hyphenation, justification, indentation, and --> + <!-- * line-breaking --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="set.default.formatting"/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Main body of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:apply-templates/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * AUTHOR section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.authors.section.enabled = 0)"> + <xsl:call-template name="author.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * COPYRIGHT section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.copyright.section.enabled = 0)"> + <xsl:call-template name="copyright.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * NOTES list (only if user wants endnotes numbered and/or listed) --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="$man.endnotes.list.enabled != 0 or $man.endnotes.are.numbered != 0"> + <xsl:call-template name="endnotes.list"/> + </xsl:if> + </xsl:variable> <!-- * end of manpage.contents --> + + <!-- * Prepare the page contents for final output, then store in --> + <!-- * $manpage.contents.prepared so the we can pass it on to the --> + <!-- * write.text.chunk() function --> + <xsl:variable name="manpage.contents.prepared"> + <!-- * "Preparing" the page contents involves, at a minimum, --> + <!-- * doubling any backslashes found (so they aren't interpreted --> + <!-- * as roff escapes). --> + <!-- * --> + <!-- * If $charmap.enabled is true, "preparing" the page contents also --> + <!-- * involves applying a character map to convert Unicode symbols and --> + <!-- * special characters into corresponding roff escape sequences. --> + <xsl:call-template name="prepare.manpage.contents"> + <xsl:with-param name="content" select="$manpage.contents"/> + </xsl:call-template> + </xsl:variable> + + <!-- * Write the prepared page contents to disk to create --> + <!-- * the final man page. --> + <xsl:call-template name="write.man.file"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + <xsl:with-param name="content" select="$manpage.contents.prepared"/> + </xsl:call-template> + + <!-- * Generate "stub" (alias) pages (if any needed) --> + <xsl:call-template name="write.stubs"> + <xsl:with-param name="first.refname" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/refentry.xsl b/docs/xsl-generic/manpages/refentry.xsl new file mode 100644 index 00000000..9cb7005f --- /dev/null +++ b/docs/xsl-generic/manpages/refentry.xsl @@ -0,0 +1,256 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: refentry.xsl 6657 2007-02-26 20:04:25Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + + <xsl:template match="refnamediv"> + <xsl:choose> + <xsl:when test="preceding-sibling::refnamediv"> + <!-- * No title on secondary refnamedivs! --> + <!-- * Just put a single line break instead --> + <xsl:text>.br </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:apply-templates select="." mode="title.markup"/> + <xsl:text>"</xsl:text> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:call-template name="mark.subheading"/> + <!-- * if we have multiple Refname instances, separate the names --> + <!-- * with commas --> + <xsl:for-each select="refname"> + <xsl:if test="position()>1"> + <xsl:text>, </xsl:text> + </xsl:if> + <xsl:value-of select="."/> + </xsl:for-each> + <!-- * The man(7) man pages says: --> + <!-- * --> + <!-- * The only required heading is NAME, which should be the --> + <!-- * first section and be followed on the next line by a one --> + <!-- * line description of the program: --> + <!-- * --> + <!-- * .SH NAME chess \- the game of chess --> + <!-- * --> + <!-- * It is extremely important that this format is followed, --> + <!-- * and that there is a backslash before the single dash --> + <!-- * which follows the command name. This syntax is used by --> + <!-- * the makewhatis(8) program to create a database of short --> + <!-- * command descriptions for the whatis(1) and apropos(1) --> + <!-- * commands. --> + <!-- * --> + <!-- * So why don't we precede the hyphen with a backslash here? --> + <!-- * Well, because it's added later, by the apply-string-subst-map --> + <!-- * template, before we generate final output --> + <xsl:if test="refpurpose/node()"> + <xsl:text> - </xsl:text> + <xsl:value-of select="normalize-space(refpurpose)"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="refsynopsisdiv"> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:apply-templates select="." mode="title.markup"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="refsect1|refentry/refsection"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:variable> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="refsect2|refentry/refsection/refsection"> + <xsl:call-template name="mark.subheading"/> + <xsl:variable name="title"> + <xsl:apply-templates + select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]/node()"/> + + </xsl:variable> + <xsl:text>.SS "</xsl:text> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:choose> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect2 --> + <xsl:when test="not($man.indent.refsect = 0)"> + <xsl:text>.RS </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If default-indentation adjustment is on, then do not --> + <!-- * indent the child content of thie Refsect2, because --> + <!-- * the title is already "sticking out to the left" --> + <!-- * (as the groff_man(7) man page describes it), which --> + <!-- * actually means the title is indented by the value of --> + <!-- * the SN register, which appears by default to be --> + <!-- * about half of the default indentation value --> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="refsect3|refentry/refsection/refsection/refsection"> + <xsl:variable name="title"> + <xsl:value-of select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + </xsl:variable> + <xsl:choose> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect3 or Refsection. --> + <xsl:when test="not($man.indent.refsect != 0)"> + <xsl:call-template name="nested-section-title"/> + <xsl:text>.RS </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If default-indentation adjustment is on, then do not --> + <!-- * indent the child content of thie Refsect2, because --> + <!-- * the title is already "sticking out to the left" --> + <!-- * (as the groff_man(7) man page describes it), which --> + <!-- * actually means the title is indented by the value of --> + <!-- * the SN register, which appears by default to be --> + <!-- * about half of the default indentation value --> + <xsl:text>.ti (\n(SNu * 5u / 3u) </xsl:text> + <xsl:call-template name="nested-section-title"/> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="refsection"> + <!-- * This template is used for a nested Refsection that is --> + <!-- * is a child of a Refsect3-level section (The numberd --> + <!-- * Refsect hierarchy in DocBook ends with Refsect3, so --> + <!-- * there is not actually a Refsect4 element.) --> + <xsl:variable name="title"> + <xsl:value-of select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + </xsl:variable> + <xsl:variable name="indent-width"> + <xsl:if test="not($man.indent.refsect = 0)"> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect3 or Refsection. --> + <xsl:text>(\n(SNu) </xsl:text> + </xsl:if> + </xsl:variable> + <xsl:call-template name="nested-section-title"/> + <xsl:text>.RS (\n(SNu) </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:template> + + <!-- ==================================================================== --> + + <!-- * Use uppercase to render titles of all instances of Refsect1 or --> + <!-- * top-level Refsection, including in cross-references --> + <xsl:template match="refsect1|refentry/refsection" + mode="title.markup"> + <xsl:variable name="title" select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:apply-templates select="$title" mode="title.markup"/> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Output of Titles from Xref with Endterm needs to be handled --> + <!-- * separately from output for Endterm-less Xref --> + <xsl:template match="refsect1/title + |refentry/refsection/title + |refsynopsisdiv/title" + mode="endterm"> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:apply-templates/> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Use uppercase to render titles of all instances of Refsynopsisdiv, --> + <!-- * including in cross-references --> + <xsl:template match="refsynopsisdiv" mode="title.markup"> + <xsl:param name="allow-anchors" select="0"/> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <xsl:when test="info/title + |refsynopsisdivinfo/title + |title"> + <xsl:apply-templates + select="(info/title + |refsynopsisdivinfo/title + |title)[1]" mode="title.markup"> + <xsl:with-param name="allow-anchors" select="$allow-anchors"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RefSynopsisDiv'"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Use uppercase to render titles of all instances of Refnamediv, --> + <!-- * including in cross-references --> + <xsl:template match="refnamediv" mode="title.markup"> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RefName'"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <xsl:template match="refnamediv" mode="xref-to"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:template> + + <!-- ==================================================================== --> + + <!-- * suppress any title we don't otherwise process elsewhere --> + + <xsl:template match="title"/> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/synop.xsl b/docs/xsl-generic/manpages/synop.xsl new file mode 100644 index 00000000..cbca36c7 --- /dev/null +++ b/docs/xsl-generic/manpages/synop.xsl @@ -0,0 +1,305 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: synop.xsl 7235 2007-08-13 11:13:37Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:variable name="arg.or.sep"> |</xsl:variable> + +<!-- * Note: If you're looking for the *Synopsis* element, you won't --> +<!-- * find any code here for handling it. It's a "verbatim" --> +<!-- * environment; see the block.xsl file instead. --> + +<xsl:template match="synopfragmentref"> + <xsl:variable name="target" select="key('id',@linkend)"/> + <xsl:variable name="snum"> + <xsl:apply-templates select="$target" mode="synopfragment.number"/> + </xsl:variable> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + <xsl:text>▀</xsl:text> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="exsl:node-set(normalize-space(.))"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="synopfragment" mode="synopfragment.number"> + <xsl:number format="1"/> +</xsl:template> + +<xsl:template match="synopfragment"> + <xsl:variable name="snum"> + <xsl:apply-templates select="." mode="synopfragment.number"/> + </xsl:variable> + <xsl:text> </xsl:text> + <!-- * If we have a group of Synopgfragments, we only want to output a --> + <!-- * line of space before the first; so when we find a Synopfragment --> + <!-- * whose first preceding sibling is another Synopfragment, we back --> + <!-- * up one line vertically to negate the line of vertical space --> + <!-- * that's added by the .HP macro --> + <xsl:if test="preceding-sibling::*[1][self::synopfragment]"> + <xsl:text>.sp -1n </xsl:text> + </xsl:if> + <xsl:text>.HP </xsl:text> + <!-- * For each Synopfragment, make a hanging paragraph, with the --> + <!-- * indent calculated from the length of the generated number --> + <!-- * used as a reference + pluse 3 characters (for the open and --> + <!-- * close parens around the number, plus a space). --> + <xsl:value-of select="string-length (normalize-space ($snum)) + 3"/> + <xsl:text> </xsl:text> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + <xsl:text> </xsl:text> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="group|arg" name="group-or-arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:variable name="sepchar"> + <xsl:choose> + <xsl:when test="ancestor-or-self::*/@sepchar"> + <xsl:value-of select="ancestor-or-self::*/@sepchar"/> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="position()>1 and + not(preceding-sibling::*[1][self::sbr])" + ><xsl:value-of select="$sepchar"/></xsl:if> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <!-- * do nothing --> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.open.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.open.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:variable name="arg"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:choose> + <xsl:when test="local-name(.) = 'arg' and not(ancestor::arg)"> + <!-- * Prevent arg contents from getting wrapped and broken up --> + <xsl:variable name="arg.wrapper"> + <Arg><xsl:value-of select="normalize-space($arg)"/></Arg> + </xsl:variable> + <xsl:apply-templates mode="prevent.line.breaking" + select="exsl:node-set($arg.wrapper)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$rep='repeat'"> + <xsl:value-of select="$arg.rep.repeat.str"/> + </xsl:when> + <xsl:when test="$rep='norepeat'"> + <xsl:value-of select="$arg.rep.norepeat.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.rep.def.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:if test='arg'> + <xsl:value-of select="$arg.choice.plain.close.str"/> + </xsl:if> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.close.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.close.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.close.str"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="group/arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:if test="position()>1"><xsl:value-of select="$arg.or.sep"/></xsl:if> + <xsl:call-template name="group-or-arg"/> +</xsl:template> + +<xsl:template match="sbr"> + <xsl:text>▒</xsl:text> + <xsl:text>.br▒</xsl:text> +</xsl:template> + +<xsl:template match="cmdsynopsis"> + <!-- * if justification is enabled by default, turn it off temporarily --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it off temporarily --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy 0 </xsl:text> + </xsl:if> + <xsl:text>.HP </xsl:text> + <xsl:value-of select="string-length (normalize-space (command)) + 1"/> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <!-- * if justification is enabled by default, turn it back on --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it back on --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> +<!-- * Funcsynopis hierarchy starts here --> +<!-- ==================================================================== --> + +<!-- * Note: If you're looking for the *Funcsynopsisinfo* element, --> +<!-- * you won't find any code here for handling it. It's a "verbatim" --> +<!-- * environment; see the block.xsl file instead. --> + +<!-- * Within funcsynopis output, disable hyphenation, and use --> +<!-- * left-aligned filling for the duration of the synopsis, so that --> +<!-- * line breaks only occur between separate paramdefs. --> +<xsl:template match="funcsynopsis"> + <!-- * if justification is enabled by default, turn it off temporarily --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it off temporarily --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy 0 </xsl:text> + </xsl:if> + <xsl:apply-templates/> + <!-- * if justification is enabled by default, turn it back on --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it back on --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy </xsl:text> + </xsl:if> +</xsl:template> + +<!-- * All Funcprototype content is by default rendered in bold, --> +<!-- * because the man(7) man page says this: --> +<!-- * --> +<!-- * For functions, the arguments are always specified using --> +<!-- * italics, even in the SYNOPSIS section, where the rest of --> +<!-- * the function is specified in bold --> +<!-- * --> +<!-- * Look through the contents of the man/man2 and man3 directories --> +<!-- * on your system, and you'll see that most existing pages do follow --> +<!-- * this "bold everything in function synopsis" rule. --> +<!-- * --> +<!-- * Users who don't want the bold output can choose to adjust the --> +<!-- * man.font.funcprototype parameter on their own. So even if you --> +<!-- * don't personally like the way it looks, please don't change the --> +<!-- * default to be non-bold - because it's a convention that's --> +<!-- * followed is the vast majority of existing man pages that document --> +<!-- * functions, and we need to follow it by default, like it or no. --> +<xsl:template match="funcprototype"> + <xsl:variable name="funcprototype.string.value"> + <xsl:value-of select="funcdef"/> + </xsl:variable> + <xsl:variable name="funcprototype"> + <xsl:apply-templates select="funcdef"/> + </xsl:variable> + <xsl:text>.HP </xsl:text> + <!-- * Hang Paragraph by length of string value of <funcdef> + 1 --> + <!-- * (because funcdef is always followed by one open paren char) --> + <xsl:value-of select="string-length (normalize-space ($funcprototype.string.value)) + 1"/> + <xsl:text> </xsl:text> + <xsl:text>.</xsl:text> + <xsl:value-of select="$man.font.funcprototype"/> + <xsl:text> </xsl:text> + <!-- * The following quotation mark (and the one further below) are --> + <!-- * needed to properly delimit the parts of the Funcprototype that --> + <!-- * should be rendered in the prevailing font (either Bold or Roman) --> + <!-- * from Parameter output that needs to be alternately rendered in --> + <!-- * italic. --> + <xsl:text>"</xsl:text> + <xsl:value-of select="normalize-space($funcprototype)"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="*[local-name() != 'funcdef']"/> + <xsl:text>"</xsl:text> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="funcdef"> + <xsl:apply-templates mode="prevent.line.breaking"/> +</xsl:template> + +<xsl:template match="funcdef/function"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="void"> + <xsl:text>void);</xsl:text> +</xsl:template> + +<xsl:template match="varargs"> + <xsl:text>...);</xsl:text> +</xsl:template> + +<xsl:template match="paramdef"> + <xsl:apply-templates mode="prevent.line.breaking" select="."/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>);</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter"> + <!-- * We use U+2591 here in place of a normal space, because if we --> + <!-- * were to just use a normal space, it would get replaced with a --> + <!-- * non-breaking space when we run the whole Paramdef through the --> + <!-- * prevent.line.breaking template. And as far as why we're --> + <!-- * inserting the space and quotation marks around each Parameter --> + <!-- * to begin with, the reason is that we need to because we are --> + <!-- * outputting Funcsynopsis in either the "BI" or "RI" font, and --> + <!-- * the space and quotation marks delimit the text as the --> + <!-- * "alternate" or "I" text that needs to be rendered in italic. --> + <xsl:text>"░"</xsl:text> + <xsl:apply-templates/> + <xsl:text>"░"</xsl:text> +</xsl:template> + +<xsl:template match="funcparams"> + <xsl:text>(</xsl:text> + <xsl:apply-templates/> + <xsl:text>)</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/table.xsl b/docs/xsl-generic/manpages/table.xsl new file mode 100644 index 00000000..b6e2fe2e --- /dev/null +++ b/docs/xsl-generic/manpages/table.xsl @@ -0,0 +1,633 @@ +<?xml version="1.0"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + + <!-- ******************************************************************** + $Id: table.xsl 7177 2007-08-06 10:18:36Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + <!-- + <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/> + <xsl:param name="tbl.font.title">B</xsl:param> + <xsl:param name="tbl.font.headings">B</xsl:param> + --> + <xsl:param name="tbl.running.header.from.thead" select="0"/> + <xsl:param name="tbl.column.separator.char">:</xsl:param> + + <!-- ==================================================================== --> + + <!-- * This stylesheet transforms DocBook and HTML table source into --> + <!-- * tbl(1) markup. --> + <!-- * --> + <!-- * For details on tbl(1) and its markup syntaxt, see M. E. Lesk,--> + <!-- * "Tbl - A Program to Format Tables": --> + <!-- * --> + <!-- * http://cm.bell-labs.com/7thEdMan/vol2/tbl --> + <!-- * http://cm.bell-labs.com/cm/cs/doc/76/tbl.ps.gz --> + <!-- * http://www.snake.net/software/troffcvt/tbl.html --> + + <xsl:template match="table|informaltable" mode="to.tbl"> + <!--* the "source" param is an optional param; it can be any --> + <!--* string you want to use that gives some indication of the --> + <!--* source context for a table; it gets passed down to the named --> + <!--* templates that do the actual table processing; this --> + <!--* stylesheet currently uses the "source" information for --> + <!--* logging purposes --> + <xsl:param name="source"/> + <xsl:param name="title"> + <xsl:if test="local-name(.) = 'table'"> + <xsl:apply-templates select="." mode="object.title.markup.textonly"/> + </xsl:if> + </xsl:param> + <!-- * ============================================================== --> + <!-- * Set global table parameters --> + <!-- * ============================================================== --> + <!-- * First, set a few parameters based on attributes specified in --> + <!-- * the table source. --> + <xsl:param name="allbox"> + <xsl:if test="not(@frame = 'none') and not(@border = '0')"> + <!-- * By default, put a box around table and between all cells, --> + <!-- * unless frame="none" or border="0" --> + <xsl:text>allbox </xsl:text> + </xsl:if> + </xsl:param> + <xsl:param name="center"> + <!-- * If align="center", center the table. Otherwise, tbl(1) --> + <!-- * left-aligns it by default; note that there is no support --> + <!-- * in tbl(1) for specifying right alignment. --> + <xsl:if test="@align = 'center' or tgroup/@align = 'center'"> + <xsl:text>center </xsl:text> + </xsl:if> + </xsl:param> + <xsl:param name="expand"> + <!-- * If pgwide="1" or width="100%", then "expand" the table by --> + <!-- * making it "as wide as the current line length" (to quote --> + <!-- * the tbl(1) guide). --> + <xsl:if test="@pgwide = '1' or @width = '100%'"> + <xsl:text>expand </xsl:text> + </xsl:if> + </xsl:param> + + <!-- * ============================================================== --> + <!-- * Convert table to HTML --> + <!-- * ============================================================== --> + <!-- * Process the table by applying the HTML templates from the --> + <!-- * DocBook XSL stylesheets to the whole thing; because we don't --> + <!-- * override any of the <row>, <entry>, <tr>, <td>, etc. templates, --> + <!-- * the templates in the HTML stylesheets (which we import) are --> + <!-- * used to process those. --> + <xsl:param name="html-table-output"> + <xsl:choose> + <xsl:when test=".//tr"> + <!-- * If this table has a TR child, it means that it's an --> + <!-- * HTML table in the DocBook source, instead of a CALS --> + <!-- * table. So we just copy it as-is, while wrapping it --> + <!-- * in an element with same name as its original parent. --> + <xsl:for-each select="descendant-or-self::table|descendant-or-self::informaltable"> + <xsl:element name="{local-name(..)}"> + <table> + <xsl:copy-of select="*"/> + </table> + </xsl:element> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, this is a CALS table in the DocBook source, --> + <!-- * so we need to apply the templates in the HTML --> + <!-- * stylesheets to transform it into HTML before we do --> + <!-- * any further processing of it. --> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:param name="contents" select="exsl:node-set($html-table-output)"/> + + <!-- ==================================================================== --> + <!-- * Output the table --> + <!-- ==================================================================== --> + <!-- * --> + <!-- * This is the "driver" part of the code; it calls a series of named + * templates (further below) to generate the actual tbl(1) markup, --> + <!-- * including the optional "options line", required "format section", --> + <!-- * and then the actual contents of the table. --> + <!-- * --> + <!-- ==================================================================== --> + + <xsl:for-each select="$contents//table"> + <!-- * ============================================================== --> + <!-- * Output table title --> + <!-- * ============================================================== --> + <xsl:if test="$title != '' or parent::td"> + <xsl:text>.PP </xsl:text> + <xsl:text>.</xsl:text> + <xsl:value-of select="$tbl.font.title"/> + <xsl:text> </xsl:text> + <xsl:if test="parent::td"> + <xsl:text>*[nested▀table]</xsl:text> + </xsl:if> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + </xsl:if> + + <!-- * mark the start of the table --> + <!-- * "TS" = "table start" --> + <xsl:text>.TS</xsl:text> + <xsl:if test="thead and $tbl.running.header.from.thead"> + <!-- * H = "has header" --> + <xsl:text> H</xsl:text> + </xsl:if> + <xsl:text> </xsl:text> + + <!-- * ============================================================== --> + <!-- * Output "options line" --> + <!-- * ============================================================== --> + <xsl:variable name="options-line"> + <xsl:value-of select="$allbox"/> + <xsl:value-of select="$center"/> + <xsl:value-of select="$expand"/> + <xsl:text>tab(</xsl:text> + <xsl:value-of select="$tbl.column.separator.char"/> + <xsl:text>)</xsl:text> + </xsl:variable> + <xsl:if test="normalize-space($options-line) != ''"> + <xsl:value-of select="normalize-space($options-line)"/> + <xsl:text>; </xsl:text> + </xsl:if> + + <!-- * ============================================================== --> + <!-- * Output table header rows --> + <!-- * ============================================================== --> + <xsl:if test="thead"> + <xsl:call-template name="output.rows"> + <xsl:with-param name="rows" select="thead/tr"/> + </xsl:call-template> + <xsl:text> </xsl:text> + + <!-- * mark the end of table-header rows --> + <xsl:choose> + <xsl:when test="$tbl.running.header.from.thead"> + <!-- * "TH" = "table header end" --> + <xsl:text>.TH </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * "T&" = "table continuation" and is meant just as a kind --> + <!-- * of convenience macro and is sorta equivalent to a "TE" --> + <!-- * (table end) followed immediately by a "TS" (table start); --> + <!-- * in this case, it marks the end of a table "subsection" --> + <!-- * with header rows, and the start of a subsection with body --> + <!-- * rows. It's necessary to output it here because the "TH" --> + <!-- * macro is not being output, so there's otherwise no way --> + <!-- * for tbl(1) to know we have the table "sectioned". --> + <xsl:text>.T& </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <!-- * ============================================================== --> + <!-- * Output table body rows --> + <!-- * ============================================================== --> + <!-- * First create node set with all non-thead rows (tbody+tfoot), --> + <!-- * but reordered with the tfoot rows at the end of the node set --> + <xsl:variable name="rows-set"> + <xsl:copy-of select="tbody/tr|tr"/> + <xsl:copy-of select="tfoot/tr"/> + </xsl:variable> + <xsl:call-template name="output.rows"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="rows" select="exsl:node-set($rows-set)"/> + </xsl:call-template> + + <!-- * mark the end of the table --> + <xsl:text> </xsl:text> + <!-- * .TE = "Table End" --> + <xsl:text>.TE </xsl:text> + <!-- * put a blank line of space below the table --> + <xsl:text>.sp </xsl:text> + </xsl:for-each> + </xsl:template> + + <!-- ==================================================================== --> + <!-- * named templates --> + <!-- ==================================================================== --> + <!-- * --> + <!-- * All of the following are named templates that get called directly --> + <!-- * or indirectly by the main "driver" part of the code (above) --> + <!-- * --> + <!-- ==================================================================== --> + + <xsl:template name="output.rows"> + <xsl:param name="source"/> + <xsl:param name="rows"/> + <!-- * ============================================================== --> + <!-- * Flatten row set into simple list of cells --> + <!-- * ============================================================== --> + <!-- * Now we flatten the structure further into just a set of --> + <!-- * cells without the row parents. This basically creates a --> + <!-- * copy of the entire contents of the original table, but --> + <!-- * restructured in such a way that we can more easily generate --> + <!-- * the corresponding tbl(1) markup we need to output. --> + <xsl:variable name="cells-list"> + <xsl:call-template name="build.cell.list"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="rows" select="$rows"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="cells" select="exsl:node-set($cells-list)"/> + + <!-- * Output the table "format section", which tells tbl(1) how to --> + <!-- * format each row and column --> + <xsl:call-template name="create.table.format"> + <xsl:with-param name="cells" select="$cells"/> + </xsl:call-template> + + <!--* Output the formatted contents of each cell. --> + <xsl:for-each select="$cells/cell"> + <xsl:call-template name="output.cell"/> + </xsl:for-each> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Output the tbl(1)-formatted contents of each cell. --> + <!-- * ============================================================== --> + <xsl:template name="output.cell"> + <xsl:choose> + <xsl:when test="preceding-sibling::cell[1]/@row != @row or + not(preceding-sibling::cell)"> + <!-- * If the value of the "row" attribute on this cell is --> + <!-- * different from the value of that on the previous cell, it --> + <!-- * means we have a new row. So output a line break (as long --> + <!-- * as this isn't the first cell in the table) --> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise we are not at the start of a new row, so we --> + <!-- * output a tab character to delimit the contents of this --> + <!-- * cell from the contents of the next one. --> + <xsl:value-of select="$tbl.column.separator.char"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="@type = '^'"> + <!-- * If this is a dummy cell resulting from the presence of --> + <!-- * rowpan attribute in the source, it has no contents, so --> + <!-- * we need to handle it differently. --> + <xsl:if test="@colspan and @colspan > 1"> + <!-- * If there is a colspan attribute on this dummy row, then --> + <!-- * we need to output a tab character for each column that --> + <!-- * it spans. --> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string" select="$tbl.column.separator.char"/> + <xsl:with-param name="count"> + <xsl:value-of select="@colspan - 1"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, we have a "real" cell (not a dummy one) with --> + <!-- * contents that we need to output, --> + <!-- * --> + <!-- * The "T{" and "T}" stuff are delimiters to tell tbl(1) that --> + <!-- * the delimited contents are "text blocks" that roff --> + <!-- * needs to process --> + <xsl:text>T{ </xsl:text> + <xsl:copy-of select="."/> + <xsl:text> T}</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Build a restructured "cell list" copy of the entire table --> + <!-- * ============================================================== --> + <xsl:template name="build.cell.list"> + <xsl:param name="source"/> + <xsl:param name="rows"/> + <xsl:param name="cell-data-unsorted"> + <!-- * This param collects all the "real" cells from the table, --> + <!-- * along with "dummy" rows that we generate for keeping --> + <!-- * track of Rowspan instances. --> + <xsl:apply-templates select="$rows" mode="cell.list"> + <xsl:with-param name="source" select="$source"/> + </xsl:apply-templates> + </xsl:param> + <xsl:param name="cell-data-sorted"> + <!-- * Sort the cells so that the dummy cells get put where we --> + <!-- * need them in the structure. Note that we need to specify --> + <!-- * data-type="number" here because the default sorting method --> + <!-- * for xsl:sort is "text" (alphabetical). --> + <xsl:for-each select="exsl:node-set($cell-data-unsorted)/cell"> + <xsl:sort select="@row" data-type="number"/> + <xsl:sort select="@slot" data-type="number"/> + <xsl:copy-of select="."/> + </xsl:for-each> + </xsl:param> + <!-- * Return the sorted cell list --> + <xsl:copy-of select="$cell-data-sorted"/> + </xsl:template> + + <xsl:template match="tr" mode="cell.list"> + <xsl:param name="source"/> + <xsl:variable name="row"> + <xsl:value-of select="count(preceding-sibling::tr) + 1"/> + </xsl:variable> + <xsl:for-each select="td|th"> + <xsl:call-template name="cell"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="row" select="$row"/> + <!-- * pass on the element name so we can select the appropriate --> + <!-- * roff font for styling the cell contents --> + <xsl:with-param name="class" select="name(.)"/> + </xsl:call-template> + </xsl:for-each> + </xsl:template> + + <xsl:template name="cell"> + <xsl:param name="source"/> + <xsl:param name="row"/> + <xsl:param name="class"/> + <xsl:param name="slot"> + <!-- * The "slot" is the horizontal position of this cell (usually --> + <!-- * just the same as its column, but not so when it is preceded --> + <!-- * by cells that have colspans or cells in preceding rows that --> + <!-- * that have rowspans). --> + <xsl:value-of select="position()"/> + </xsl:param> + <!-- * For each real TD cell, create a Cell instance; contents will --> + <!-- * be the roff-formatted contents of its original table cell. --> + <cell type="" + row="{$row}" + slot="{$slot}" + class="{$class}" + colspan="{@colspan}" + align="{@align}" + valign="{@valign}" + > + <xsl:choose> + <xsl:when test=".//tr"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="context-desc">tbl convert</xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Extracted a nested table</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:text>[\fInested▀table\fR]* </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Apply templates to the child contents of this cell, to --> + <!-- * transform them into marked-up roff. --> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <!-- * We now have the contents in roff (plain-text) form, --> + <!-- * but we may also still have unnecessary whitespace at --> + <!-- * the beginning and/or end of it, so trim it off. --> + <xsl:call-template name="trim.text"> + <xsl:with-param name="contents" select="$contents"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </cell> + + <!-- * For each instance of a rowspan attribute found, we create N --> + <!-- * dummy cells, where N is equal to the value of the rowspan. --> + <xsl:if test="@rowspan and @rowspan > 0"> + <!-- * If this cell is preceded in the same row by cells that --> + <!-- * have colspan attributes, then we need to calculate the --> + <!-- * "offset" caused by those colspan instances; the formula --> + <!-- * is to (1) check for all the preceding cells that have --> + <!-- * colspan attributes that are not empty and which have a --> + <!-- * value greater than 1, then (2) take the sum of the values --> + <!-- * of all those colspan attributes, and subtract from that --> + <!-- * the number of such colspan instances found. --> + <xsl:variable name="colspan-offset"> + <xsl:value-of + select="sum(preceding-sibling::td[@colspan != '' + and @colspan > 1]/@colspan) - + count(preceding-sibling::td[@colspan != '' + and @colspan > 1]/@colspan)"/> + </xsl:variable> + <xsl:call-template name="create.dummy.cells"> + <xsl:with-param name="row" select="$row + 1"/> + <!-- * The slot value on each dummy cell must be offset by the --> + <!-- * value of $colspan-offset to adjust for preceding colpans --> + <xsl:with-param name="slot" select="$slot + $colspan-offset"/> + <xsl:with-param name="colspan" select="@colspan"/> + <xsl:with-param name="rowspan" select="@rowspan"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <xsl:template name="create.dummy.cells"> + <xsl:param name="row"/> + <xsl:param name="slot"/> + <xsl:param name="colspan"/> + <xsl:param name="rowspan"/> + <xsl:choose> + <xsl:when test="$rowspan > 1"> + <!-- * Tail recurse until we have no more rowspans, creating --> + <!-- * an empty dummy cell each time. The type value, '^' --> + <!-- * is the marker that tbl(1) uses to indicate a --> + <!-- * "vertically spanned heading". --> + <cell row="{$row}" slot="{$slot}" type="^" colspan="{@colspan}"/> + <xsl:call-template name="create.dummy.cells"> + <xsl:with-param name="row" select="$row + 1"/> + <xsl:with-param name="slot" select="$slot"/> + <xsl:with-param name="colspan" select="$colspan"/> + <xsl:with-param name="rowspan" select="$rowspan - 1"/> + </xsl:call-template> + </xsl:when> + </xsl:choose> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Build the "format section" for the table --> + <!-- * ============================================================== --> + <!-- * Description from the tbl(1) guide: --> + <!-- * --> + <!-- * "The format section of the table specifies the layout of the --> + <!-- * columns. Each line in this section corresponds to one line of --> + <!-- * the table... and each line contains a key-letter for each --> + <!-- * column of the table." --> + <xsl:template name="create.table.format"> + <xsl:param name="cells"/> + <xsl:apply-templates mode="table.format" select="$cells"/> + <!-- * last line of table format section must end with a dot --> + <xsl:text>.</xsl:text> + </xsl:template> + + <xsl:template match="cell" mode="table.format"> + <xsl:choose> + <xsl:when test="preceding-sibling::cell[1]/@row != @row"> + <!-- * If the value of the row attribute on this cell is --> + <!-- * different from the value of that on the previous cell, it --> + <!-- * means we have a new row. So output a line break. --> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If this isn't the first cell, output a space before it to --> + <!-- * separate it from the preceding key letter. --> + <xsl:if test="position() != 1"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + <!-- * Select an appropriate "alignment" key letter based on this --> + <!-- * cell's attributes. --> + <xsl:choose> + <xsl:when test="@type = '^'"> + <xsl:text>^</xsl:text> + </xsl:when> + <xsl:when test="@align = 'center'"> + <xsl:text>c</xsl:text> + </xsl:when> + <xsl:when test="@align = 'right'"> + <xsl:text>r</xsl:text> + </xsl:when> + <xsl:when test="@align = 'char'"> + <xsl:text>n</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Default to left alignment. --> + <xsl:text>l</xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * By default, tbl(1) vertically centers cell contents within --> + <!-- * their cells; the "t" key latter tells it to top-align the --> + <!-- * contents instead. Note that tbl(1) has no options for --> + <!-- * bottom or baseline alignment. --> + <xsl:if test="@valign = 'top'"> + <xsl:text>t</xsl:text> + </xsl:if> + <xsl:if test="@class = 'th'"> + <!-- * If this is a heading row, generate a font indicator (B or I), --> + <!-- * or if the value of $tbl.font.headings is empty, nothing. --> + <xsl:value-of select="$tbl.font.headings"/> + </xsl:if> + <!-- * We only need to deal with colspans whose value is greater --> + <!-- * than one (a colspan="1" is the same as having no colspan --> + <!-- * attribute at all). --> + <xsl:if test="@colspan > 1"> + <xsl:call-template name="process.colspan"> + <xsl:with-param name="colspan" select="@colspan - 1"/> + <xsl:with-param name="type" select="@type"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <xsl:template name="process.colspan"> + <xsl:param name="colspan"/> + <xsl:param name="type"/> + <!-- * Output a space to separate this key letter from preceding one. --> + <xsl:text> </xsl:text> + <xsl:choose> + <xsl:when test="$type = '^'"> + <!-- * A '^' ("vertically spanned heading" marker) indicates --> + <!-- * that the "parent" of this spanned cell is a dummy cell; --> + <!-- * in this case, we need to generate a '^' instead of the --> + <!-- * normal 's'. --> + <xsl:text>^</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * s = 'spanned heading' --> + <xsl:text>s</xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$colspan > 1"> + <!-- * Tail recurse until we have no more colspans, outputting --> + <!-- * another marker each time. --> + <xsl:call-template name="process.colspan"> + <xsl:with-param name="colspan" select="$colspan - 1"/> + <xsl:with-param name="type" select="$type"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * colgroup and col --> + <!-- * ============================================================== --> + <!-- * We currently don't do anything with colgroup. Not sure if it --> + <!-- * is widely used enough to bother adding support for it --> + <xsl:template match="colgroup"/> + <xsl:template match="col"/> + + <!-- * ============================================================== --> + <!-- * table footnotes --> + <!-- * ============================================================== --> + <xsl:template match="footnote" mode="table.footnote.mode"> + <xsl:variable name="footnotes" select=".//footnote"/> + <xsl:variable name="table.footnotes" + select=".//tgroup//footnote"/> + <xsl:value-of select="$man.table.footnotes.divider"/> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + <xsl:apply-templates select="*[1]" mode="footnote.body.number"/> + <xsl:apply-templates select="*[position() > 1]"/> + </xsl:template> + + <!-- * The following template for footnote.body.number mode was just --> + <!-- * lifted from the HTML stylesheets with some minor adjustments --> + <xsl:template match="*" mode="footnote.body.number"> + <xsl:variable name="name"> + <xsl:text>ftn.</xsl:text> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="ancestor::footnote"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="href"> + <xsl:text>#</xsl:text> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="ancestor::footnote"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="footnote.mark"> + <xsl:text>[</xsl:text> + <xsl:apply-templates select="ancestor::footnote" + mode="footnote.number"/> + <xsl:text>] </xsl:text> + </xsl:variable> + <xsl:variable name="html"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:variable name="html-nodes" select="exsl:node-set($html)"/> + <xsl:choose> + <xsl:when test="$html-nodes//p"> + <xsl:apply-templates select="$html-nodes" mode="insert.html.p"> + <xsl:with-param name="mark" select="$footnote.mark"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$html-nodes" mode="insert.html.text"> + <xsl:with-param name="mark" select="$footnote.mark"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$html"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- * The HTML stylesheets output <sup><a>...</a></sup> around --> + <!-- * footnote markers in tables --> + <xsl:template match="th/sup"> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="a"> + <xsl:apply-templates/> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/utility.xsl b/docs/xsl-generic/manpages/utility.xsl new file mode 100644 index 00000000..cf72479b --- /dev/null +++ b/docs/xsl-generic/manpages/utility.xsl @@ -0,0 +1,452 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:dyn="http://exslt.org/dynamic" + xmlns:saxon="http://icl.com/saxon" + exclude-result-prefixes="exsl dyn saxon" + version='1.0'> + +<!-- ******************************************************************** + $Id: utility.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<!-- * This file contains "utility" templates that are called multiple --> +<!-- * times per each Refentry. --> + +<!-- ==================================================================== --> + + <!-- * NOTE TO DEVELOPERS: For ease of maintenance, the current --> + <!-- * manpages stylesheets use the "bold" and "italic" named --> + <!-- * templates for anything and everything that needs to get --> + <!-- * boldfaced or italicized. --> + <!-- * --> + <!-- * So if you add anything that needs bold or italic character --> + <!-- * formatting, try to apply these templates to it rather than --> + <!-- * writing separate code to format it. This can be a little odd if --> + <!-- * the content you want to format is not element content; in those --> + <!-- * cases, you need to turn it into element content before applying --> + <!-- * the template; see examples of this in the existing code. --> + + <xsl:template name="bold"> + <xsl:param name="node"/> + <xsl:param name="context"/> + <xsl:choose> + <xsl:when test="not($context[ancestor::title])"> + <xsl:for-each select="$node/node()"> + <xsl:text>\fB</xsl:text> + <xsl:apply-templates select="."/> + <xsl:text>\fR</xsl:text> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$node/node()"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template name="italic"> + <xsl:param name="node"/> + <xsl:param name="context"/> + <xsl:for-each select="$node/node()"> + <xsl:text>\fI</xsl:text> + <xsl:apply-templates select="."/> + <xsl:text>\fR</xsl:text> + </xsl:for-each> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * NOTE TO DEVELOPERS: For ease of maintenance, the current --> + <!-- * manpages stylesheets use the mode="prevent.line.breaking" --> + <!-- * templates for anything and everything that needs to have --> + <!-- * embedded spaces turned into no-break spaces in output - in --> + <!-- * order to prevent that output from getting broken across lines --> + <!-- * --> + <!-- * So if you add anything that whose output, try to apply this --> + <!-- * template to it rather than writing separate code to format --> + <!-- * it. This can be a little odd if the content you want to --> + <!-- * format is not element content; in those cases, you need to --> + <!-- * turn it into element content before applying the template; --> + <!-- * see examples of this in the existing code. --> + <!-- * --> + <!-- * This template is currently called by the funcdef and paramdef --> + <!-- * and group/arg templates. --> + <xsl:template mode="prevent.line.breaking" match="*"> + <xsl:variable name="rcontent"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:variable name="content"> + <xsl:value-of select="normalize-space($rcontent)"/> + </xsl:variable> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="' '"/> + <!-- * U+2580 is a "UPPER HALF BLOCK"; we use it here because --> + <!-- * if we were to just use a normal space, it would get --> + <!-- * replaced when normalization is done. We replace it --> + <!-- * later with the groff markup for non-breaking space. --> + <xsl:with-param name="replacement" select="'▀'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="suppress.hyphenation"> + <!-- * we need to suppress hyphenation inline only if hyphenation is --> + <!-- * actually on, and even then only outside of Cmdsynopsis and --> + <!-- * Funcsynopsis, where it is already always turned off --> + <xsl:if test="$man.hyphenate != 0 and + not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)"> + <xsl:text>\%</xsl:text> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The replace.dots.and.dashes template is used to cause real --> + <!-- * dots and dashes to be output in the top comment (instead of --> + <!-- * escaped ones as in the source for the text displayed in the --> + <!-- * body of the page) --> + <xsl:template name="replace.dots.and.dashes"> + <xsl:param name="content"> + <xsl:apply-templates/> + </xsl:param> + <xsl:variable name="dot-content"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="'\.'"/> + <xsl:with-param name="replacement" select="'.'"/> + </xsl:call-template> + </xsl:variable> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$dot-content"/> + <xsl:with-param name="target" select="'\-'"/> + <xsl:with-param name="replacement" select="'-'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The nested-section-title template is called for refsect3, and any --> + <!-- * refsection nested more than 2 levels deep. --> + <xsl:template name="nested-section-title"> + <!-- * The next few lines are some arcane roff code to control line --> + <!-- * spacing after headings. --> + <xsl:text>.sp </xsl:text> + <xsl:text>.it 1 an-trap </xsl:text> + <xsl:text>.nr an-no-space-flag 1 </xsl:text> + <xsl:text>.nr an-break-flag 1 </xsl:text> + <xsl:text>.br </xsl:text> + <!-- * make title wrapper so that we can use mode="bold" template to --> + <!-- * apply character formatting to it --> + <xsl:variable name="title.wrapper"> + <bold><xsl:choose> + <xsl:when test="title"> + <xsl:value-of select="normalize-space(title[1])"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="object.title.markup.textonly"/> + </xsl:otherwise> + </xsl:choose></bold> + </xsl:variable> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates mode="bold" select="exsl:node-set($title.wrapper)"/> + <xsl:text> </xsl:text> + <xsl:call-template name="mark.subheading"/> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The mixed-block template jumps through a few hoops to deal with --> + <!-- * mixed-content blocks, so that we don't end up munging verbatim --> + <!-- * environments or lists and so that we don't gobble up whitespace --> + <!-- * when we shouldn't --> + <xsl:template name="mixed-block"> + <xsl:for-each select="node()"> + <xsl:choose> + <!-- * Check to see if this node is a verbatim environment. --> + <!-- * If so, put a line of space before it. --> + <!-- * --> + <!-- * Yes, address and synopsis are vertabim environments. --> + <!-- * --> + <!-- * The code here previously also treated informaltable as a --> + <!-- * verbatim, presumably to support some kludge; I removed it --> + <xsl:when test="self::address|self::literallayout|self::programlisting| + self::screen|self::synopsis"> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + <xsl:call-template name="mark.up.block.start"/> + <xsl:apply-templates select="."/> + </xsl:when> + <!-- * Check to see if this node is a list; if it is, we don't --> + <!-- * want to normalize-space(), so we just apply-templates. --> + <!-- * Do same for all admonitions --> + <xsl:when test="(self::itemizedlist|self::orderedlist| + self::variablelist|self::glosslist| + self::simplelist[@type !='inline']| + self::segmentedlist| + self::caution|self::important| + self::note|self::tip|self::warning| + self::table|self::informaltable)"> + <xsl:call-template name="mark.up.block.start"/> + <xsl:apply-templates select="."/> + </xsl:when> + <xsl:when test="self::text()"> + <!-- * Check to see if this is a text node. --> + <!-- * --> + <!-- * If so, replace all whitespace at the beginning or end of it --> + <!-- * with a single linebreak. --> + <!-- * --> + <xsl:variable name="content"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:if + test="starts-with(translate(.,' ',' '), ' ') + and preceding-sibling::node()[1][name(.)!=''] + and normalize-space($content) != '' + and not( + preceding-sibling::*[1][ + self::caution or + self::important or + self::note or + self::tip or + self::warning or + self::variablelist or + self::glosslist or + self::itemizedlist or + self::orderedlist or + self::segmentedlist or + self::procedure or + self::address or + self::literallayout or + self::programlisting or + self::screen or + self::table or + self::informaltable + ] + ) + "> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if + test="(translate(substring(., string-length(.), 1),' ',' ') = ' ' + and following-sibling::node()[1][name(.)!='']) + or following-sibling::node()[1][self::comment()] + or following-sibling::node()[1][self::processing-instruction()] + "> + <xsl:if test="normalize-space($content) != '' + or concat(normalize-space($content), ' ') = ' '"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * At this point, we know that this node is not a verbatim --> + <!-- * environment, list, admonition, or text node; so we can --> + <!-- * safely normalize-space() it. --> + <xsl:variable name="content"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:call-template name="mark.up.block.end"/> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * Footnote and annotation contents are displayed using a hanging --> + <!-- * indent out to $man.indent.width If a paragraph-level block --> + <!-- * element (verbatim, list, or admonition) is the first block --> + <!-- * element nested at its same level within the same footnote or --> + <!-- * annotation, then we push it over by the same indent width. --> + <!-- * --> + <!-- * We don't reset the indent for each following sibling, but --> + <!-- * instead do it after for-eaching over all block siblings at --> + <!-- * the same level. So the effect is that if there are any --> + <!-- * following-sibling blocks after the block that starts this --> + <!-- * indent, then they just retain the indent that was already set --> + + <xsl:template name="mark.up.block.start"> + <xsl:choose> + <xsl:when test="(ancestor::footnote + or ancestor::annotation)"> + <xsl:if test="not(preceding-sibling::address| + preceding-sibling::literallayout| + preceding-sibling::programlisting| + preceding-sibling::screen| + preceding-sibling::synopsis| + preceding-sibling::itemizedlist| + preceding-sibling::orderedlist| + preceding-sibling::variablelist| + preceding-sibling::glosslist| + preceding-sibling::simplelist[@type !='inline']| + preceding-sibling::segmentedlist| + preceding-sibling::caution| + preceding-sibling::important| + preceding-sibling::note| + preceding-sibling::tip| + preceding-sibling::warning| + preceding-sibling::table| + preceding-sibling::informaltable + )"> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:when> + </xsl:choose> + </xsl:template> + + <!-- * Check to see if we were called from a block within a footnote or --> + <!-- * annotation; if so, and the block contains any nested block --> + <!-- * content, then we know the mark.up.block.end template was already --> + <!-- * called to generate a .RS macro to indent that nested block --> + <!-- * content; so we need to generate a .RE to set the margin back to --> + <!-- * where it was prior to the .RS call. --> + <xsl:template name="mark.up.block.end"> + <xsl:if test="(ancestor::footnote + or ancestor::annotation)"> + <xsl:if test="address| + literallayout| + programlisting| + screen| + synopsis| + itemizedlist| + orderedlist| + variablelist| + glosslist| + simplelist[@type !='inline']| + segmentedlist| + caution| + important| + note| + tip| + warning| + table| + informaltable"> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The person.name template in the HTML stylesheets outputs extra --> + <!-- * spaces that we need to strip out for manpages output. This --> + <!-- * template calls person.name, then tries to do some smart --> + <!-- * normalization of the result tree fragment from that. --> + <xsl:template name="person.name.normalized"> + <xsl:variable name="contents"> + <xsl:call-template name="person.name"/> + </xsl:variable> + <!-- * We put the output of person.name into a node-set and then we --> + <!-- * check it node-by-node and strip out space only where needed. --> + <xsl:variable name="contents.tree" select="exsl:node-set($contents)"/> + <xsl:for-each select="$contents.tree/node()"> + <xsl:choose> + <!-- * We don't want to monkey with single spaces or commas/periods --> + <!-- * followed by spaces, because those are bits of text that are --> + <!-- * actually generated by the person.name template itself (that --> + <!-- * is, they're not in the source). So, we preserve them. --> + <xsl:when test=". = ' ' or . = ', ' or . = '. '"> + <xsl:value-of select="."/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space(.)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="make.adjusted.man.filename"> + <xsl:param name="name"/> + <xsl:param name="lang"/> + <xsl:param name="name.with.lang"> + <xsl:choose> + <xsl:when test="$lang != 'en' + and not($man.output.lang.in.name.enabled = 0) + and ($man.output.subdirs.enabled = 0 or + $man.output.in.separate.dir = 0)"> + <!-- * $lang is not en (English) --> + <!-- * AND user has specified man.output.lang.in.name.enabled --> + <!-- * AND doesn't want output going into separate dirs, --> + <!-- * SO... we include the $lang value in the filename; e.g., --> + <!-- * foo.ja.1 --> + <xsl:value-of select="concat($name, '.', $lang)"/> + </xsl:when> + <xsl:otherwise> + <!-- * user either has man.output.lang.in.name.enabled unset --> + <!-- * or has set it but also has man.output.subdirs.enabled --> + <!-- * set (in which case the $lang value is used to add a --> + <!-- * $lang subdir in the pathname); in either case, we don't --> + <!-- * want to include the $lang in the filename --> + <xsl:value-of select="$name"/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:param name="section"/> + <xsl:param name="dirname"> + <xsl:if test="not($man.output.in.separate.dir = 0)"> + <xsl:choose> + <xsl:when test="not($man.output.subdirs.enabled = 0)"> + <xsl:variable name="lang.subdir"> + <xsl:if test="not($man.output.lang.in.name.enabled = 0)"> + <!-- * user has man.output.lang.in.name.enabled set, so --> + <!-- * we need to add a $lang subdir --> + <xsl:value-of select="concat($lang, '/')"/> + </xsl:if> + </xsl:variable> + <xsl:value-of + select="concat($man.output.base.dir, $lang.subdir, + 'man', normalize-space($section), '/')"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$man.output.base.dir"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:param> + <xsl:call-template name="string.subst"> + <!-- * To create the man filename, replace any spaces in filename with --> + <!-- * underscores and then append a dot plus a section number. --> + <xsl:with-param name="string" + select="concat($dirname, + normalize-space($name.with.lang), + '.', normalize-space($section))"/> + <xsl:with-param name="target" select="' '"/> + <xsl:with-param name="replacement" select="'_'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * Put a horizontal rule or other divider around section titles --> + <!-- * in roff source (just to make things easier to read). --> + <xsl:template name="mark.subheading"> + <xsl:if test="$man.subheading.divider.enabled != 0"> + <xsl:text>.\" </xsl:text> + <xsl:value-of select="$man.subheading.divider"/> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/infrastructure/BoxPlatform.pm.in b/infrastructure/BoxPlatform.pm.in new file mode 100644 index 00000000..59ab5d85 --- /dev/null +++ b/infrastructure/BoxPlatform.pm.in @@ -0,0 +1,132 @@ +package BoxPlatform; +use Exporter; +@ISA = qw/Exporter/; +@EXPORT = qw/$build_os $target_os $make_command $bsd_make $platform_define $platform_cpu $gcc_v3 $product_version $product_name $install_into_dir $sub_make_options $platform_compile_line_extra $platform_link_line_extra $platform_lib_files $platform_exe_ext $target_windows/; + +BEGIN +{ + # which OS are we building under? + $target_os = '@target_os@'; + $target_windows = 0; + $target_windows = 1 if $target_os =~ m'^mingw32' + or $target_os eq "winnt"; + + if ($^O eq "MSWin32" and not -x "/usr/bin/uname") + { + $build_os = "winnt"; + } + else + { + $build_os = `uname`; + chomp $build_os; + } + + # 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/; + + $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'); + + # blank extra flags by default + $platform_compile_line_extra = ''; + $platform_link_line_extra = ''; + $platform_lib_files = '@LIBS@'; + $platform_exe_ext = '@EXEEXT@'; + + # get version + my $version_file = "VERSION.txt"; + if (not -r $version_file) { $version_file = "../../$version_file" } + die "missing version file: $version_file" unless $version_file; + + open VERSION, $version_file or die "$version_file: $!"; + $product_version = <VERSION>; + chomp $product_version; + $product_name = <VERSION>; + chomp $product_name; + close VERSION; + + if($product_version =~ /USE_SVN_VERSION/) + { + # for developers, use SVN version + my $svnversion = `svnversion .`; + chomp $svnversion; + $svnversion =~ tr/0-9A-Za-z/_/c; + open INFO,'svn info . |'; + my $svnurl; + while(<INFO>) + { + if(m/^URL: (.+?)[\n\r]+/) + { + $svnurl = $1 + } + } + close INFO; + + my $svndir; + if ($svnurl =~ m!/box/(.+)$!) + { + $svndir = $1; + } + elsif ($svnurl =~ m'/(boxi/.+)/boxi/boxbackup') + { + $svndir = $1; + } + + $svndir =~ tr/0-9A-Za-z/_/c; + $product_version =~ s/USE_SVN_VERSION/$svndir.'_'.$svnversion/e; + } + + # where to put the files + $install_into_dir = '@sbindir_expanded@'; + + # if it's Darwin, + if($build_os eq 'Darwin') + { + # see how many processors there are, and set make flags accordingly + my $cpus = `sysctl hw.ncpu`; + if($cpus =~ m/hw.ncpu:\s(\d+)/ && $1 > 1) + { + print STDERR "$1 processors detected, will set make to perform concurrent jobs\n"; + $sub_make_options = ' -j '.($1 + 1); + } + + # test for fink installation + if(-d '/sw/include' && -d '/sw/lib') + { + print STDERR "Fink installation detected, will use headers and libraries\n"; + $platform_compile_line_extra = '-I/sw/include '; + $platform_link_line_extra = '-L/sw/lib '; + } + } +} + +sub make_flag +{ + if($bsd_make) + { + return "-D $_[0]" + } + return $_[0].'=1'; +} + +sub parcel_root +{ + my $tos = $_[1] || $target_os; + return $product_name.'-'.$product_version.'-'.$_[0].'-'.$tos; +} + +sub parcel_dir +{ + 'parcels/'.parcel_root($_[0], $_[1]) +} + +sub parcel_target +{ + parcel_dir($_[0]).'.tgz' +} + +1; + diff --git a/infrastructure/buildenv-testmain-template.cpp b/infrastructure/buildenv-testmain-template.cpp new file mode 100644 index 00000000..b646a27b --- /dev/null +++ b/infrastructure/buildenv-testmain-template.cpp @@ -0,0 +1,390 @@ +// +// AUTOMATICALLY GENERATED FILE +// do not edit +// +// Note that infrastructure/buildenv-testmain-template.cpp is NOT +// auto-generated, but test/*/_main.cpp are generated from it. +// + + +// -------------------------------------------------------------------------- +// +// File +// Name: testmain.template.h +// Purpose: Template file for running tests +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> + +#ifdef HAVE_GETOPT_H + #include <getopt.h> +#endif + +#include <sys/stat.h> +#include <sys/types.h> + +#include <exception> +#include <string> + +#include "Logging.h" +#include "Test.h" +#include "Timer.h" + +#include "MemLeakFindOn.h" + +int test(int argc, const char *argv[]); + +#ifdef BOX_RELEASE_BUILD + #define MODE_TEXT "release" +#else + #define MODE_TEXT "debug" +#endif + +int failures = 0; +int first_fail_line; +std::string first_fail_file; + +#ifdef WIN32 + #define QUIET_PROCESS "-Q" +#else + #define QUIET_PROCESS "" +#endif + +std::string bbackupd_args = QUIET_PROCESS, + bbstored_args = QUIET_PROCESS, + bbackupquery_args, + test_args; + +int filedes_open_at_beginning = -1; + +#ifdef WIN32 + +// any way to check for open file descriptors on Win32? +inline bool check_filedes(bool x) { return 0; } +inline bool checkfilesleftopen() { return false; } + +#else // !WIN32 + +#define FILEDES_MAX 256 + +bool filedes_open[FILEDES_MAX]; + +bool check_filedes(bool report) +{ + bool allOk = true; + + // See how many file descriptors there are with values < 256 + for(int d = 0; d < FILEDES_MAX; ++d) + { + if(::fcntl(d, F_GETFD) != -1) + { + // File descriptor obviously exists + if (report && !filedes_open[d]) + { + struct stat st; + if (fstat(d, &st) == 0) + { + int m = st.st_mode; + #define flag(x) ((m & x) ? #x " " : "") + BOX_FATAL("File descriptor " << d << + " left open (type == " << + flag(S_IFIFO) << + flag(S_IFCHR) << + flag(S_IFDIR) << + flag(S_IFBLK) << + flag(S_IFREG) << + flag(S_IFLNK) << + flag(S_IFSOCK) << + " or " << m << ")"); + } + else + { + BOX_FATAL("File descriptor " << d << + " left open (and stat failed)"); + } + + allOk = false; + + } + else if (!report) + { + filedes_open[d] = true; + } + } + else + { + if (report && filedes_open[d]) + { + BOX_FATAL("File descriptor " << d << + " was open, now closed"); + allOk = false; + } + else + { + filedes_open[d] = false; + } + } + } + + if (!report && allOk) + { + filedes_open_at_beginning = 0; + } + + return !allOk; +} + +bool checkfilesleftopen() +{ + if(filedes_open_at_beginning == -1) + { + // Not used correctly, pretend that there were things + // left open so this gets investigated + BOX_FATAL("File descriptor test was not initialised"); + return true; + } + + // Count the file descriptors open + return check_filedes(true); +} + +#endif + +int main(int argc, char * const * argv) +{ + // Start memory leak testing + MEMLEAKFINDER_START + + Logging::SetProgramName(BOX_MODULE); + +#ifdef HAVE_GETOPT_H + #ifdef BOX_RELEASE_BUILD + int logLevel = Log::NOTICE; // need an int to do math with + #else + int logLevel = Log::INFO; // need an int to do math with + #endif + + struct option longopts[] = + { + { "bbackupd-args", required_argument, NULL, 'c' }, + { "bbstored-args", required_argument, NULL, 's' }, + { "test-daemon-args", required_argument, NULL, 'd' }, + { NULL, 0, NULL, 0 } + }; + + int ch; + + while ((ch = getopt_long(argc, argv, "c:d:qs:t:vPTUV", longopts, NULL)) + != -1) + { + switch(ch) + { + case 'c': + { + if (bbackupd_args.length() > 0) + { + bbackupd_args += " "; + } + bbackupd_args += optarg; + } + break; + + case 'd': + { + if (test_args.length() > 0) + { + test_args += " "; + } + test_args += optarg; + } + break; + + case 's': + { + bbstored_args += " "; + bbstored_args += optarg; + } + break; + + #ifndef WIN32 + case 'P': + { + Console::SetShowPID(true); + } + break; + #endif + + case 'q': + { + if(logLevel == Log::NOTHING) + { + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; + } + logLevel--; + } + break; + + case 'v': + { + if(logLevel == Log::EVERYTHING) + { + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; + } + logLevel++; + } + break; + + case 'V': + { + logLevel = Log::EVERYTHING; + } + break; + + case 't': + { + Logging::SetProgramName(optarg); + Console::SetShowTag(true); + } + break; + + case 'T': + { + Console::SetShowTime(true); + } + break; + + case 'U': + { + Console::SetShowTime(true); + Console::SetShowTimeMicros(true); + } + break; + + case '?': + { + fprintf(stderr, "Unknown option: '%c'\n", + optopt); + exit(2); + } + + default: + { + fprintf(stderr, "Unknown option code '%c'\n", + ch); + exit(2); + } + } + } + + Logging::SetGlobalLevel((Log::Level)logLevel); + + argc -= optind - 1; + argv += optind - 1; +#endif // HAVE_GETOPT_H + + // If there is more than one argument, then the test is doing something advanced, so leave it alone + bool fulltestmode = (argc == 1); + + if(fulltestmode) + { + // banner + BOX_NOTICE("Running test TEST_NAME in " MODE_TEXT " mode..."); + + // Count open file descriptors for a very crude "files left open" test + check_filedes(false); + + #ifdef WIN32 + // Under win32 we must initialise the Winsock library + // before using sockets + + WSADATA info; + TEST_THAT(WSAStartup(0x0101, &info) != SOCKET_ERROR) + #endif + } + + try + { + #ifdef BOX_MEMORY_LEAK_TESTING + memleakfinder_init(); + #endif + + Timers::Init(); + int returncode = test(argc, (const char **)argv); + Timers::Cleanup(); + + fflush(stdout); + fflush(stderr); + + // check for memory leaks, if enabled + #ifdef BOX_MEMORY_LEAK_TESTING + if(memleakfinder_numleaks() != 0) + { + failures++; + printf("FAILURE: Memory leaks detected in test code\n"); + printf("==== MEMORY LEAKS =================================\n"); + memleakfinder_reportleaks(); + printf("===================================================\n"); + } + #endif + + if(fulltestmode) + { + bool filesleftopen = checkfilesleftopen(); + + fflush(stdout); + fflush(stderr); + + if(filesleftopen) + { + failures++; + printf("IMPLICIT TEST FAILED: Something left files open\n"); + } + if(failures > 0) + { + printf("FAILED: %d tests failed (first at " + "%s:%d)\n", failures, + first_fail_file.c_str(), + first_fail_line); + } + else + { + printf("PASSED\n"); + } + } + + return returncode; + } + catch(std::exception &e) + { + printf("FAILED: Exception caught: %s\n", e.what()); + return 1; + } + catch(...) + { + printf("FAILED: Unknown exception caught\n"); + return 1; + } + if(fulltestmode) + { + if(checkfilesleftopen()) + { + printf("WARNING: Files were left open\n"); + } + } +} + diff --git a/infrastructure/m4/ac_cxx_exceptions.m4 b/infrastructure/m4/ac_cxx_exceptions.m4 new file mode 100644 index 00000000..4c3013dc --- /dev/null +++ b/infrastructure/m4/ac_cxx_exceptions.m4 @@ -0,0 +1,24 @@ +dnl @synopsis AC_CXX_EXCEPTIONS +dnl +dnl If the C++ compiler supports exceptions handling (try, throw and +dnl catch), define HAVE_EXCEPTIONS. +dnl +dnl @category Cxx +dnl @author Todd Veldhuizen +dnl @author Luc Maisonobe <luc@spaceroots.org> +dnl @version 2004-02-04 +dnl @license AllPermissive + +AC_DEFUN([AC_CXX_EXCEPTIONS], +[AC_CACHE_CHECK(whether the compiler supports exceptions, +ac_cv_cxx_exceptions, +[AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE(,[try { throw 1; } catch (int i) { return i; }], + ac_cv_cxx_exceptions=yes, ac_cv_cxx_exceptions=no) + AC_LANG_RESTORE +]) +if test "$ac_cv_cxx_exceptions" = yes; then + AC_DEFINE(HAVE_EXCEPTIONS,,[define if the compiler supports exceptions]) +fi +]) diff --git a/infrastructure/m4/ac_cxx_namespaces.m4 b/infrastructure/m4/ac_cxx_namespaces.m4 new file mode 100644 index 00000000..2f18477b --- /dev/null +++ b/infrastructure/m4/ac_cxx_namespaces.m4 @@ -0,0 +1,25 @@ +dnl @synopsis AC_CXX_NAMESPACES +dnl +dnl If the compiler can prevent names clashes using namespaces, define +dnl HAVE_NAMESPACES. +dnl +dnl @category Cxx +dnl @author Todd Veldhuizen +dnl @author Luc Maisonobe <luc@spaceroots.org> +dnl @version 2004-02-04 +dnl @license AllPermissive + +AC_DEFUN([AC_CXX_NAMESPACES], +[AC_CACHE_CHECK(whether the compiler implements namespaces, +ac_cv_cxx_namespaces, +[AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([namespace Outer { namespace Inner { int i = 0; }}], + [using namespace Outer::Inner; return i;], + ac_cv_cxx_namespaces=yes, ac_cv_cxx_namespaces=no) + AC_LANG_RESTORE +]) +if test "$ac_cv_cxx_namespaces" = yes; then + AC_DEFINE(HAVE_NAMESPACES,,[define if the compiler implements namespaces]) +fi +]) diff --git a/infrastructure/m4/ax_bswap64.m4 b/infrastructure/m4/ax_bswap64.m4 new file mode 100644 index 00000000..8110743c --- /dev/null +++ b/infrastructure/m4/ax_bswap64.m4 @@ -0,0 +1,52 @@ +dnl @synopsis AX_BSWAP64 +dnl +dnl This macro will check for a built in way of endian reversing an int64_t. +dnl If one is found then HAVE_BSWAP64 is set to 1 and BSWAP64 will be defined +dnl to the name of the endian swap function. +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2006/02/02 +dnl @license AllPermissive + +AC_DEFUN([AX_BSWAP64], [ + bswap64_function="" + AC_CHECK_HEADERS([sys/endian.h asm/byteorder.h]) + if test "x$ac_cv_header_sys_endian_h" = "xyes"; then + AC_CACHE_CHECK([for htobe64], [box_cv_have_htobe64], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include <sys/endian.h> + ]], [[ + htobe64(0); + return 1; + ]])], + [box_cv_have_htobe64=yes], [box_cv_have_htobe64=no] + )]) + if test "x$box_cv_have_htobe64" = "xyes"; then + bswap64_function=htobe64 + fi + fi + if test "x$bswap64_function" = "x" && \ + test "x$ac_cv_header_asm_byteorder_h" = "xyes"; then + AC_CACHE_CHECK([for __cpu_to_be64], [box_cv_have___cpu_to_be64], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include <asm/byteorder.h> + ]], [[ + __cpu_to_be64(0); + return 1; + ]])], + [box_cv_have___cpu_to_be64=yes], [box_cv_have___cpu_to_be64=no] + )]) + if test "x$box_cv_have___cpu_to_be64" = "xyes"; then + bswap64_function=__cpu_to_be64 + fi + fi + + if test "x$bswap64_function" != "x"; then + AC_DEFINE([HAVE_BSWAP64], 1, + [Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian swapping function]) + AC_DEFINE_UNQUOTED([BSWAP64], [$bswap64_function], [Name of the 64 bit endian swapping function]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_bdb_v1.m4 b/infrastructure/m4/ax_check_bdb_v1.m4 new file mode 100644 index 00000000..fc5e0692 --- /dev/null +++ b/infrastructure/m4/ax_check_bdb_v1.m4 @@ -0,0 +1,43 @@ +dnl @synopsis AX_CHECK_BDB_V1 +dnl +dnl This macro find an installation of Berkeley DB version 1, or compatible. +dnl It will define the following macros on success: +dnl +dnl HAVE_DB - If Berkeley DB version 1 or compatible is available +dnl DB_HEADER - The relative path and filename of the header file +dnl LIBS - Updated for correct library +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_BDB_V1], [ + ac_have_bdb=no + AC_CHECK_HEADERS([db_185.h db4/db_185.h db3/db_185.h db1/db.h db.h], + [ac_bdb_header=$ac_header; break], [ac_bdb_header=""]) + if test "x$ac_bdb_header" != x; then + AC_SEARCH_LIBS([__db185_open], + [db db-4.4 db-4.3 db-4.2 db-4.1 db-4.0 db4 db-3 db3], + [ac_have_bdb=yes], + [AC_SEARCH_LIBS([dbopen], [db-1 db1 db], [ac_have_bdb=yes])]) + fi + if test "x$ac_have_bdb" = "xyes"; then + AC_MSG_CHECKING([whether found db libraries work]) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include "$ac_bdb_header"]], [[ + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + return 0; + ]])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_DB], 1, [Define to 1 if Berkeley DB is available]) + AC_DEFINE_UNQUOTED([DB_HEADER], ["$ac_bdb_header"], + [Define to the location of the Berkeley DB 1.85 header]) + ], [ + AC_MSG_RESULT([no]) + ac_have_bdb=no + ]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_define_pragma.m4 b/infrastructure/m4/ax_check_define_pragma.m4 new file mode 100644 index 00000000..e3f7fe89 --- /dev/null +++ b/infrastructure/m4/ax_check_define_pragma.m4 @@ -0,0 +1,25 @@ +dnl @synopsis AX_CHECK_DEFINE_PRAGMA([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if the compiler will accept #pragma inside a +dnl #define. HAVE_DEFINE_PRAGMA will be defined if this is the case, and +dnl ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_DEFINE_PRAGMA], [ + AC_CACHE_CHECK([for pre-processor pragma defines], [box_cv_have_define_pragma], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + #define TEST_DEFINE #pragma pack(1) + TEST_DEFINE + ]])], + [box_cv_have_define_pragma=yes], [box_cv_have_define_pragma=no] + )]) + if test "x$box_cv_have_define_pragma" = "xyes"; then + AC_DEFINE([HAVE_DEFINE_PRAGMA], 1, [Define to 1 if #define of pragmas works]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_dirent_d_type.m4 b/infrastructure/m4/ax_check_dirent_d_type.m4 new file mode 100644 index 00000000..078a39ee --- /dev/null +++ b/infrastructure/m4/ax_check_dirent_d_type.m4 @@ -0,0 +1,45 @@ +dnl @synopsis AX_CHECK_DIRENT_D_TYPE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if struct dirent.d_type is present and supported. +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_STRUCT_DIRENT_D_TYPE +dnl HAVE_VALID_DIRENT_D_TYPE +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_DIRENT_D_TYPE], [ + AC_CHECK_MEMBERS([struct dirent.d_type],,, [[#include <dirent.h>]]) + if test "x$ac_cv_member_struct_dirent_d_type" = "xyes"; then + AC_CACHE_CHECK([[whether struct dirent.d_type is valid]], [box_cv_have_valid_dirent_d_type], + [AC_TRY_RUN( + [ + $ac_includes_default + #include <dirent.h> + int main() + { + DIR* dir = opendir("."); + struct dirent* res = NULL; + if(dir) res = readdir(dir); + return res ? (res->d_type != DT_FILE && res->d_type != DT_DIR) : 1; + } + ], + [box_cv_have_valid_dirent_d_type=yes], + [box_cv_have_valid_dirent_d_type=no], + [box_cv_have_valid_dirent_d_type=cross] + )]) + if test "x$box_cv_have_valid_dirent_d_type" = "xyes"; then + AC_DEFINE([HAVE_VALID_DIRENT_D_TYPE], 1, [Define to 1 if struct dirent.d_type is valid]) + fi + fi + if test "x$ac_cv_member_struct_dirent_d_type" = "xyes" || \ + test "x$box_cv_have_valid_dirent_d_type" = "xyes" + then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_llong_minmax.m4 b/infrastructure/m4/ax_check_llong_minmax.m4 new file mode 100644 index 00000000..f3f99c53 --- /dev/null +++ b/infrastructure/m4/ax_check_llong_minmax.m4 @@ -0,0 +1,76 @@ +dnl @synopsis AX_CHECK_LLONG_MINMAX +dnl +dnl This macro will fix up LLONG_MIN and LLONG_MAX as appropriate. I'm finding +dnl it quite difficult to believe that so many hoops are necessary. The world +dnl seems to have gone quite mad. +dnl +dnl This gem is adapted from the OpenSSH configure script so here's +dnl the original copyright notice: +dnl +dnl Copyright (c) 1999-2004 Damien Miller +dnl +dnl Permission to use, copy, modify, and distribute this software for any +dnl purpose with or without fee is hereby granted, provided that the above +dnl copyright notice and this permission notice appear in all copies. +dnl +dnl @category C +dnl @author Martin Ebourne and Damien Miller +dnl @version 2005/07/07 + +AC_DEFUN([AX_CHECK_LLONG_MINMAX], [ + AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], , [[#include <limits.h>]]) + if test -z "$have_llong_max"; then + AC_MSG_CHECKING([[for max value of long long]]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #include <stdio.h> + /* Why is this so damn hard? */ + #undef __GNUC__ + #undef __USE_ISOC99 + #define __USE_ISOC99 + #include <limits.h> + #define DATA "conftest.llminmax" + int main(void) { + FILE *f; + long long i, llmin, llmax = 0; + + if((f = fopen(DATA,"w")) == NULL) + exit(1); + + #if defined(LLONG_MIN) && defined(LLONG_MAX) + fprintf(stderr, "Using system header for LLONG_MIN and LLONG_MAX\n"); + llmin = LLONG_MIN; + llmax = LLONG_MAX; + #else + fprintf(stderr, "Calculating LLONG_MIN and LLONG_MAX\n"); + /* This will work on one's complement and two's complement */ + for (i = 1; i > llmax; i <<= 1, i++) + llmax = i; + llmin = llmax + 1LL; /* wrap */ + #endif + + /* Sanity check */ + if (llmin + 1 < llmin || llmin - 1 < llmin || llmax + 1 > llmax || llmax - 1 > llmax) { + fprintf(f, "unknown unknown\n"); + exit(2); + } + + if (fprintf(f ,"%lld %lld", llmin, llmax) < 0) + exit(3); + + exit(0); + } + ]])], [ + read llong_min llong_max < conftest.llminmax + AC_MSG_RESULT([$llong_max]) + AC_DEFINE_UNQUOTED([LLONG_MAX], [${llong_max}LL], + [max value of long long calculated by configure]) + AC_MSG_CHECKING([[for min value of long long]]) + AC_MSG_RESULT([$llong_min]) + AC_DEFINE_UNQUOTED([LLONG_MIN], [${llong_min}LL], + [min value of long long calculated by configure]) + ], + [AC_MSG_RESULT(not found)], + [AC_MSG_WARN([[cross compiling: not checking]])] + ) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_malloc_workaround.m4 b/infrastructure/m4/ax_check_malloc_workaround.m4 new file mode 100644 index 00000000..7655c0f7 --- /dev/null +++ b/infrastructure/m4/ax_check_malloc_workaround.m4 @@ -0,0 +1,38 @@ +dnl @synopsis AX_CHECK_MALLOC_WORKAROUND +dnl +dnl This macro will see if there is a potential STL memory leak, and if we can +dnl work around it will define __USE_MALLOC as the fix. +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_MALLOC_WORKAROUND], [ + if test "x$GXX" = "xyes"; then + AC_CACHE_CHECK([for gcc version 3 or later], [box_cv_gcc_3_plus], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + #if __GNUC__ < 3 + #error "Old GNU C" + #endif + ]])], + [box_cv_gcc_3_plus=yes], [box_cv_gcc_3_plus=no] + )]) + if test "x$box_cv_gcc_3_plus" = "xno"; then + AC_CACHE_CHECK([for malloc workaround], [box_cv_malloc_workaround], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define __USE_MALLOC + #include <string> + ]], [[ + std::string s; + s = "test"; + ]])], + [box_cv_malloc_workaround=yes], [box_cv_malloc_workaround=no] + )]) + if test "x$box_cv_malloc_workaround" = "xyes"; then + AC_DEFINE([__USE_MALLOC], 1, + [Define to 1 if __USE_MALLOC is required work around STL memory leaks]) + fi + fi + fi + ])dnl diff --git a/infrastructure/m4/ax_check_mount_point.m4 b/infrastructure/m4/ax_check_mount_point.m4 new file mode 100644 index 00000000..d26bf3e5 --- /dev/null +++ b/infrastructure/m4/ax_check_mount_point.m4 @@ -0,0 +1,60 @@ +dnl @synopsis AX_CHECK_MOUNT_POINT([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out how to get mount point information if possible. +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_MOUNTS +dnl HAVE_MNTENT_H +dnl HAVE_SYS_MNTTAB_H +dnl HAVE_SYS_MOUNT_H +dnl HAVE_STRUCT_MNTENT_MNT_DIR +dnl HAVE_STRUCT_MNTTAB_MNT_MOUNTP +dnl HAVE_STRUCT_STATFS_F_MNTONNAME +dnl HAVE_STRUCT_STATVFS_F_MNTONNAME +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_MOUNT_POINT], [ + AC_CHECK_FUNCS([getmntent statfs]) + AC_CHECK_HEADERS([sys/param.h]) + AC_CHECK_HEADERS([mntent.h sys/mnttab.h sys/mount.h],,, [[ + #include <stdio.h> + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + ]]) + # BSD + AC_CHECK_MEMBERS([struct statfs.f_mntonname],,, [[ + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + #include <sys/mount.h> + ]]) + # NetBSD + AC_CHECK_MEMBERS([struct statvfs.f_mntonname],,, [[ + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + #include <sys/mount.h> + ]]) + # Linux + AC_CHECK_MEMBERS([struct mntent.mnt_dir],,, [[#include <mntent.h>]]) + # Solaris + AC_CHECK_MEMBERS([struct mnttab.mnt_mountp],,, [[ + #include <stdio.h> + #include <sys/mnttab.h> + ]]) + if test "x$ac_cv_member_struct_statfs_f_mntonname" = "xyes" || \ + test "x$ac_cv_member_struct_statvfs_f_mntonname" = "xyes" || \ + test "x$ac_cv_member_struct_mntent_mnt_dir" = "xyes" || \ + test "x$ac_cv_member_struct_mnttab_mnt_mountp" = "xyes" + then + AC_DEFINE([HAVE_MOUNTS], [1], [Define to 1 if this platform supports mounts]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_nonaligned_access.m4 b/infrastructure/m4/ax_check_nonaligned_access.m4 new file mode 100644 index 00000000..ab2d0b7e --- /dev/null +++ b/infrastructure/m4/ax_check_nonaligned_access.m4 @@ -0,0 +1,57 @@ +dnl @synopsis AX_CHECK_NONALIGNED_ACCESS +dnl +dnl This macro will see if non-aligned memory accesses will fail. The following +dnl defines will be made as appropriate: +dnl HAVE_ALIGNED_ONLY_INT16 +dnl HAVE_ALIGNED_ONLY_INT32 +dnl HAVE_ALIGNED_ONLY_INT64 +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_NONALIGNED_ACCESS], [ + AC_CACHE_CHECK([if non-aligned 16 bit word accesses fail], [box_cv_have_aligned_only_int16], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT16_T + #define uint16_t u_int16_t; + #endif + uint16_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint16_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int16=no], [box_cv_have_aligned_only_int16=yes] + )]) + if test "x$box_cv_have_aligned_only_int16" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT16], 1, [Define to 1 if non-aligned int16 access will fail]) + fi + AC_CACHE_CHECK([if non-aligned 32 bit word accesses fail], [box_cv_have_aligned_only_int32], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT32_T + #define uint32_t u_int32_t; + #endif + uint32_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint32_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int32=no], [box_cv_have_aligned_only_int32=yes] + )]) + if test "x$box_cv_have_aligned_only_int32" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT32], 1, [Define to 1 if non-aligned int32 access will fail]) + fi + AC_CACHE_CHECK([if non-aligned 64 bit word accesses fail], [box_cv_have_aligned_only_int64], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT64_T + #define uint64_t u_int64_t; + #endif + uint64_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint64_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int64=no], [box_cv_have_aligned_only_int64=yes] + )]) + if test "x$box_cv_have_aligned_only_int64" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT64], 1, [Define to 1 if non-aligned int64 access will fail]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_ssl.m4 b/infrastructure/m4/ax_check_ssl.m4 new file mode 100644 index 00000000..03362bb6 --- /dev/null +++ b/infrastructure/m4/ax_check_ssl.m4 @@ -0,0 +1,37 @@ +dnl @synopsis AX_CHECK_SSL([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will check for OpenSSL in the standard path, allowing the user +dnl to specify a directory if it is not found. The user uses +dnl '--with-ssl-headers=/path/to/headers' or +dnl '--with-ssl-lib=/path/to/lib' as arguments to configure. +dnl +dnl If OpenSSL is found the include directory gets added to CPPFLAGS, +dnl '-lcrypto', '-lssl', and the libraries directory are added to LDFLAGS. +dnl Also HAVE_SSL is defined to 1, and ACTION-IF-TRUE and ACTION-IF-FALSE are +dnl run as appropriate +dnl +dnl @category InstalledPackages +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_SSL], [ + AC_ARG_WITH( + [ssl-headers], + [AC_HELP_STRING([--with-ssl-headers=DIR], [SSL include files location])], + [CPPFLAGS="$CPPFLAGS -I$withval"]) + AC_ARG_WITH( + [ssl-lib], + [AC_HELP_STRING([--with-ssl-lib=DIR], [SSL library location])], + [LDFLAGS="$LDFLAGS -L$withval"]) + + ax_check_ssl_found=yes + AC_CHECK_HEADERS([openssl/ssl.h],, [ax_check_ssl_found=no]) + AC_CHECK_LIB([ssl], [SSL_read],, [ax_check_ssl_found=no], [-lcrypto]) + + if test "x$ax_check_ssl_found" = "xyes"; then + AC_DEFINE([HAVE_SSL], 1, [Define to 1 if SSL is available]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_syscall_lseek.m4 b/infrastructure/m4/ax_check_syscall_lseek.m4 new file mode 100644 index 00000000..9fd04c81 --- /dev/null +++ b/infrastructure/m4/ax_check_syscall_lseek.m4 @@ -0,0 +1,69 @@ +dnl @synopsis AX_CHECK_SYSCALL_LSEEK([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if the lseek syscall requires a dummy middle +dnl parameter +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_LSEEK_DUMMY_PARAM +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_SYSCALL_LSEEK], [ + AC_REQUIRE([AX_FUNC_SYSCALL])dnl + if test "x$ac_cv_header_sys_syscall_h" = "xyes"; then + AC_CACHE_CHECK([[whether syscall lseek requires dummy parameter]], [box_cv_have_lseek_dummy_param], + [AC_TRY_RUN( + [ + $ac_includes_default + #include <fcntl.h> + #include <sys/syscall.h> + #ifdef HAVE___SYSCALL_NEED_DEFN + extern "C" off_t __syscall(quad_t number, ...); + #endif + #ifdef HAVE___SYSCALL // always use it if we have it + #undef syscall + #define syscall __syscall + #endif + int main() + { + int fh = creat("lseektest", 0600); + int res = 0; + if(fh>=0) + { + // This test tries first to seek to position 0, with NO + // "dummy argument". If lseek does actually require a dummy + // argument, then it will eat SEEK_SET for the offset and + // try to use 99 as whence, which is invalid, so res will be + // -1, the program will return zero and + // have_lseek_dummy_param=yes + // (whew! that took 1 hour to figure out) + // The "dummy argument" probably means that it takes a 64-bit + // offset, so this was probably a bug anyway, and now that + // we cast the offset to off_t, it should never be needed + // (if my reasoning is correct). + res = syscall(SYS_lseek, fh, (off_t)0, SEEK_SET, 99); + close(fh); + } + unlink("lseektest"); + return res!=-1; + } + ], + [box_cv_have_lseek_dummy_param=yes], + [box_cv_have_lseek_dummy_param=no], + [box_cv_have_lseek_dummy_param=no # assume not for cross-compiling] + )]) + if test "x$box_cv_have_lseek_dummy_param" = "xyes"; then + AC_DEFINE([HAVE_LSEEK_DUMMY_PARAM], 1, + [Define to 1 if syscall lseek requires a dummy middle parameter]) + fi + fi + if test "x$box_cv_have_lseek_dummy_param" = "xno" + then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_compare_version.m4 b/infrastructure/m4/ax_compare_version.m4 new file mode 100644 index 00000000..bd6c51b2 --- /dev/null +++ b/infrastructure/m4/ax_compare_version.m4 @@ -0,0 +1,162 @@ +dnl @synopsis AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro compares two version strings. It is used heavily in the +dnl macro _AX_PATH_BDB for library checking. Due to the various number +dnl of minor-version numbers that can exist, and the fact that string +dnl comparisons are not compatible with numeric comparisons, this is +dnl not necessarily trivial to do in a autoconf script. This macro +dnl makes doing these comparisons easy. +dnl +dnl The six basic comparisons are available, as well as checking +dnl equality limited to a certain number of minor-version levels. +dnl +dnl The operator OP determines what type of comparison to do, and can +dnl be one of: +dnl +dnl eq - equal (test A == B) +dnl ne - not equal (test A != B) +dnl le - less than or equal (test A <= B) +dnl ge - greater than or equal (test A >= B) +dnl lt - less than (test A < B) +dnl gt - greater than (test A > B) +dnl +dnl Additionally, the eq and ne operator can have a number after it to +dnl limit the test to that number of minor versions. +dnl +dnl eq0 - equal up to the length of the shorter version +dnl ne0 - not equal up to the length of the shorter version +dnl eqN - equal up to N sub-version levels +dnl neN - not equal up to N sub-version levels +dnl +dnl When the condition is true, shell commands ACTION-IF-TRUE are run, +dnl otherwise shell commands ACTION-IF-FALSE are run. The environment +dnl variable 'ax_compare_version' is always set to either 'true' or +dnl 'false' as well. +dnl +dnl Examples: +dnl +dnl AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +dnl AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +dnl +dnl would both be true. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +dnl AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +dnl +dnl would both be false. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +dnl +dnl would be true because it is only comparing two minor versions. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +dnl +dnl would be true because it is only comparing the lesser number of +dnl minor versions of the two values. +dnl +dnl Note: The characters that separate the version numbers do not +dnl matter. An empty string is the same as version 0. OP is evaluated +dnl by autoconf, not configure, so must be a string, not a variable. +dnl +dnl The author would like to acknowledge Guido Draheim whose advice +dnl about the m4_case and m4_ifvaln functions make this macro only +dnl include the portions necessary to perform the specific comparison +dnl specified by the OP argument in the final configure script. +dnl +dnl @category Misc +dnl @author Tim Toolan <toolan@ele.uri.edu> +dnl @version 2004-03-01 +dnl @license GPLWithACException + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | awk '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | awk '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION diff --git a/infrastructure/m4/ax_config_scripts.m4 b/infrastructure/m4/ax_config_scripts.m4 new file mode 100644 index 00000000..8f5436ae --- /dev/null +++ b/infrastructure/m4/ax_config_scripts.m4 @@ -0,0 +1,16 @@ +dnl @synopsis AX_CONFIG_SCRIPTS(SCRIPT_FILE, ...) +dnl +dnl Run AC_CONFIG_FILES on a list of scripts while preserving execute +dnl permission. +dnl +dnl @category Automake +dnl @author Martin Ebourne <martin@zepler.org> +dnl @script +dnl @license AllPermissive + +AC_DEFUN([AX_CONFIG_SCRIPTS],[ + AC_REQUIRE([AC_CONFIG_FILES])dnl + m4_foreach([SCRIPT_FILE], + m4_quote(m4_split(m4_normalize([$1]))), + [AC_CONFIG_FILES(SCRIPT_FILE, m4_quote(chmod +x SCRIPT_FILE))])dnl +]) diff --git a/infrastructure/m4/ax_func_syscall.m4 b/infrastructure/m4/ax_func_syscall.m4 new file mode 100644 index 00000000..2429ca93 --- /dev/null +++ b/infrastructure/m4/ax_func_syscall.m4 @@ -0,0 +1,50 @@ +dnl @synopsis AX_FUNC_SYSCALL +dnl +dnl This macro will find out how to call syscall. One or more of the following +dnl defines will be made as appropriate: +dnl HAVE_UNISTD_H - If unistd.h is available +dnl HAVE_SYS_SYSCALL_H - If sys/syscall.h is available +dnl HAVE_SYSCALL - If syscall() is available and is defined in unistd.h +dnl HAVE___SYSCALL - If __syscall() is available and is defined in unistd.h +dnl HAVE___SYSCALL_NEED_DEFN - If __syscall() is available but is not defined in unistd.h +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive +dnl +dnl Changed by Chris on 081026: +dnl +dnl Reversed the test for __syscall(), remove the test for syscall(), +dnl remove the definition and reverse the sense in ax_func_syscall.m4 +dnl (which checks for __syscall() needing definition). +dnl +dnl Autoconf's AC_CHECK_FUNC defines it when testing for its presence, +dnl so HAVE___SYSCALL will be true even if __syscall has no definition +dnl in the system libraries, and this is precisely the case that we +dnl want to test for, so now we test whether the test program compiles +dnl with no explicit definition (only the system headers) and if that +dnl fails, we set HAVE___SYSCALL_NEED_DEFN to 1. + +AC_DEFUN([AX_FUNC_SYSCALL], [ + AC_CHECK_HEADERS([sys/syscall.h unistd.h]) + AC_CHECK_FUNCS([syscall __syscall]) + if test "x$ac_cv_func___syscall" = "xyes"; then + AC_CACHE_CHECK([for __syscall needing definition], [box_cv_have___syscall_need_defn], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #ifdef HAVE_SYS_SYSCALL_H + #include <sys/syscall.h> + #endif + ]], [[ + __syscall(SYS_exit, 0); + return 1; + ]])], + [box_cv_have___syscall_need_defn=no], [box_cv_have___syscall_need_defn=yes] + )]) + if test "x$box_cv_have___syscall_need_defn" = "xyes"; then + AC_DEFINE([HAVE___SYSCALL_NEED_DEFN], 1, + [Define to 1 if __syscall is available but needs a definition]) + fi + fi + ])dnl diff --git a/infrastructure/m4/ax_path_bdb.m4 b/infrastructure/m4/ax_path_bdb.m4 new file mode 100644 index 00000000..1a771048 --- /dev/null +++ b/infrastructure/m4/ax_path_bdb.m4 @@ -0,0 +1,615 @@ +dnl @synopsis AX_PATH_BDB([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl +dnl This macro finds the latest version of Berkeley DB on the system, +dnl and ensures that the header file and library versions match. If +dnl MINIMUM-VERSION is specified, it will ensure that the library found +dnl is at least that version. +dnl +dnl It determines the name of the library as well as the path to the +dnl header file and library. It will check both the default environment +dnl as well as the default Berkeley DB install location. When found, it +dnl sets BDB_LIBS, BDB_CPPFLAGS, and BDB_LDFLAGS to the necessary +dnl values to add to LIBS, CPPFLAGS, and LDFLAGS, as well as setting +dnl BDB_VERSION to the version found. HAVE_DB_H is defined also. +dnl +dnl The options --with-bdb-headers=DIR and --with-bdb-lib=DIR can be +dnl used to specify a specific Berkeley DB installation to use. +dnl +dnl An example of its use is: +dnl +dnl AX_PATH_BDB([3],[ +dnl LIBS="$BDB_LIBS $LIBS" +dnl LDFLAGS="$BDB_LDFLAGS $LDFLAGS" +dnl CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" +dnl ]) +dnl +dnl which will locate the latest version of Berkeley DB on the system, +dnl and ensure that it is version 3.0 or higher. +dnl +dnl Details: This macro does not use either AC_CHECK_HEADERS or +dnl AC_CHECK_LIB because, first, the functions inside the library are +dnl sometimes renamed to contain a version code that is only available +dnl from the db.h on the system, and second, because it is common to +dnl have multiple db.h and libdb files on a system it is important to +dnl make sure the ones being used correspond to the same version. +dnl Additionally, there are many different possible names for libdb +dnl when installed by an OS distribution, and these need to be checked +dnl if db.h does not correspond to libdb. +dnl +dnl When cross compiling, only header versions are verified since it +dnl would be difficult to check the library version. Additionally the +dnl default Berkeley DB installation locations /usr/local/BerkeleyDB* +dnl are not searched for higher versions of the library. +dnl +dnl The format for the list of library names to search came from the +dnl Cyrus IMAP distribution, although they are generated dynamically +dnl here, and only for the version found in db.h. +dnl +dnl The macro AX_COMPARE_VERSION is required to use this macro, and +dnl should be available from the Autoconf Macro Archive. +dnl +dnl The author would like to acknowledge the generous and valuable +dnl feedback from Guido Draheim, without which this macro would be far +dnl less robust, and have poor and inconsistent cross compilation +dnl support. +dnl +dnl Changes: +dnl +dnl 1/5/05 applied patch from Rafa Rzepecki to eliminate compiler +dnl warning about unused variable, argv +dnl 1/7/07 Add --with-bdb-headers and --with-bdb-lib options +dnl (James O'Gorman, james@netinertia.co.uk) +dnl +dnl @category InstalledPackages +dnl @author Tim Toolan <toolan@ele.uri.edu> +dnl @version 2005-01-17 +dnl @license GPLWithACException + +dnl ######################################################################### +AC_DEFUN([AX_PATH_BDB], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_ok=no + + # Add --with-bdb-headers and --with-bdb-lib options + AC_ARG_WITH([bdb-headers], + [AC_HELP_STRING([--with-bdb-headers=DIR], + [Berkeley DB include files location])]) + + AC_ARG_WITH([bdb-lib], + [AC_HELP_STRING([--with-bdb-lib=DIR], + [Berkeley DB library location])]) + + # Check if --with-bdb-dir was specified. + if test "x$with_bdb_headers" = "x" -a "x$with_bdb_lib" = "x"; then + # No option specified, so just search the system. + AX_PATH_BDB_NO_OPTIONS([$1], [HIGHEST], [ + ax_path_bdb_ok=yes + ]) + else + ax_path_bdb_INC="$with_bdb_headers" + ax_path_bdb_LIB="$with_bdb_lib" + + dnl # Save previous environment, and modify with new stuff. + ax_path_bdb_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-I$ax_path_bdb_INC $CPPFLAGS" + + ax_path_bdb_save_LDFLAGS=$LDFLAGS + LDFLAGS="-L$ax_path_bdb_LIB $LDFLAGS" + + # Check for specific header file db.h + AC_MSG_CHECKING([db.h presence in $ax_path_bdb_INC]) + if test -f "$ax_path_bdb_INC/db.h" ; then + AC_MSG_RESULT([yes]) + # Check for library + AX_PATH_BDB_NO_OPTIONS([$1], [ENVONLY], [ + ax_path_bdb_ok=yes + BDB_CPPFLAGS="-I$ax_path_bdb_INC" + BDB_LDFLAGS="-L$ax_path_bdb_LIB" + ]) + else + AC_MSG_RESULT([no]) + AC_MSG_NOTICE([no usable Berkeley DB not found]) + fi + + dnl # Restore the environment. + CPPFLAGS="$ax_path_bdb_save_CPPFLAGS" + LDFLAGS="$ax_path_bdb_save_LDFLAGS" + + fi + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_ok" = "yes" ; then + m4_ifvaln([$2],[$2],[:])dnl + m4_ifvaln([$3],[else $3])dnl + fi + +]) dnl AX_PATH_BDB + +dnl ######################################################################### +dnl Check for berkeley DB of at least MINIMUM-VERSION on system. +dnl +dnl The OPTION argument determines how the checks occur, and can be one of: +dnl +dnl HIGHEST - Check both the environment and the default installation +dnl directories for Berkeley DB and choose the version that +dnl is highest. (default) +dnl ENVFIRST - Check the environment first, and if no satisfactory +dnl library is found there check the default installation +dnl directories for Berkeley DB which is /usr/local/BerkeleyDB* +dnl ENVONLY - Check the current environment only. +dnl +dnl Requires AX_PATH_BDB_PATH_GET_VERSION, AX_PATH_BDB_PATH_FIND_HIGHEST, +dnl AX_PATH_BDB_ENV_CONFIRM_LIB, AX_PATH_BDB_ENV_GET_VERSION, and +dnl AX_COMPARE_VERSION macros. +dnl +dnl Result: sets ax_path_bdb_no_options_ok to yes or no +dnl sets BDB_LIBS, BDB_CPPFLAGS, BDB_LDFLAGS, BDB_VERSION +dnl +dnl AX_PATH_BDB_NO_OPTIONS([MINIMUM-VERSION], [OPTION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_NO_OPTIONS], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_no_options_ok=no + + # Values to add to environment to use Berkeley DB. + BDB_VERSION='' + BDB_LIBS='' + BDB_CPPFLAGS='' + BDB_LDFLAGS='' + + # Check cross compilation here. + if test "x$cross_compiling" = "xyes" ; then + # If cross compiling, can't use AC_RUN_IFELSE so do these tests. + # The AC_PREPROC_IFELSE confirms that db.h is preprocessable, + # and extracts the version number from it. + AC_MSG_CHECKING([for db.h]) + + AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_no_options_HEADER_VERSION])dnl + HEADER_VERSION='' + AC_PREPROC_IFELSE([ + AC_LANG_SOURCE([[ +#include <db.h> +#ifdef DB_VERSION_MAJOR +AX_PATH_BDB_STUFF DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH +#else +AX_PATH_BDB_STUFF 1,0,0 +#endif + ]]) + ],[ + # Extract version from preprocessor output. + HEADER_VERSION=`eval "$ac_cpp conftest.$ac_ext" 2> /dev/null \ + | grep AX_PATH_BDB_STUFF | sed 's/[[^0-9,]]//g;s/,/./g;1q'` + ],[]) + + if test "x$HEADER_VERSION" = "x" ; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([$HEADER_VERSION]) + + # Check that version is high enough. + AX_COMPARE_VERSION([$HEADER_VERSION],[ge],[$1],[ + # get major and minor version numbers + AS_VAR_PUSHDEF([MAJ],[ax_path_bdb_no_options_MAJOR])dnl + MAJ=`echo $HEADER_VERSION | sed 's,\..*,,'` + AS_VAR_PUSHDEF([MIN],[ax_path_bdb_no_options_MINOR])dnl + MIN=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` + + dnl # Save LIBS. + ax_path_bdb_no_options_save_LIBS="$LIBS" + + # Check that we can link with the library. + AC_SEARCH_LIBS([db_version], + [db db-$MAJ.$MIN db$MAJ.$MIN db$MAJ$MIN db-$MAJ db$MAJ],[ + # Sucessfully found library. + ax_path_bdb_no_options_ok=yes + BDB_VERSION=$HEADER_VERSION + + # Extract library from LIBS + ax_path_bdb_no_options_LEN=` \ + echo "x$ax_path_bdb_no_options_save_LIBS" \ + | awk '{print(length)}'` + BDB_LIBS=`echo "x$LIBS " \ + | sed "s/.\{$ax_path_bdb_no_options_LEN\}\$//;s/^x//;s/ //g"` + ],[]) + + dnl # Restore LIBS + LIBS="$ax_path_bdb_no_options_save_LIBS" + + AS_VAR_POPDEF([MAJ])dnl + AS_VAR_POPDEF([MIN])dnl + ]) + fi + + AS_VAR_POPDEF([HEADER_VERSION])dnl + else + # Not cross compiling. + # Check version of Berkeley DB in the current environment. + AX_PATH_BDB_ENV_GET_VERSION([ + AX_COMPARE_VERSION([$ax_path_bdb_env_get_version_VERSION],[ge],[$1],[ + # Found acceptable version in current environment. + ax_path_bdb_no_options_ok=yes + BDB_VERSION="$ax_path_bdb_env_get_version_VERSION" + BDB_LIBS="$ax_path_bdb_env_get_version_LIBS" + ]) + ]) + + # Determine if we need to search /usr/local/BerkeleyDB* + ax_path_bdb_no_options_DONE=no + if test "x$2" = "xENVONLY" ; then + ax_path_bdb_no_options_DONE=yes + elif test "x$2" = "xENVFIRST" ; then + ax_path_bdb_no_options_DONE=$ax_path_bdb_no_options_ok + fi + + if test "$ax_path_bdb_no_options_DONE" = "no" ; then + ax_compare_version=false + # Check for highest in /usr/local/BerkeleyDB* + AX_PATH_BDB_PATH_FIND_HIGHEST([ + if test "$ax_path_bdb_no_options_ok" = "yes" ; then + # If we already have an acceptable version use this if higher. + AX_COMPARE_VERSION( + [$ax_path_bdb_path_find_highest_VERSION],[gt],[$BDB_VERSION]) + else + # Since we didn't have an acceptable version check if this one is. + AX_COMPARE_VERSION( + [$ax_path_bdb_path_find_highest_VERSION],[ge],[$1]) + fi + ]) + + dnl # If result from _AX_COMPARE_VERSION is true we want this version. + if test "$ax_compare_version" = "true" ; then + ax_path_bdb_no_options_ok=yes + BDB_LIBS="-ldb" + if test "x$ax_path_bdb_path_find_highest_DIR" != x ; then + BDB_CPPFLAGS="-I$ax_path_bdb_path_find_highest_DIR/include" + BDB_LDFLAGS="-L$ax_path_bdb_path_find_highest_DIR/lib" + fi + BDB_VERSION="$ax_path_bdb_path_find_highest_VERSION" + fi + fi + fi + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_no_options_ok" = "yes" ; then + AC_MSG_NOTICE([using Berkeley DB version $BDB_VERSION]) + AC_DEFINE([HAVE_DB_H],[1], + [Define to 1 if you have the <db.h> header file.]) + m4_ifvaln([$3],[$3])dnl + else + AC_MSG_NOTICE([no Berkeley DB version $1 or higher found]) + m4_ifvaln([$4],[$4])dnl + fi +]) dnl AX_PATH_BDB_NO_OPTIONS + +dnl ######################################################################### +dnl Check the default installation directory for Berkeley DB which is +dnl of the form /usr/local/BerkeleyDB* for the highest version. +dnl +dnl Result: sets ax_path_bdb_path_find_highest_ok to yes or no, +dnl sets ax_path_bdb_path_find_highest_VERSION to version, +dnl sets ax_path_bdb_path_find_highest_DIR to directory. +dnl +dnl AX_PATH_BDB_PATH_FIND_HIGHEST([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_PATH_FIND_HIGHEST], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_path_find_highest_ok=no + + AS_VAR_PUSHDEF([VERSION],[ax_path_bdb_path_find_highest_VERSION])dnl + VERSION='' + + ax_path_bdb_path_find_highest_DIR='' + + # find highest verison in default install directory for Berkeley DB + AS_VAR_PUSHDEF([CURDIR],[ax_path_bdb_path_find_highest_CURDIR])dnl + AS_VAR_PUSHDEF([CUR_VERSION],[ax_path_bdb_path_get_version_VERSION])dnl + + for CURDIR in `ls -d /usr/local/BerkeleyDB* 2> /dev/null` + do + AX_PATH_BDB_PATH_GET_VERSION([$CURDIR],[ + AX_COMPARE_VERSION([$CUR_VERSION],[gt],[$VERSION],[ + ax_path_bdb_path_find_highest_ok=yes + ax_path_bdb_path_find_highest_DIR="$CURDIR" + VERSION="$CUR_VERSION" + ]) + ]) + done + + AS_VAR_POPDEF([VERSION])dnl + AS_VAR_POPDEF([CUR_VERSION])dnl + AS_VAR_POPDEF([CURDIR])dnl + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_path_find_highest_ok" = "yes" ; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + +]) dnl AX_PATH_BDB_PATH_FIND_HIGHEST + +dnl ######################################################################### +dnl Checks for Berkeley DB in specified directory's lib and include +dnl subdirectories. +dnl +dnl Result: sets ax_path_bdb_path_get_version_ok to yes or no, +dnl sets ax_path_bdb_path_get_version_VERSION to version. +dnl +dnl AX_PATH_BDB_PATH_GET_VERSION(BDB-DIR, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_PATH_GET_VERSION], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_path_get_version_ok=no + + # Indicate status of checking for Berkeley DB header. + AC_MSG_CHECKING([in $1/include for db.h]) + ax_path_bdb_path_get_version_got_header=no + test -f "$1/include/db.h" && ax_path_bdb_path_get_version_got_header=yes + AC_MSG_RESULT([$ax_path_bdb_path_get_version_got_header]) + + # Indicate status of checking for Berkeley DB library. + AC_MSG_CHECKING([in $1/lib for library -ldb]) + + ax_path_bdb_path_get_version_VERSION='' + + if test -d "$1/include" && test -d "$1/lib" && + test "$ax_path_bdb_path_get_version_got_header" = "yes" ; then + dnl # save and modify environment + ax_path_bdb_path_get_version_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-I$1/include $CPPFLAGS" + + ax_path_bdb_path_get_version_save_LIBS="$LIBS" + LIBS="$LIBS -ldb" + + ax_path_bdb_path_get_version_save_LDFLAGS="$LDFLAGS" + LDFLAGS="-L$1/lib $LDFLAGS" + + # Compile and run a program that compares the version defined in + # the header file with a version defined in the library function + # db_version. + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; +#ifdef DB_VERSION_MAJOR + int major,minor,patch; + db_version(&major,&minor,&patch); + if (argc > 1) + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); + if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && + DB_VERSION_PATCH == patch) + return 0; + else + return 1; +#else + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + if (argc > 1) + printf("1.0.0\n"); + if (dbp) + return 0; + else + return 1; +#endif +} + ]]) + ],[ + # Program compiled and ran, so get version by adding argument. + ax_path_bdb_path_get_version_VERSION=`./conftest$ac_exeext x` + ax_path_bdb_path_get_version_ok=yes + ],[],[]) + + dnl # restore environment + CPPFLAGS="$ax_path_bdb_path_get_version_save_CPPFLAGS" + LIBS="$ax_path_bdb_path_get_version_save_LIBS" + LDFLAGS="$ax_path_bdb_path_get_version_save_LDFLAGS" + fi + + dnl # Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_path_get_version_ok" = "yes" ; then + AC_MSG_RESULT([$ax_path_bdb_path_get_version_VERSION]) + m4_ifvaln([$2],[$2])dnl + else + AC_MSG_RESULT([no]) + m4_ifvaln([$3],[$3])dnl + fi +]) dnl AX_PATH_BDB_PATH_GET_VERSION + +############################################################################# +dnl Checks if version of library and header match specified version. +dnl Only meant to be used by AX_PATH_BDB_ENV_GET_VERSION macro. +dnl +dnl Requires AX_COMPARE_VERSION macro. +dnl +dnl Result: sets ax_path_bdb_env_confirm_lib_ok to yes or no. +dnl +dnl AX_PATH_BDB_ENV_CONFIRM_LIB(VERSION, [LIBNAME]) +AC_DEFUN([AX_PATH_BDB_ENV_CONFIRM_LIB], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_env_confirm_lib_ok=no + + dnl # save and modify environment to link with library LIBNAME + ax_path_bdb_env_confirm_lib_save_LIBS="$LIBS" + LIBS="$LIBS $2" + + # Compile and run a program that compares the version defined in + # the header file with a version defined in the library function + # db_version. + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; +#ifdef DB_VERSION_MAJOR + int major,minor,patch; + db_version(&major,&minor,&patch); + if (argc > 1) + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); + if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && + DB_VERSION_PATCH == patch) + return 0; + else + return 1; +#else + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + if (argc > 1) + printf("1.0.0\n"); + if (dbp) + return 0; + else + return 1; +#endif +} + ]]) + ],[ + # Program compiled and ran, so get version by giving an argument, + # which will tell the program to print the output. + ax_path_bdb_env_confirm_lib_VERSION=`./conftest$ac_exeext x` + + # If the versions all match up, indicate success. + AX_COMPARE_VERSION([$ax_path_bdb_env_confirm_lib_VERSION],[eq],[$1],[ + ax_path_bdb_env_confirm_lib_ok=yes + ]) + ],[],[]) + + dnl # restore environment + LIBS="$ax_path_bdb_env_confirm_lib_save_LIBS" + +]) dnl AX_PATH_BDB_ENV_CONFIRM_LIB + +############################################################################# +dnl Finds the version and library name for Berkeley DB in the +dnl current environment. Tries many different names for library. +dnl +dnl Requires AX_PATH_BDB_ENV_CONFIRM_LIB macro. +dnl +dnl Result: set ax_path_bdb_env_get_version_ok to yes or no, +dnl set ax_path_bdb_env_get_version_VERSION to the version found, +dnl and ax_path_bdb_env_get_version_LIBNAME to the library name. +dnl +dnl AX_PATH_BDB_ENV_GET_VERSION([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_ENV_GET_VERSION], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_env_get_version_ok=no + + ax_path_bdb_env_get_version_VERSION='' + ax_path_bdb_env_get_version_LIBS='' + + AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_env_get_version_HEADER_VERSION])dnl + AS_VAR_PUSHDEF([TEST_LIBNAME],[ax_path_bdb_env_get_version_TEST_LIBNAME])dnl + + # Indicate status of checking for Berkeley DB library. + AC_MSG_CHECKING([for db.h]) + + # Compile and run a program that determines the Berkeley DB version + # in the header file db.h. + HEADER_VERSION='' + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; + if (argc > 1) +#ifdef DB_VERSION_MAJOR + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); +#else + printf("1.0.0\n"); +#endif + return 0; +} + ]]) + ],[ + # Program compiled and ran, so get version by adding an argument. + HEADER_VERSION=`./conftest$ac_exeext x` + AC_MSG_RESULT([$HEADER_VERSION]) + ],[AC_MSG_RESULT([no])],[AC_MSG_RESULT([no])]) + + # Have header version, so try to find corresponding library. + # Looks for library names in the order: + # nothing, db, db-X.Y, dbX.Y, dbXY, db-X, dbX + # and stops when it finds the first one that matches the version + # of the header file. + if test "x$HEADER_VERSION" != "x" ; then + AC_MSG_CHECKING([for library containing Berkeley DB $HEADER_VERSION]) + + AS_VAR_PUSHDEF([MAJOR],[ax_path_bdb_env_get_version_MAJOR])dnl + AS_VAR_PUSHDEF([MINOR],[ax_path_bdb_env_get_version_MINOR])dnl + + # get major and minor version numbers + MAJOR=`echo $HEADER_VERSION | sed 's,\..*,,'` + MINOR=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` + + # see if it is already specified in LIBS + TEST_LIBNAME='' + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db" + TEST_LIBNAME='-ldb' + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db-X.Y" + TEST_LIBNAME="-ldb-${MAJOR}.$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbX.Y" + TEST_LIBNAME="-ldb${MAJOR}.$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbXY" + TEST_LIBNAME="-ldb$MAJOR$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db-X" + TEST_LIBNAME="-ldb-$MAJOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbX" + TEST_LIBNAME="-ldb$MAJOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + dnl # Found a valid library. + if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then + if test "x$TEST_LIBNAME" = "x" ; then + AC_MSG_RESULT([none required]) + else + AC_MSG_RESULT([$TEST_LIBNAME]) + fi + ax_path_bdb_env_get_version_VERSION="$HEADER_VERSION" + ax_path_bdb_env_get_version_LIBS="$TEST_LIBNAME" + ax_path_bdb_env_get_version_ok=yes + else + AC_MSG_RESULT([no]) + fi + + AS_VAR_POPDEF([MAJOR])dnl + AS_VAR_POPDEF([MINOR])dnl + fi + + AS_VAR_POPDEF([HEADER_VERSION])dnl + AS_VAR_POPDEF([TEST_LIBNAME])dnl + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + +]) dnl BDB_ENV_GET_VERSION + +############################################################################# diff --git a/infrastructure/m4/ax_random_device.m4 b/infrastructure/m4/ax_random_device.m4 new file mode 100644 index 00000000..ab9b56fd --- /dev/null +++ b/infrastructure/m4/ax_random_device.m4 @@ -0,0 +1,31 @@ +dnl @synopsis AX_RANDOM_DEVICE +dnl +dnl This macro will check for a random device, allowing the user to explicitly +dnl set the path. The user uses '--with-random=FILE' as an argument to +dnl configure. +dnl +dnl If A random device is found then HAVE_RANDOM_DEVICE is set to 1 and +dnl RANDOM_DEVICE contains the path. +dnl +dnl @category Miscellaneous +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_RANDOM_DEVICE], [ + AC_ARG_WITH([random], + [AC_HELP_STRING([--with-random=FILE], [Use FILE as random number seed [auto-detected]])], + [RANDOM_DEVICE="$withval"], + [AC_CHECK_FILE("/dev/urandom", [RANDOM_DEVICE="/dev/urandom";], + [AC_CHECK_FILE("/dev/arandom", [RANDOM_DEVICE="/dev/arandom";], + [AC_CHECK_FILE("/dev/random", [RANDOM_DEVICE="/dev/random";])] + )]) + ]) + if test "x$RANDOM_DEVICE" != "x" ; then + AC_DEFINE([HAVE_RANDOM_DEVICE], 1, + [Define to 1 (and set RANDOM_DEVICE) if a random device is available]) + AC_SUBST([RANDOM_DEVICE]) + AC_DEFINE_UNQUOTED([RANDOM_DEVICE], ["$RANDOM_DEVICE"], + [Define to the filename of the random device (and set HAVE_RANDOM_DEVICE)]) + fi + ])dnl diff --git a/infrastructure/m4/ax_split_version.m4 b/infrastructure/m4/ax_split_version.m4 new file mode 100644 index 00000000..20b353df --- /dev/null +++ b/infrastructure/m4/ax_split_version.m4 @@ -0,0 +1,19 @@ +dnl @synopsis AX_SPLIT_VERSION(DEFINE, VERSION) +dnl +dnl Splits a version number in the format MAJOR.MINOR.POINT into it's +dnl separate components and AC_DEFINES <DEFINE>_MAJOR etc with the values. +dnl +dnl @category Automake +dnl @author Martin Ebourne <martin@zepler.org> +dnl @version +dnl @license AllPermissive + +AC_DEFUN([AX_SPLIT_VERSION],[ + ax_major_version=`echo "$2" | sed 's/\([[^.]][[^.]]*\).*/\1/'` + ax_minor_version=`echo "$2" | sed 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'` + ax_point_version=`echo "$2" | sed 's/[[^.]][[^.]]*.[[^.]][[^.]]*.\(.*\)/\1/'` + + AC_DEFINE_UNQUOTED([$1_MAJOR], [$ax_major_version], [Define to major version for $1]) + AC_DEFINE_UNQUOTED([$1_MINOR], [$ax_minor_version], [Define to minor version for $1]) + AC_DEFINE_UNQUOTED([$1_POINT], [$ax_point_version], [Define to point version for $1]) +]) diff --git a/infrastructure/m4/vl_lib_readline.m4 b/infrastructure/m4/vl_lib_readline.m4 new file mode 100644 index 00000000..a0571bfa --- /dev/null +++ b/infrastructure/m4/vl_lib_readline.m4 @@ -0,0 +1,135 @@ +dnl @synopsis VL_LIB_READLINE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl Searches for a readline compatible library. If found, defines +dnl `HAVE_LIBREADLINE'. If the found library has the `add_history' +dnl function, sets also `HAVE_READLINE_HISTORY'. Also checks for the +dnl locations of the necessary include files and sets `HAVE_READLINE_H' +dnl or `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +dnl 'HAVE_HISTORY_H' if the corresponding include files exists. +dnl +dnl The libraries that may be readline compatible are `libedit', +dnl `libeditline' and `libreadline'. Sometimes we need to link a +dnl termcap library for readline to work, this macro tests these cases +dnl too by trying to link with `libtermcap', `libcurses' or +dnl `libncurses' before giving up. +dnl +dnl Here is an example of how to use the information provided by this +dnl macro to perform the necessary includes or declarations in a C +dnl file: +dnl +dnl #ifdef HAVE_LIBREADLINE +dnl # if defined(HAVE_READLINE_READLINE_H) +dnl # include <readline/readline.h> +dnl # elif defined(HAVE_READLINE_H) +dnl # include <readline.h> +dnl # else /* !defined(HAVE_READLINE_H) */ +dnl extern char *readline (); +dnl # endif /* !defined(HAVE_READLINE_H) */ +dnl char *cmdline = NULL; +dnl #else /* !defined(HAVE_READLINE_READLINE_H) */ +dnl /* no readline */ +dnl #endif /* HAVE_LIBREADLINE */ +dnl +dnl #ifdef HAVE_READLINE_HISTORY +dnl # if defined(HAVE_READLINE_HISTORY_H) +dnl # include <readline/history.h> +dnl # elif defined(HAVE_HISTORY_H) +dnl # include <history.h> +dnl # else /* !defined(HAVE_HISTORY_H) */ +dnl extern void add_history (); +dnl extern int write_history (); +dnl extern int read_history (); +dnl # endif /* defined(HAVE_READLINE_HISTORY_H) */ +dnl /* no history */ +dnl #endif /* HAVE_READLINE_HISTORY */ +dnl +dnl Modifications to add --enable-gnu-readline to work around licensing +dnl problems between the traditional BSD licence and the GPL. +dnl Martin Ebourne, 2005/7/11 +dnl Rewrite to match headers with libraries and be more selective. +dnl Martin Ebourne, 2006/1/4 +dnl +dnl @category InstalledPackages +dnl @author Ville Laurikari <vl@iki.fi> +dnl @version 2002-04-04 +dnl @license AllPermissive + +AC_DEFUN([VL_LIB_READLINE], [ + AC_ARG_ENABLE( + [gnu-readline], + AC_HELP_STRING([--enable-gnu-readline], + [Use GNU readline if present (may violate GNU licence)]) + ) + vl_cv_lib_readline_compat_found=no + if test "x$enable_gnu_readline" = "xyes"; then + VL_LIB_READLINE_CHECK([readline], + [readline], + [readline/readline.h readline.h], + [readline/history.h history.h]) + fi + if test "x$vl_cv_lib_readline_compat_found" = "xno"; then + VL_LIB_READLINE_CHECK([editline], + [edit editline], + [editline/readline.h], + [editline/readline.h]) + fi + if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi +]) + +dnl VL_LIB_READLINE_CHECK(name, libraries, headers, history headers) +AC_DEFUN([VL_LIB_READLINE_CHECK], [ + AC_CACHE_CHECK([for $1 library], + [vl_cv_lib_$1], [ + ORIG_LIBS="$LIBS" + vl_cv_lib_$1="" + for readline_lib in $2; do + for termcap_lib in "" termcap curses ncurses; do + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + AC_TRY_LINK_FUNC([readline], [vl_cv_lib_$1="$TRY_LIB"]) + if test -n "$vl_cv_lib_$1"; then + break + fi + done + if test -n "$vl_cv_lib_$1"; then + break + fi + done + if test -z "$vl_cv_lib_$1"; then + vl_cv_lib_$1=no + LIBS="$ORIG_LIBS" + fi + ]) + + 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 + AC_DEFINE([HAVE_LIBREADLINE], 1, + [Define if you have a readline compatible library]) + + AC_CACHE_CHECK([whether $1 supports history], + [vl_cv_lib_$1_history], [ + vl_cv_lib_$1_history=no + AC_TRY_LINK_FUNC([add_history], [vl_cv_lib_$1_history=yes]) + ]) + if test "x$vl_cv_lib_$1_history" = "xyes"; then + vl_cv_lib_$1_history=no + AC_CHECK_HEADERS( + [$4], + [AC_DEFINE([HAVE_READLINE_HISTORY], [1], + [Define if your readline library has add_history])]) + fi + else + LIBS="$ORIG_LIBS" + fi +])dnl diff --git a/infrastructure/makebuildenv.pl.in b/infrastructure/makebuildenv.pl.in new file mode 100755 index 00000000..33b0b635 --- /dev/null +++ b/infrastructure/makebuildenv.pl.in @@ -0,0 +1,973 @@ +#!@PERL@ +use strict; +use Symbol; + +my @modules; +my %module_dependency; +my %module_library_link_opts; +my %header_dependency; + +$|=1; + + +# note: Mac OS X resource forks and .DS_Store files are explicity ignored + +print "Box build environment setup.\n"; + +my @implicit_deps = ('lib/common'); + +# work out platform variables +use lib 'infrastructure'; +use BoxPlatform; + +print "Building on '$build_os'.\n\n"; + +# keep copy of command line args +my $makebuildenv_args = join(' ',@ARGV); + +# do command line arguments +my $compile_line_extra = $platform_compile_line_extra; +my $link_line_extra = $platform_link_line_extra; + +# make sure local files directory exists +unless(-d 'local') +{ + mkdir 'local',0755; +} + + +# flags about the environment +my %env_flags; + +$module_dependency{"lib/common"} = ["lib/win32"]; +push @implicit_deps, "lib/win32"; + +# print "Flag: $_\n" for(keys %env_flags); + +# seed autogen code +print "Seeding autogen code...\n"; +open FINDAUTOGEN,"find . -follow -name Makefile.extra |" or die "Can't use find for locating files"; +while(<FINDAUTOGEN>) +{ + chomp; + my $file = $_; + $file =~ m~\A(.+)/[^/]+\Z~; + my $dir = $1; + open FL,$file or die "Can't open $_ for reading"; + my %vars; + $vars{_PERL} = "@PERL@"; + my $do_cmds = 0; + while(<FL>) + { + chomp; + if(m/\A(.+)\s+=\s+(.+)\Z/) + { + # is a variable + $vars{$1} = $2; + next; + } + next unless m/\S/; + if(m/AUTOGEN SEEDING/) + { + $do_cmds = 1; + } + elsif(m/\A\S/) + { + $do_cmds = 0 if $do_cmds == 2; + } + else + { + # command, run it? + if($do_cmds) + { + $do_cmds = 2; # flag something has been done + + # subsitute variables, repeatedly + my $c = $_; + $c =~ s/\A\s+//; + while(1) + { + my $did_subst = 0; + + for my $k (keys %vars) + { + $did_subst = 1 if $c =~ s/\$\($k\)/$vars{$k}/g; + } + + last unless $did_subst; + } + + # run command + unless (0 == system("(cd $dir; $c)")) + { + die "Couldn't run command $c " . + "(in $dir) for $file"; + } + } + } + } + close FL; +} +close FINDAUTOGEN; +print "done\n\n"; + + +# open test mail program template file +my $test_template_file = 'infrastructure/buildenv-testmain-template.cpp'; +open FL,$test_template_file or die "Can't open test template file\n"; +my $test_template; +read FL,$test_template,-s $test_template_file; +close FL; + + +# extra platform defines +my $extra_platform_defines = ''; + +# read in module definitions file, and any files it includes +my @modules_files; +sub read_modules_file +{ + my ($mf) = @_; + my $f = gensym; + open $f,$mf or die "Can't open modules file '$mf'\n"; + while(<$f>) + { + if(m/\AINCLUDE\s+(\S+)\Z/) + { + # include another file + read_modules_file($1) + } + else + { + push @modules_files,$_ + } + } + close $f; +} +read_modules_file('modules.txt'); + +# prepare directories... +mkdir "release",0755; +mkdir "debug",0755; + +# is the library code in another directory? +my $external_lib = readlink('lib'); +if($external_lib ne '') +{ + # adjust to root of the library distribution + $external_lib =~ s!/lib\Z!!; + $external_lib = '../'.$external_lib; + # make symlinks + make_obj_symlink('debug'); + make_obj_symlink('release'); +} +sub make_obj_symlink +{ + my $m = $_[0]; + my $target = $external_lib."/$m/lib/"; + my $link = "$m/lib"; + # check link + if(-e $link) + { + if(-l $link) + { + if(readlink($link) ne $target) + { + print "Warning: replacing $link with new link to $target\n"; + unlink $link; + } + } + else + { + die "$link already exists, but it isn't a symbolic link" + } + } + if(!-e $link) + { + symlink $target,$link or die "Can't make $m/lib symlink"; + } +} + +print "Scanning code...\n"; + +my $modules_omitted = 0; +my $modules_omitting = 0; + +# process lines in flattened modules files +for(@modules_files) +{ + # clean up line + chomp; s/\A\s+//; s/#.*\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + # omit bits on some platforms? + if(m/\AEND-OMIT/) + { + $modules_omitting = 0; + next; + } + + next if $modules_omitting; + + if(m/\AOMIT:(.+)/) + { + if($1 eq $build_os or $1 eq $target_os) + { + $modules_omitted = 1; + $modules_omitting = 1; + } + next; + } + + # split up... + my ($mod, @deps_i) = split / /; + + # ignore this module? + next if ignore_module($mod); + + # deps for this platform + my @deps; + for(@deps_i) + { + my ($dep,$exclude_from) = split /!/; + # generic library translation + $dep = $env_flags{'LIBTRANS_'.$dep} if exists($env_flags{'LIBTRANS_'.$dep}); + next if $dep eq ''; + if($exclude_from =~ m/\A\+(.+)\Z/) + { + $exclude_from = $1; + my $inc = 0; + for(split /,/,$exclude_from) + { + $inc = 1 if $_ eq $build_os + } + push @deps,$dep if $inc + } + else + { + my $inc = 1; + for(split /,/,$exclude_from) + { + $inc = 0 if $_ eq $build_os + } + push @deps,$dep if $inc + } + } + + # check directory exists + die "Module $mod can't be found\n" unless -d $mod; + + # and put in lists + push @modules,$mod; + my @md; # module dependencies + my @lo; # link line options + for(@deps) + { + if(/\A-l/) + { + push @lo,$_ + } + else + { + push @md,$_ unless ignore_module($_) + } + } + $module_dependency{$mod} = [@implicit_deps,@md]; + $module_library_link_opts{$mod} = [@lo]; + + # make directories, but not if we're using an external library and this a library module + my ($s,$d) = split /\//,$mod; + if($s ne 'lib' || $external_lib eq '') + { + mkdir "release/$s",0755; + mkdir "release/$s/$d",0755; + mkdir "debug/$s",0755; + mkdir "debug/$s/$d",0755; + } +} + +# make dirs for implicit dep +foreach my $dep (@implicit_deps) +{ + mkdir "release/$dep",0755; + mkdir "debug/$dep",0755; +} + +# write a list of all the modules we've configured to use +open CONFIGURED_MODS,'>local/modules.h' or die "Can't write configured modules list"; +print CONFIGURED_MODS <<__E; +// automatically generated file, do not edit +#ifndef _CONFIGURED_MODULES__H +#define _CONFIGURED_MODULES__H +__E +for(@implicit_deps,@modules) +{ + my $m = $_; + $m =~ s~/~_~; + print CONFIGURED_MODS "#define MODULE_$m\n"; +} +print CONFIGURED_MODS <<__E; +#endif // _CONFIGURED_MODULES__H +__E +close CONFIGURED_MODS; + + +# now make a list of all the .h files we can find, recording which module they're in +my %hfiles; +for my $mod (@modules, @implicit_deps) +{ + opendir DIR,$mod; + my @items = readdir DIR; + closedir DIR; + + # add in items from autogen directories, and create output directories + { + my @autogen_items; + + for my $di (@items) + { + if($di =~ m/\Aautogen/ && -d "$mod/$di") + { + # Read items + my $d = "$mod/$di"; + opendir DIR,$d; + my @i = readdir DIR; + closedir DIR; + for(@i) + { + next if m/\A\./; + push @autogen_items,"$di/$_" + } + } + } + @items = (@items, @autogen_items); + } + + for(grep /\.h\Z/i, @items) + { + next if /\A\._/; # Temp Mac OS Resource hack + die "Header file $_ already used in module ".$hfiles{$_}."\n" if exists $hfiles{$_}; + $hfiles{$_} = $mod + } +} + +for my $mod (@modules, @implicit_deps) +{ + opendir DIR,$mod; + for my $h (grep /\.h\Z/i, readdir DIR) + { + next if $h =~ /\A\./; # Ignore Mac resource forks, autosaves, etc + + open FL,"$mod/$h" or die "can't open $mod/$h"; + my $f; + read FL,$f,-s "$mod/$h"; + close FL; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + my $i = $1; + # ignore autogen exceptions + next if $i =~ m/\Aautogen_.+?Exception.h\Z/; + # record dependency + ${$header_dependency{$h}}{$i} = 1 if exists $hfiles{$i}; + } + } + closedir DIR; +} + +print "done\n\nGenerating Makefiles...\n"; + +my %module_resources_win32; + +# Then write a makefile for each module +for my $mod (@implicit_deps, @modules) +{ + print $mod,"\n"; + + my ($type,$name) = split /\//,$mod; + + # add additional files for tests + if($type eq 'test') + { + my $testmain = $test_template; + $testmain =~ s/TEST_NAME/$name/g; + open TESTMAIN,">$mod/_main.cpp" or die "Can't open test main file for $mod for writing\n"; + print TESTMAIN $testmain; + close TESTMAIN; + + # test file... + sub writetestfile + { + my ($filename,$runcmd,$module) = @_; + + open TESTFILE,">$filename" or die "Can't open " . + "test script file for $module for writing\n"; + print TESTFILE "#!/bin/sh\necho TEST: $module\n"; + + if ($target_windows) + { + print TESTFILE <<__E; +kill_process() +{ + if test -r testfiles/\$1.pid; then + /bin/kill -0 -f `cat testfiles/\$1.pid` \\ + && /bin/kill -f `cat testfiles/\$1.pid` + rm testfiles/\$1.pid + fi +} +__E + } + else + { + print TESTFILE <<__E; +kill_process() +{ + test -r testfiles/\$1.pid \\ + && kill -0 `cat testfiles/\$1.pid` \\ + && kill `cat testfiles/\$1.pid` +} +__E + } + + if (-d "$module/testfiles") + { + print TESTFILE <<__E; +kill_daemons() +{ + kill_process bbackupd + kill_process bbstored + kill_process httpserver + kill_process s3simulator +} + +echo Killing any running daemons... +kill_daemons +__E + } + + print TESTFILE <<__E; +echo Removing old test files... +chmod -R a+rwx testfiles +rm -rf testfiles + +echo Copying new test files... +cp -p -R ../../../$module/testfiles . + +__E + + if (-e "$module/testextra") + { + open FL,"$module/testextra" or die + "Can't open $module/testextra"; + while(<FL>) {print TESTFILE} + close FL; + } + + print TESTFILE "$runcmd\n"; + + if (-d "$module/testfiles") + { + print TESTFILE <<__E; +kill_daemons +__E + } + + close TESTFILE; + } + + writetestfile("$mod/_t", "GLIBCXX_FORCE_NEW=1 ". + './test' . $platform_exe_ext . ' "$@"', $mod); + writetestfile("$mod/_t-gdb", "GLIBCXX_FORCE_NEW=1 ". + 'gdb ./test' . $platform_exe_ext . ' "$@"', $mod); + + } + + my @all_deps_for_module; + { + # work out what dependencies need to be run + my @deps_raw; + sub add_mod_deps + { + my ($arr_r,$nm) = @_; + if($#{$module_dependency{$nm}} >= 0) + { + push @$arr_r,@{$module_dependency{$nm}}; + for(@{$module_dependency{$nm}}) + { + add_mod_deps($arr_r,$_) + } + } + } + add_mod_deps(\@deps_raw, $mod); + # and then dedup and reorder them + my %d_done; + foreach my $dep (reverse @deps_raw) + { + if(!exists $d_done{$dep}) + { + # insert + push @all_deps_for_module, $dep; + # mark as done + $d_done{$dep} = 1; + } + } + } + + + # make include path + my $include_paths = join(' ',map {'-I../../'.$_} @all_deps_for_module); + + # is target a library? + my $target_is_library = ($type ne 'bin' && $type ne 'test'); + + # make target name + my $end_target = $name; + + if ($target_is_library) + { + $end_target .= '.a'; + } + else + { + $end_target .= $platform_exe_ext; + } + + $end_target = 'test'.$platform_exe_ext if $type eq 'test'; + + # adjust for outdir + $end_target = '$(OUTDIR)/' . $end_target; + + # start the makefile + my $mk_name_extra = ($bsd_make)?'':'X'; + open MAKE,">$mod/Makefile".$mk_name_extra or die "Can't open Makefile for $mod\n"; + my $debug_link_extra = ($target_is_library)?'':'../../debug/lib/debug/debug.a'; + + my $default_cxxflags = '@CXXFLAGS@'; + $default_cxxflags =~ s/ -O2//g; + + my $release_flags = "-O2"; + if ($target_windows) + { + $release_flags = "-O0 -g"; + } + + print MAKE <<__E; +# +# AUTOMATICALLY GENERATED FILE +# do not edit! +# +# +CXX = @CXX@ +AR = @AR@ +RANLIB = @RANLIB@ +PERL = @PERL@ +WINDRES = @WINDRES@ + +DEFAULT_CXXFLAGS = @CPPFLAGS@ $default_cxxflags @CXXFLAGS_STRICT@ \\ + $include_paths $extra_platform_defines \\ + -DBOX_VERSION="\\"$product_version\\"" +LDFLAGS = @LDFLAGS@ @LDADD_RDYNAMIC@ + +.ifdef RELEASE +CXXFLAGS = -DBOX_RELEASE_BUILD $release_flags \$(DEFAULT_CXXFLAGS) +OUTBASE = ../../release +OUTDIR = ../../release/$mod +DEPENDMAKEFLAGS = -D RELEASE +VARIENT = RELEASE +.else +CXXFLAGS = -g \$(DEFAULT_CXXFLAGS) +OUTBASE = ../../debug +OUTDIR = ../../debug/$mod +DEPENDMAKEFLAGS = +VARIENT = DEBUG +.endif + +__E + + if ($bsd_make) + { + print MAKE <<__E; +.ifdef V +HIDE = +_CXX = \$(CXX) +_LINK = \$(CXX) +_WINDRES = \$(WINDRES) +_AR = \$(AR) +_RANLIB = \$(RANLIB) +_PERL = \$(PERL) +.else +HIDE = @ +_CXX = @ echo " [CXX] " \$(*F) && \$(CXX) +_LINK = @ echo " [LINK] " \$(*F) && \$(CXX) +_WINDRES = @ echo " [WINDRES]" \$(*F) && \$(WINDRES) +_AR = @ echo " [AR] " \$(*F) && \$(AR) +_RANLIB = @ echo " [RANLIB] " \$(*F) && \$(RANLIB) +_PERL = @ echo " [PERL] " \$(*F) && \$(PERL) >/dev/null +.endif + +__E + } + else + { + print MAKE <<__E; +HIDE = \$(if \$(V),,@) +_CXX = \$(if \$(V),\$(CXX), @ echo " [CXX] \$<" && \$(CXX)) +_LINK = \$(if \$(V),\$(CXX), @ echo " [LINK] \$@" && \$(CXX)) +_WINDRES = \$(if \$(V),\$(WINDRES), @ echo " [WINDRES] \$<" && \$(WINDRES)) +_AR = \$(if \$(V),\$(AR), @ echo " [AR] \$@" && \$(AR)) +_RANLIB = \$(if \$(V),\$(RANLIB), @ echo " [RANLIB] \$@" && \$(RANLIB)) +_PERL = \$(if \$(V),\$(PERL), @ echo " [PERL] \$@" && \$(PERL) >/dev/null) + +__E + } + + # read directory + opendir DIR,$mod; + my @items = readdir DIR; + closedir DIR; + + # add in items from autogen directories, and create output directories + { + my @autogen_items; + for my $di (@items) + { + if($di =~ m/\Aautogen/ && -d "$mod/$di") + { + # Read items + my $d = "$mod/$di"; + opendir DIR,$d; + my @i = readdir DIR; + closedir DIR; + for(@i) + { + next if m/\A\./; + push @autogen_items,"$di/$_" + } + + # output directories + mkdir "release/$mod/$di",0755; + mkdir "debug/$mod/$di",0755; + } + } + @items = (@items, @autogen_items); + } + + # first, obtain a list of dependencies within the .h files + my %headers; + for my $h (grep /\.h\Z/i, @items) + { + open FL,"$mod/$h"; + my $f; + read FL,$f,-s "$mod/$h"; + close FL; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + ${$headers{$h}}{$1} = 1 if exists $hfiles{$1}; + } + } + + # ready for the rest of the details... + my $make; + + # then... do the cpp files... + my @obj_base; + for my $file (@items) + { + my $is_cpp = $file =~ m/\A(.+)\.cpp\Z/i; + my $is_rc = $file =~ m/\A(.+)\.rc\Z/i; + my $base = $1; + + if ($target_windows) + { + next if not $is_cpp and not $is_rc; + } + else + { + next if not $is_cpp; + } + + next if $file =~ /\A\._/; # Temp Mac OS Resource hack + + # store for later + push @obj_base,$base; + + # get the file... + open FL,"$mod/$file"; + my $f; + read FL,$f,-s "$mod/$file"; + close FL; + + my %dep; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + insert_dep($1, \%dep) if exists $hfiles{$1}; + } + + # output filename + my $out_name = '$(OUTDIR)/'.$base.'.o'; + + # write the line for this cpp file + my @dep_paths = map + { + ($hfiles{$_} eq $mod) + ? $_ + : '../../'.$hfiles{$_}."/$_" + } + keys %dep; + + $make .= $out_name.': '.join(' ',$file,@dep_paths)."\n"; + + if ($is_cpp) + { + $make .= "\t\$(_CXX) \$(CXXFLAGS) $compile_line_extra ". + "-DBOX_MODULE=\"\\\"$mod\\\"\" " . + "-c $file -o $out_name\n\n"; + } + elsif ($is_rc) + { + $make .= "\t\$(_WINDRES) $file $out_name\n\n"; + my $res_list = $module_resources_win32{$mod}; + $res_list ||= []; + push @$res_list, $base.'.o'; + $module_resources_win32{$mod} = $res_list; + } + } + + my $has_deps = ($#{$module_dependency{$mod}} >= 0); +# ----- # always has dependencies with debug library + $has_deps = 1; + $has_deps = 0 if $target_is_library; + + # Depenency stuff + my $deps_makeinfo; + if($has_deps) + { + if($bsd_make) + { + $deps_makeinfo = <<'__E'; +.BEGIN:: +.ifndef NODEPS +. if $(.TARGETS) == "" +__E + } + else + { + # gnu make + $deps_makeinfo = <<'__E'; +.PHONY: dep_modules +dep_modules: +ifndef NODEPS +ifeq ($(strip $(.TARGETS)),) +__E + } + + # run make for things we require + for my $dep (@all_deps_for_module) + { + $deps_makeinfo .= "\t\t\$(HIDE) (cd ../../$dep; \$(MAKE)$sub_make_options -q \$(DEPENDMAKEFLAGS) -D NODEPS || \$(MAKE)$sub_make_options \$(DEPENDMAKEFLAGS) -D NODEPS)\n"; + } + + $deps_makeinfo .= ".\tendif\n.endif\n\n"; + } + print MAKE $deps_makeinfo if $bsd_make; + + # 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))); + + # need to see if the extra makefile fragments require extra object files + # or include any more makefiles + my @objs = @obj_base; + my @makefile_includes; + + additional_objects_from_make_fragment("$mod/Makefile.extra", \@objs, \@makefile_includes); + additional_objects_from_make_fragment("$mod/Makefile.extra.$build_os", \@objs, \@makefile_includes); + + my $o_file_list = join(' ',map {'$(OUTDIR)/'.$_.'.o'} sort @objs); + + if ($has_deps and not $bsd_make) + { + print MAKE ".PHONY: all\n" . + "all: dep_modules $end_target\n\n"; + } + + print MAKE $end_target,': ',$o_file_list; + print MAKE " ",$lib_files unless $target_is_library; + print MAKE "\n"; + + if ($target_windows) + { + foreach my $dep (@all_deps_for_module) + { + my $res_list = $module_resources_win32{$dep}; + next unless $res_list; + $o_file_list .= ' '.join(' ', + map {'$(OUTBASE)/'.$dep."/$_"} @$res_list); + } + } + + # stuff to make the final target... + if($target_is_library) + { + # make a library archive... + print MAKE "\t\$(HIDE) (echo -n > $end_target; rm $end_target)\n"; + print MAKE "\t\$(_AR) cq $end_target $o_file_list\n"; + print MAKE "\t\$(_RANLIB) $end_target\n"; + } + else + { + # work out library options + # need to be... least used first, in absolute order they appear in the modules.txt file + my @libops; + sub libops_fill + { + my ($m,$r) = @_; + push @$r,$_ for(@{$module_library_link_opts{$m}}); + libops_fill($_,$r) for(@{$module_dependency{$m}}); + } + libops_fill($mod,\@libops); + my $lo = ''; + my %ldone; + for(@libops) + { + next if exists $ldone{$_}; + $lo .= ' '.$_; + $ldone{$_} = 1; + } + + # link line... + print MAKE "\t\$(_LINK) \$(LDFLAGS) $link_line_extra " . + "-o $end_target $o_file_list " . + "$lib_files$lo $platform_lib_files\n"; + } + # 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"; + } + # dependency line? + print MAKE "\n"; + + # module dependencies for GNU make? + print MAKE $deps_makeinfo if !$bsd_make; + + # print the rest of the file + print MAKE $make,"\n"; + + # and a clean target + print MAKE <<EOF; +clean: + -rm -rf \$(OUTDIR)/* +. ifndef SUBCLEAN +EOF + for my $dep (@all_deps_for_module) + { + print MAKE "\t\$(HIDE) (cd ../../$dep; \$(MAKE) \$(DEPENDMAKEFLAGS) -D SUBCLEAN clean)\n"; + } + print MAKE ".\tendif\n"; + + # include any extra stuff + print MAKE "\n\n"; + if(-e "$mod/Makefile.extra") + { + print MAKE ".include <Makefile.extra>\n\n"; + } + if(-e "$mod/Makefile.extra.$build_os") + { + print MAKE ".include <Makefile.extra.$build_os>\n\n"; + } + for(@makefile_includes) + { + print MAKE ".include <$_>\n\n"; + } + + # and finally a target for rebuilding the build system + print MAKE "\nbuildsystem:\n\t(cd ../..; perl ./infrastructure/makebuildenv.pl $makebuildenv_args)\n\n"; + + close MAKE; + + if(!$bsd_make) + { + # need to post process this into a GNU makefile + open MAKE,">$mod/Makefile"; + open MAKEB,"$mod/MakefileX"; + + while(<MAKEB>) + { + s/\A\.\s*(ifdef|else|endif|ifndef)/$1/; + s/\A\.\s*include\s+<(.+?)>/include $1/; + s/-D\s+(\w+)/$1=1/g; + print MAKE; + } + + close MAKEB; + close MAKE; + unlink "$mod/MakefileX"; + } +} + +print "\nType 'cd <module_dir>; $make_command' to build a module\n\n"; + +if($modules_omitted) +{ + print "\nNOTE: Some modules have been omitted on this platform\n\n" +} + +sub insert_dep +{ + my ($h,$dep_r) = @_; + + # stop random recusion + return if exists $$dep_r{$h}; + + # insert more depencies + insert_dep($_,$dep_r) for keys %{$header_dependency{$h}}; + + # mark this one as a dependency + $$dep_r{$h} = 1; +} + + +sub additional_objects_from_make_fragment +{ + my ($fn,$objs_r,$include_r) = @_; + + if(-e $fn) + { + open FL,$fn or die "Can't open $fn"; + + while(<FL>) + { + chomp; + if(m/link-extra:\s*(.+)\Z/) + { + my $extra = $1; + do + { + my @o = split /\s+/, $extra; + for(@o) + { + push @$objs_r,$1 if m/\A(.+)\.o\Z/; + } + last unless $extra =~ m'\\$'; + $extra = <FL>; + } + while(1); + } + elsif(m/include-makefile:\s*(\S+)/) + { + push @$include_r,$1 + } + } + + close FL; + } +} + + +sub ignore_module +{ + exists $env_flags{'IGNORE_'.$_[0]} +} diff --git a/infrastructure/makedistribution.pl.in b/infrastructure/makedistribution.pl.in new file mode 100755 index 00000000..0ccd92be --- /dev/null +++ b/infrastructure/makedistribution.pl.in @@ -0,0 +1,363 @@ +#!@PERL@ + +use strict; +use Symbol; + +# comment string for various endings +my %comment_chars = ('cpp' => '// ', 'h' => '// ', 'pl' => '# ', 'pm' => '# ', '' => '# '); + +# other extensions which need text copying, just to remove the private stuff +# .in is included here, as these could be any kind of source, but clearly +# they have text substitutions run on them by autoconf, so we can too :) +my %text_files = ('txt' => 1, 'spec' => 1, 'in' => 1); + +# files which don't get the license added +# my %file_license = (); # 'filename' => 'GPL', 'DUAL' or 'none' + +# ---------------------------------------------- + +# filled in from the manifest file +# my %dir_license = (); # 'dir' => 'GPL', 'DUAL' or 'none' +# +# most recently specified LICENSE become default until overridden +my $current_license; # 'GPL', 'DUAL' or 'none' + +# distribution name +my $distribution = $ARGV[0]; +die "No distribution name specified on the command line" if $distribution eq ''; +my $dist_root = "distribution/$distribution"; + +# check distribution exists +die "Distribution '$distribution' does not exist" unless -d $dist_root; + +# get version +open VERSION,"$dist_root/VERSION.txt" or die "Can't open $dist_root/VERSION.txt"; +my $version = <VERSION>; +chomp $version; +my $archive_name = <VERSION>; +chomp $archive_name; +close VERSION; + +# consistency check +die "Archive name '$archive_name' is not equal to the distribution name '$distribution'" + unless $archive_name eq $distribution; + +my $svnversion = `svnversion .`; +chomp $svnversion; +$svnversion =~ tr/0-9A-Za-z/_/c; + +if($version =~ /USE_SVN_VERSION/) +{ + # for developers, use SVN version + open INFO,'svn info . |'; + my $svnurl; + while(<INFO>) + { + if(m/^URL: (.+?)[\n\r]+/) + { + $svnurl = $1; + } + } + close INFO; + $svnurl =~ m'box/(.+)$'; + my $svndir = $1; + $svndir =~ tr/0-9A-Za-z/_/c; + $version =~ s/USE_SVN_VERSION/$svndir.'_'.$svnversion/e; +} + +# make initial directory +my $base_name = "$archive_name-$version"; +system "rm -rf $base_name"; +system "rm $base_name.tgz"; +mkdir $base_name,0755; + +# get license files +my %license_text; # name of license => array of lines of license text +foreach my $license ("GPL", "DUAL") +{ + my $file = "./LICENSE-$license.txt"; + open LICENSE, $file or die "Can't open $file: $!"; + my @lines = <LICENSE>; + close LICENSE; + unshift @lines, "distribution $base_name (svn version: $svnversion)\n"; + $license_text{$license} = \@lines; +} + +# copy files, make a note of all the modules included +my %modules_included; +my $private_sections_removed = 0; +my $non_distribution_sections_removed = 0; +sub copy_from_list +{ + my $list = $_[0]; + open LIST,$list or die "Can't open $list"; + + while(my $line = <LIST>) + { + next unless $line =~ m/\S/; + chomp $line; + my @words = split /\s+/, $line; + my ($src,$dst,$other) = @words; + $dst = $src if $dst eq ''; + if($src eq 'MKDIR') + { + # actually we just need to make a directory here + mkdir "$base_name/$dst",0755; + } + elsif($src eq 'LICENSE') + { + $current_license = $dst; + } + elsif($src eq 'REPLACE-VERSION-IN') + { + replace_version_in($dst); + } + elsif($src eq 'RUN') + { + my ($junk,$cmd) = split /\s+/, $line, 2; + print "Running $cmd...\n"; + if(system($cmd) != 0) + { + print "Error running $cmd. Aborting.\n"; + exit(1); + } + } + elsif(-d $src) + { + $modules_included{$line} = 1; + copy_dir($src,$dst); + } + else + { + copy_file($src,$dst); + } + } + + close LIST; +} +copy_from_list("distribution/COMMON-MANIFEST.txt"); +copy_from_list("$dist_root/DISTRIBUTION-MANIFEST.txt"); + +# Copy in the root directory and delete the DISTRIBUTION-MANIFEST file +(system("cp $dist_root/*.* $base_name/") == 0) + or die "Copy of root extra files failed"; +unlink "$base_name/DISTRIBUTION-MANIFEST.txt" + or die "Delete of DISTRIBUTION-MANIFEST.txt file failed"; +replace_version_in("VERSION.txt"); + +# produce a new modules file +my $modules = gensym; +open $modules,"modules.txt" or die "Can't open modules.txt for reading"; +open MODULES_OUT,">$base_name/modules.txt"; + +while(<$modules>) +{ + # skip lines for modules which aren't included + next if m/\A(\w+\/\w+)\s/ && !exists $modules_included{$1}; + + # skip private sections + unless(skip_non_applicable_section($_, $modules, 'modules.txt')) + { + # copy line to out files + print MODULES_OUT + } +} + +close MODULES_OUT; +close $modules; + +# report on how many private sections were removed +print "Private sections removed: $private_sections_removed\nNon-distribution sections removed: $non_distribution_sections_removed\n"; + +# tar it up +system "tar cf - $base_name | gzip -9 - > $base_name.tgz"; + +sub copy_file +{ + my ($fn,$dst_fn) = @_; + + my $ext; + $ext = $1 if $fn =~ m/\.(\w+)\Z/; + $dst_fn =~ m~\A(.+)/[^/]+?\Z~; + + # licensed or not? + if(exists $comment_chars{$ext} && $current_license ne "none") + { + # copy as text, inserting license + # print "source copy $fn to $base_name/$dst_fn\n"; + + my $in = gensym; + open $in,$fn or die "$fn: $!"; + open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; + + my $first = <$in>; + if($first =~ m/\A#!/) + { + print OUT $first; + $first = ''; + } + + # write license + my $b = $comment_chars{$ext}; + my $this_license = $license_text{$current_license}; + for (@$this_license) + { + print OUT $b, $_; + } + + if($first ne '') + { + print OUT $first; + } + + while(<$in>) + { + unless(skip_non_applicable_section($_, $in, $fn)) + { + print OUT + } + } + + close OUT; + close $in; + } + elsif(exists $text_files{$ext}) + { + # copy this as text, to remove private stuff + # print "text copy $fn to $base_name/$dst_fn\n"; + + my $in = gensym; + open $in,$fn or die "$fn: $!"; + open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; + + while(<$in>) + { + unless(skip_non_applicable_section($_, $in, $fn)) + { + print OUT + } + } + + close OUT; + close $in; + } + else + { + # copy as binary + # print "binary copy $fn to $base_name/$dst_fn\n"; + my $cmd = "cp -p $fn $base_name/$dst_fn"; + system($cmd) == 0 or die "copy failed: $cmd"; + } + + # copy executable bit from src + if(-x $fn) + { + system 'chmod','a+x',"$base_name/$dst_fn" + } + else + { + system 'chmod','a-x',"$base_name/$dst_fn" + } +} + +sub skip_non_applicable_section +{ + my ($l, $filehandle, $filename) = @_; + if($l =~ m/BOX_PRIVATE_BEGIN/) + { + # skip private section + print "Removing private section from $filename\n"; + $private_sections_removed++; + while(<$filehandle>) {last if m/BOX_PRIVATE_END/} + + # skipped something + return 1; + } + elsif($l =~ m/IF_DISTRIBUTION\((.+?)\)/) + { + # which distributions does this apply to? + my $applies = 0; + for(split /,/,$1) + { + $applies = 1 if $_ eq $distribution + } + unless($applies) + { + # skip section? + print "Removing distribution specific section from $filename\n"; + $non_distribution_sections_removed++; + while(<$filehandle>) {last if m/END_IF_DISTRIBUTION/} + } + # hide this line + return 1; + } + elsif($l =~ m/END_IF_DISTRIBUTION/) + { + # hide these lines + return 1; + } + else + { + # no skipping, return this line + return 0; + } +} + +sub copy_dir +{ + my ($dir,$dst_dir) = @_; + + # copy an entire directory... first make sure it exists + my @n = split /\//,$dst_dir; + my $d = $base_name; + for(@n) + { + $d .= '/'; + $d .= $_; + mkdir $d,0755; + } + + # then do each of the files within in + opendir DIR,$dir; + my @items = readdir DIR; + closedir DIR; + + for(@items) + { + next if m/\A\./; + next if m/\A_/; + next if m/\AMakefile\Z/; + next if m/\Aautogen/; + next if m/-smf-method\Z/; # copy only the .in versions + next if m/-manifest.xml\Z/; # copy only the .in versions + if($dir eq 'docs') { + next if m/.(x[sm]l|tmpl)\Z/; # don't include doc sources + next if m/generate_except_xml.pl/; + } + next if !-f "$dir/$_"; + + copy_file("$dir/$_","$dst_dir/$_"); + } +} + +sub replace_version_in +{ + my ($file) = @_; + + my $fn = $base_name . '/' . $file; + open IN,$fn or die "Can't open $fn"; + open OUT,'>'.$fn.'.new' or die "Can't open $fn.new for writing"; + + while(<IN>) + { + s/###DISTRIBUTION-VERSION-NUMBER###/$version/g; + s/.*USE_SVN_VERSION.*/$version/g; + print OUT + } + + close OUT; + close IN; + + rename($fn.'.new', $fn) or die "Can't rename in place $fn"; +} + diff --git a/infrastructure/makeparcels.pl.in b/infrastructure/makeparcels.pl.in new file mode 100755 index 00000000..4dc94925 --- /dev/null +++ b/infrastructure/makeparcels.pl.in @@ -0,0 +1,396 @@ +#!@PERL@ + +use strict; +use lib 'infrastructure'; +use BoxPlatform; + +my @parcels; +my %parcel_contents; + +sub starts_with ($$) +{ + my ($string,$expected) = @_; + return substr($string, 0, length $expected) eq $expected; +} + +sub os_matches ($) +{ + my ($prefix_string) = @_; + my @prefixes = split m'\,', $prefix_string; + foreach my $prefix (@prefixes) + { + return 1 if starts_with($build_os, $prefix); + return 1 if starts_with($target_os, $prefix); + } + return 0; +} + +my $copy_command = "cp -p"; + +if ($build_os eq 'CYGWIN') +{ + $copy_command = "cp -pu"; # faster +} + +open PARCELS,"parcels.txt" or die "Can't open parcels file"; +{ + my $cur_parcel = ''; + while(<PARCELS>) + { + chomp; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + # omit bits on some platforms? + next if m/\AEND-OMIT/; + if(m/\AOMIT:(.+)/) + { + if (os_matches($1)) + { + while(<PARCELS>) + { + last if m/\AEND-OMIT/; + } + } + next; + } + + if (m'\AONLY:(.+)') + { + if (not os_matches($1)) + { + while (<PARCELS>) + { + last if m'\AEND-ONLY'; + } + } + next; + } + next if (m'\AEND-ONLY'); + + if (m'\AEXCEPT:(.+)') + { + if (os_matches($1)) + { + while (<PARCELS>) + { + last if m'\AEND-EXCEPT'; + } + } + next; + } + next if (m'\AEND-EXCEPT'); + + # new parcel, or a new parcel definition? + if(m/\A\s+(.+)\Z/) + { + push @{$parcel_contents{$cur_parcel}},$1 + } + else + { + $cur_parcel = $_; + push @parcels,$_; + } + } +} +close PARCELS; + +# create parcels directory +mkdir "parcels",0755; +mkdir "parcels/scripts",0755; + +# write master makefile + +open MAKE,">Makefile" or die "Can't open master Makefile for writing"; + +print MAKE <<__E; +# +# AUTOMATICALLY GENERATED FILE +# do not edit! +# +# + +MAKE = $make_command + +__E + +print MAKE "all:\t",join(' ',map {"build-".$_} @parcels),"\n\n"; + +print MAKE <<__END_OF_FRAGMENT; +test: release/common/test + +release/common/test: + ./runtest.pl ALL release + +.PHONY: docs +docs: + \$(MAKE) -C docs + +__END_OF_FRAGMENT + +my $release_flag = BoxPlatform::make_flag('RELEASE'); +my @clean_deps; + +for my $parcel (@parcels) +{ + my $version = BoxPlatform::parcel_root($parcel); + my $target = BoxPlatform::parcel_target($parcel); + my $dir = BoxPlatform::parcel_dir($parcel); + my @parcel_deps; + + unless ($target_windows) + { + open SCRIPT,">parcels/scripts/install-$parcel" or die + "Can't open installer script for $parcel for writing"; + print SCRIPT "#!/bin/sh\n\n"; + } + + for(@{$parcel_contents{$parcel}}) + { + my @args = split /\s+/; + + my ($type,$name,$dest) = @args; + my $optional = 0; + my $install = 1; + + if ($type eq 'optional') + { + $optional = 1; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'noinstall') + { + $install = 0; + shift @args; + ($type,$name,$dest) = @args; + } + + if($type eq 'bin') + { + my $exeext = $platform_exe_ext; + print MAKE <<EOF; +$dir/$name$exeext: release/bin/$name/$name$exeext + mkdir -p $dir + $copy_command release/bin/$name/$name$exeext $dir + +.PHONY: release/bin/$name/$name$exeext +release/bin/$name/$name$exeext: + (cd bin/$name; \$(MAKE) $release_flag) + +EOF + push @parcel_deps, "$dir/$name$exeext"; + } + elsif ($type eq 'script') + { + my $fullpath = $name; + my $filename = $name; + # remove path from script name + $filename =~ s{.*/}{}; + + print MAKE <<EOF; +$dir/$filename: $fullpath + mkdir -p $dir +EOF + + if ($optional) + { + print MAKE "\ttest -r $fullpath " . + "&& $copy_command $fullpath $dir || true\n"; + } + else + { + print MAKE "\t$copy_command $fullpath $dir\n"; + } + + print MAKE "\n"; + + push @parcel_deps, "$dir/$filename"; + } + elsif($type eq 'man') + { + print MAKE <<EOF; +$dir/${name}.gz: docs/man/${name}.gz + mkdir -p $dir + $copy_command docs/man/${name}.gz $dir + +EOF + # Releases have the docs pre-made, but users + # may want to rebuild them for some reason. + print MAKE <<EOF; +.PHONY: docs/man/${name}.gz +docs/man/${name}.gz: + \$(MAKE) -C docs man/${name}.gz + +EOF + push @parcel_deps, "$dir/${name}.gz"; + } + elsif($type eq 'html') + { + print MAKE <<EOF; +$dir/docs/${name}.html: docs/htmlguide/man-html/${name}.html + mkdir -p $dir/docs + $copy_command docs/htmlguide/man-html/${name}.html $dir/docs + +EOF + # Releases have the docs pre-made, but users + # may want to rebuild them for some reason. + print MAKE <<EOF; +.PHONY: docs/htmlguide/man-html/${name}.html +docs/htmlguide/man-html/${name}.html: + \$(MAKE) -C docs htmlguide/man-html/${name}.html + +EOF + push @parcel_deps, "$dir/docs/${name}.html"; + } + elsif ($type eq 'subdir') + { + print MAKE <<EOF; +.PHONY: $name-build $name-clean + +$name-build: + \$(MAKE) -C $name + +$name-clean: + \$(MAKE) -C $name clean +EOF + push @parcel_deps, "$name-build"; + push @clean_deps, "$name-clean"; + } + } + + print MAKE <<EOF; +build-$parcel: $target + +$target: @parcel_deps + test -d $dir || mkdir $dir +EOF + + for(@{$parcel_contents{$parcel}}) + { + my @args = split /\s+/; + + my ($type,$name,$dest) = @args; + + my $optional = 0; + my $install = 1; + + if ($type eq 'optional') + { + $optional = 1; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'noinstall') + { + $install = 0; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'script') + { + # remove path from script name + $name =~ s{.*/}{}; + } + + if ($type eq 'html') + { + $dest = "share/doc/$version"; + $name = "docs/$name.html"; + } + + if ($type eq 'man') + { + $name =~ /([0-9])$/; + $dest = "man/man$1"; + $name =~ s/$/\.gz/; + } + + if ($install and not $target_windows) + { + my $local_install_dir = $install_into_dir; + if (defined $dest) + { + if ($dest =~ m,^/,) + { + # Don't add $prefix if $dest is a literal path + $local_install_dir = $dest; + } + else + { + $local_install_dir = "@prefix@/$dest"; + } + } + print SCRIPT "mkdir -p " . + "\${DESTDIR}$local_install_dir/\n"; + print SCRIPT "install $name " . + "\${DESTDIR}$local_install_dir\n"; + } + } + + unless ($target_windows) + { + close SCRIPT; + chmod 0755,"parcels/scripts/install-$parcel"; + } + + my $root = BoxPlatform::parcel_root($parcel); + + unless ($target_windows) + { + print MAKE "\tcp parcels/scripts/install-$parcel $dir\n"; + } + + print MAKE "\t(cd parcels; tar cf - $root | gzip -9 - > $root.tgz )\n"; + + print MAKE "\n"; + + unless ($target_windows) + { + print MAKE "install-$parcel:\n"; + print MAKE "\t(cd $dir; ./install-$parcel)\n\n"; + } +} + +print MAKE <<EOF; +install: + cat local/install.msg + +clean: @clean_deps + \$(MAKE) -C docs clean +EOF + +if ($build_os eq 'CYGWIN') +{ + print MAKE "\tfind release debug -type f | xargs -r rm -f\n"; +} +else +{ + print MAKE "\tfind release debug -type f -exec rm -f {} \\;\n"; +} + +for my $parcel (@parcels) +{ + print MAKE "\trm -rf ", BoxPlatform::parcel_dir($parcel), "\n"; + print MAKE "\trm -f ", BoxPlatform::parcel_target($parcel), "\n"; +} + +close MAKE; + +open INSTALLMSG,">local/install.msg" or die "Can't open install message file for writing"; +print INSTALLMSG <<__E; + +Parcels need to be installed separately, and as root. Type one of the following: + +__E + +for(@parcels) +{ + print INSTALLMSG " $make_command install-".$_."\n"; +} +print INSTALLMSG "\n"; + +close INSTALLMSG; + diff --git a/infrastructure/mingw/configure.sh b/infrastructure/mingw/configure.sh new file mode 100755 index 00000000..0486b20d --- /dev/null +++ b/infrastructure/mingw/configure.sh @@ -0,0 +1,38 @@ +#!/bin/sh + +DEP_PATH=/usr/i686-pc-mingw32 + +if [ ! -r "$DEP_PATH/lib/libssl.a" ]; then + echo "Error: install OpenSSL as instructed by" \ + "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 + exit 2 +fi + +if [ ! -r "$DEP_PATH/lib/libpcreposix.a" \ + -o ! -r "$DEP_PATH/lib/libpcre.a" \ + -o ! -r "$DEP_PATH/include/pcreposix.h" ]; then + echo "Error: install PCRE as instructed by" \ + "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 + exit 2 +fi + +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" + +if [ ! -x "configure" ]; then + if ! ./bootstrap; then + echo "Error: bootstrap failed, aborting." >&2 + exit 1 + fi +fi + +if ! ./configure --target=i686-pc-mingw32; then + echo "Error: configure failed, aborting." >&2 + exit 1 +fi + +exit 0 diff --git a/infrastructure/msvc/2003/bbackupctl.vcproj b/infrastructure/msvc/2003/bbackupctl.vcproj new file mode 100644 index 00000000..02f7482e --- /dev/null +++ b/infrastructure/msvc/2003/bbackupctl.vcproj @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="bbackupctl" + ProjectGUID="{9FD51412-E945-4457-A17A-CA3C505CF431}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/bbackupctl.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupctl" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupctl\bbackupctl.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/bbackupd.vcproj b/infrastructure/msvc/2003/bbackupd.vcproj new file mode 100644 index 00000000..f34db0cc --- /dev/null +++ b/infrastructure/msvc/2003/bbackupd.vcproj @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="bbackupd" + ProjectGUID="{22D325FB-9131-4BD6-B390-968F0491D687}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(SolutionDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(SolutionDir)..\..\..\..\boost_1_31_0";"$(SolutionDir)..\..\..\..\openssl\include";"$(SolutionDir)..\..\..\..\zlib\include";"$(SolutionDir)..\..\..\lib\backupclient";"$(SolutionDir)..\..\..\lib\server";"$(SolutionDir)..\..\..\lib\crypto";"$(SolutionDir)..\..\..\lib\compress";"$(SolutionDir)..\..\..\lib\win32";"$(SolutionDir)..\..\..\lib\common\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="2" + IgnoreAllDefaultLibraries="FALSE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/bbackupd.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOOST_REGEX_NO_LIB" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupd" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\bbackupd.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupd" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/boxbackup.sln b/infrastructure/msvc/2003/boxbackup.sln new file mode 100644 index 00000000..d9a28041 --- /dev/null +++ b/infrastructure/msvc/2003/boxbackup.sln @@ -0,0 +1,57 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.ActiveCfg = Debug|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.Build.0 = Debug|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.ActiveCfg = Release|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.Build.0 = Release|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.ActiveCfg = Debug|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.Build.0 = Debug|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.ActiveCfg = Release|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.Build.0 = Release|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.ActiveCfg = Debug|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.Build.0 = Debug|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Release.ActiveCfg = Release|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Release.Build.0 = Release|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.ActiveCfg = Debug|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.Build.0 = Debug|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.ActiveCfg = Release|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.Build.0 = Release|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.ActiveCfg = Debug|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.Build.0 = Debug|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.ActiveCfg = Release|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/infrastructure/msvc/2003/boxquery.vcproj b/infrastructure/msvc/2003/boxquery.vcproj new file mode 100644 index 00000000..6ac09024 --- /dev/null +++ b/infrastructure/msvc/2003/boxquery.vcproj @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="boxquery" + ProjectGUID="{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + RootNamespace="boxquery" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/boxquery.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOOST_REGEX_NO_LIB" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\Release\common.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="FALSE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="backupquery" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupquery\autogen_Documentation.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\bbackupquery.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="backupquery" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/common.vcproj b/infrastructure/msvc/2003/common.vcproj new file mode 100644 index 00000000..fb18b76a --- /dev/null +++ b/infrastructure/msvc/2003/common.vcproj @@ -0,0 +1,672 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="common" + ProjectGUID="{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="4" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="lib" + Filter=""> + <Filter + Name="compress" + Filter=""> + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.cpp"> + </File> + </Filter> + <Filter + Name="common" + Filter=""> + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ConversionString.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugAssertFailed.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugMemLeakFinder.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugPrintf.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Logging.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Utils.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.cpp"> + </File> + </Filter> + <Filter + Name="backupclient" + Filter=""> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbIdx.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCombine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileRevDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectDump.cpp"> + </File> + </Filter> + <Filter + Name="crypto" + Filter=""> + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.cpp"> + </File> + </Filter> + <Filter + Name="win32" + Filter=""> + <File + RelativePath="..\..\..\lib\win32\emu.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\getopt_long.cxx"> + </File> + </Filter> + <Filter + Name="server" + Filter=""> + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Socket.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.cpp"> + </File> + + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="lib" + Filter=""> + <Filter + Name="compress" + Filter=""> + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\Compress.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressException.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.h"> + </File> + </Filter> + <Filter + Name="common" + Filter=""> + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BannerText.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BeginStructPackForWire.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Box.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxPlatform.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxPortsAndFiles.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToUnix.h"> + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\CommonException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Conversion.h"> + </File> + <File + RelativePath="..\..\..\lib\common\EndStructPackForWire.h"> + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.h"> + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FileModificationTime.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Guards.h"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.h"> + </File> + <File + RelativePath="..\..\..\lib\common\LinuxWorkaround.h"> + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Logging.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MainHelper.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFinder.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOff.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOn.h"> + </File> + <File + RelativePath="..\..\..\lib\common\NamedLock.h"> + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.h"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.h"> + </File> + <File + RelativePath="..\..\..\lib\common\TemporaryDirectory.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Test.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Timer.h"> + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Utils.h"> + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxVersion.h"> + </File> + </Filter> + <Filter + Name="backupclient" + Filter=""> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreConstants.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreException.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileWire.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectMagic.h"> + </File> + </Filter> + <Filter + Name="crypto" + Filter=""> + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherException.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.h"> + </File> + </Filter> + <Filter + Name="win32" + Filter=""> + <File + RelativePath="..\..\..\lib\win32\emu.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\getopt.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + <Filter + Name="server" + Filter=""> + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolWire.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerTLS.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Socket.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketListen.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.h"> + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.h"> + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/win32test.vcproj b/infrastructure/msvc/2003/win32test.vcproj new file mode 100644 index 00000000..2ef7164e --- /dev/null +++ b/infrastructure/msvc/2003/win32test.vcproj @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="win32test" + ProjectGUID="{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\bin\bbackupd";"$(ProjectDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/win32test.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\bin\bbackupd";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <File + RelativePath="..\..\..\lib\win32\emu.cpp"> + </File> + <File + RelativePath="..\..\..\test\win32\testlibwin32.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/bbackupctl.vcproj b/infrastructure/msvc/2005/bbackupctl.vcproj new file mode 100644 index 00000000..216a284b --- /dev/null +++ b/infrastructure/msvc/2005/bbackupctl.vcproj @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="bbackupctl" + ProjectGUID="{9FD51412-E945-4457-A17A-CA3C505CF431}" + RootNamespace="bbackupctl" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/bbackupctl.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupctl" + > + <File + RelativePath="..\..\..\bin\bbackupctl\bbackupctl.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/bbackupd.vcproj b/infrastructure/msvc/2005/bbackupd.vcproj new file mode 100644 index 00000000..ac8eb86a --- /dev/null +++ b/infrastructure/msvc/2005/bbackupd.vcproj @@ -0,0 +1,299 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="bbackupd" + ProjectGUID="{22D325FB-9131-4BD6-B390-968F0491D687}" + RootNamespace="bbackupd" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(SolutionDir)..\..\..\lib\backupclient";"$(SolutionDir)..\..\..\lib\common";"$(SolutionDir)..\..\..\lib\compress";"$(SolutionDir)..\..\..\lib\crypto";"$(SolutionDir)..\..\..\lib\server";"$(SolutionDir)..\..\..\lib\win32";"$(SolutionDir)..\..\..\..\openssl\inc32";"$(SolutionDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib User32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="2" + IgnoreAllDefaultLibraries="false" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/bbackupd.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupd" + > + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\bbackupd.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupd" + > + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/boxbackup.sln b/infrastructure/msvc/2005/boxbackup.sln new file mode 100644 index 00000000..833066e9 --- /dev/null +++ b/infrastructure/msvc/2005/boxbackup.sln @@ -0,0 +1,55 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/infrastructure/msvc/2005/boxbackup.suo b/infrastructure/msvc/2005/boxbackup.suo new file mode 100644 index 00000000..534f337c Binary files /dev/null and b/infrastructure/msvc/2005/boxbackup.suo differ diff --git a/infrastructure/msvc/2005/boxquery.vcproj b/infrastructure/msvc/2005/boxquery.vcproj new file mode 100644 index 00000000..776c0ac9 --- /dev/null +++ b/infrastructure/msvc/2005/boxquery.vcproj @@ -0,0 +1,246 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="boxquery" + ProjectGUID="{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + RootNamespace="boxquery" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\pcre";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/boxquery.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="false" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="backupquery" + > + <File + RelativePath="..\..\..\bin\bbackupquery\autogen_Documentation.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\bbackupquery.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="bin" + > + <Filter + Name="backupquery" + > + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/common.vcproj b/infrastructure/msvc/2005/common.vcproj new file mode 100644 index 00000000..256bce06 --- /dev/null +++ b/infrastructure/msvc/2005/common.vcproj @@ -0,0 +1,896 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="common" + ProjectGUID="{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + RootNamespace="common" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="4" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + Description="Determining Version Number" + CommandLine="perl $(InputDir)..\getversion.pl" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\..\pcre";$(NOINHERIT)" + PreprocessorDefinitions="WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + WarnAsError="false" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="4" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="lib" + > + <Filter + Name="compress" + > + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.cpp" + > + </File> + </Filter> + <Filter + Name="common" + > + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ConversionString.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugAssertFailed.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugMemLeakFinder.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugPrintf.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Logging.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Timer.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Utils.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.cpp" + > + </File> + </Filter> + <Filter + Name="backupclient" + > + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbIdx.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCombine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileRevDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectDump.cpp" + > + </File> + </Filter> + <Filter + Name="crypto" + > + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.cpp" + > + </File> + </Filter> + <Filter + Name="win32" + > + <File + RelativePath="..\..\..\lib\win32\emu.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\win32\getopt_long.cxx" + > + </File> + </Filter> + <Filter + Name="server" + > + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Socket.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="lib" + > + <Filter + Name="compress" + > + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\Compress.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressException.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.h" + > + </File> + </Filter> + <Filter + Name="common" + > + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BannerText.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BeginStructPackForWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Box.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxConfig-MSVC.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxPlatform.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxPortsAndFiles.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToUnix.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxVersion.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\CommonException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Conversion.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\EndStructPackForWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileModificationTime.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Guards.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Logging.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MainHelper.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFinder.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOff.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOn.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\NamedLock.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\TemporaryDirectory.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Test.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Timer.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Utils.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.h" + > + </File> + </Filter> + <Filter + Name="backupclient" + > + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreConstants.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreException.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectMagic.h" + > + </File> + </Filter> + <Filter + Name="crypto" + > + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherException.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.h" + > + </File> + </Filter> + <Filter + Name="win32" + > + <File + RelativePath="..\..\..\lib\win32\emu.h" + > + </File> + <File + RelativePath="..\..\..\lib\win32\getopt.h" + > + </File> + </Filter> + <Filter + Name="server" + > + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerTLS.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Socket.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketListen.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/win32test.vcproj b/infrastructure/msvc/2005/win32test.vcproj new file mode 100644 index 00000000..0f97c302 --- /dev/null +++ b/infrastructure/msvc/2005/win32test.vcproj @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="win32test" + ProjectGUID="{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + RootNamespace="win32test" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/win32test.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath="..\..\..\lib\win32\emu.cpp" + > + </File> + <File + RelativePath="..\..\..\test\win32\testlibwin32.cpp" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/getversion.pl b/infrastructure/msvc/getversion.pl new file mode 100644 index 00000000..12554d01 --- /dev/null +++ b/infrastructure/msvc/getversion.pl @@ -0,0 +1,19 @@ +#!perl + +$basedir = $0; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; + +require "$basedir\\infrastructure\\BoxPlatform.pm.in"; + +open VERSIONFILE, "> $basedir/lib/common/BoxVersion.h" + or die "BoxVersion.h: $!"; +print VERSIONFILE "#define BOX_VERSION \"$BoxPlatform::product_version\"\n"; +close VERSIONFILE; + +exit 0; diff --git a/infrastructure/parcelpath.pl b/infrastructure/parcelpath.pl new file mode 100644 index 00000000..24f951a2 --- /dev/null +++ b/infrastructure/parcelpath.pl @@ -0,0 +1,17 @@ +#!perl + +unless (@ARGV == 2) +{ + die "Usage: $0 <parcel-name> <target-os>\n"; +} + +$basedir = $0; +$basedir =~ s|/.*||; +$basedir .= "/.."; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; +require "infrastructure/BoxPlatform.pm.in"; + +print BoxPlatform::parcel_dir(@ARGV) . "\n"; + +exit 0; diff --git a/infrastructure/printversion.pl b/infrastructure/printversion.pl new file mode 100644 index 00000000..13815284 --- /dev/null +++ b/infrastructure/printversion.pl @@ -0,0 +1,12 @@ +#!perl + +$basedir = $0; +$basedir =~ s|/.*||; +$basedir .= "/.."; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; +require "infrastructure/BoxPlatform.pm.in"; + +print "$BoxPlatform::product_version\n"; + +exit 0; diff --git a/infrastructure/setupexternal.pl b/infrastructure/setupexternal.pl new file mode 100755 index 00000000..87ec5560 --- /dev/null +++ b/infrastructure/setupexternal.pl @@ -0,0 +1,55 @@ +#!@PERL@ +use strict; + +# This script links in the essential directories and processes various +# files to allow the Box libraries to be used in projects outside the main +# box library tree. + +# directories to link through +my @linkdirs = qw/lib infrastructure/; + +# ---------------------------------------------------- + +my $libdir = $ARGV[0]; +die "Provided library dir $libdir does not exist" unless -d $libdir; + +# Check and remove links from the directory, then add new symlinks +for my $d (@linkdirs) +{ + if(-e $d) + { + die "In project, $d is not a symbolic link" + unless -l $d; + print "Removing existing symlink $d\n"; + unlink $d; + } + my $link_target = "$libdir/$d"; + print "Add symlink $d -> $link_target\n"; + die "Can't create symlink $d" unless + symlink $link_target, $d; +} + +# Copy and create a base modules file which includes all the libraries +print "Create new modules_base.txt file\n"; +open OUT,">modules_base.txt" or die "Can't open modules_base.txt file for writing"; +print OUT <<__E; +# +# Automatically generated file, do not edit +# +# Source: $libdir/modules.txt +# + +__E + +open IN,"$libdir/modules.txt" or die "Can't open $libdir/modules.txt for reading"; + +while(<IN>) +{ + if(m/\A(lib\/.+?)\s/) + { + print OUT + } +} + +close IN; +close OUT; diff --git a/lib/backupclient/BackupClientCryptoKeys.cpp b/lib/backupclient/BackupClientCryptoKeys.cpp new file mode 100644 index 00000000..7a8da7ba --- /dev/null +++ b/lib/backupclient/BackupClientCryptoKeys.cpp @@ -0,0 +1,85 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientCryptoKeys.cpp +// Purpose: function for setting up all the backup client keys +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "BackupClientCryptoKeys.h" +#include "FileStream.h" +#include "BackupStoreFilenameClear.h" +#include "BackupStoreException.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreFile.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientCryptoKeys_Setup(const char *) +// Purpose: Read in the key material file, and set keys to all the backup elements required. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename) +{ + // Read in the key material + unsigned char KeyMaterial[BACKUPCRYPTOKEYS_FILE_SIZE]; + + // Open the file + FileStream file(rKeyMaterialFilename); + + // Read in data + if(!file.ReadFullBuffer(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE, 0)) + { + THROW_EXCEPTION(BackupStoreException, CouldntLoadClientKeyMaterial) + } + + // Setup keys and encoding method for filename encryption + BackupStoreFilenameClear::SetBlowfishKey( + KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_KEY_START, + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH, + KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_IV_START, + BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH); + BackupStoreFilenameClear::SetEncodingMethod( + BackupStoreFilename::Encoding_Blowfish); + + // Setup key for attributes encryption + BackupClientFileAttributes::SetBlowfishKey( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH); + + // Setup secret for attribute hashing + BackupClientFileAttributes::SetAttributeHashSecret( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START, + BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH); + + // Setup keys for file data encryption + BackupStoreFile::SetBlowfishKeys( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH, + KeyMaterial + BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START, + BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH); + +#ifndef HAVE_OLD_SSL + // Use AES where available + BackupStoreFile::SetAESKey( + KeyMaterial + BACKUPCRYPTOKEYS_FILE_AES_KEY_START, + BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH); +#endif + + // Wipe the key material from memory + #ifdef _MSC_VER // not defined on MinGW + SecureZeroMemory(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE); + #else + ::memset(KeyMaterial, 0, BACKUPCRYPTOKEYS_FILE_SIZE); + #endif +} + diff --git a/lib/backupclient/BackupClientCryptoKeys.h b/lib/backupclient/BackupClientCryptoKeys.h new file mode 100644 index 00000000..f40e2e03 --- /dev/null +++ b/lib/backupclient/BackupClientCryptoKeys.h @@ -0,0 +1,55 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientCryptoKeys.h +// Purpose: Format of crypto keys file, and function for setting everything up +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTCRYTOKEYS__H +#define BACKUPCLIENTCRYTOKEYS__H + + +// All keys are the maximum size that Blowfish supports. Since only the +// setup time is affected by key length (encryption same speed whatever) +// there is no disadvantage to using long keys as they are never +// transmitted and are static over long periods of time. + + +// All sizes in bytes. Some gaps deliberately left in the used material. + +// How long the key material file is expected to be +#define BACKUPCRYPTOKEYS_FILE_SIZE 1024 + +// key for encrypting filenames (448 bits) +#define BACKUPCRYPTOKEYS_FILENAME_KEY_START 0 +#define BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH 56 +#define BACKUPCRYPTOKEYS_FILENAME_IV_START (0 + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH) +#define BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH 8 + +// key for encrypting attributes (448 bits) +#define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START (BACKUPCRYPTOKEYS_FILENAME_KEY_START+64) +#define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH 56 + +// Blowfish key for encrypting file data (448 bits (max blowfish key length)) +#define BACKUPCRYPTOKEYS_FILE_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START+64) +#define BACKUPCRYPTOKEYS_FILE_KEY_LENGTH 56 + +// key for encrypting file block index entries +#define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START (BACKUPCRYPTOKEYS_FILE_KEY_START+64) +#define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH 56 + +// Secret for hashing attributes +#define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START (BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START+64) +#define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH 128 + +// AES key for encrypting file data (256 bits (max AES key length)) +#define BACKUPCRYPTOKEYS_FILE_AES_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START+128) +#define BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH 32 + + +void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename); + +#endif // BACKUPCLIENTCRYTOKEYS__H + diff --git a/lib/backupclient/BackupClientFileAttributes.cpp b/lib/backupclient/BackupClientFileAttributes.cpp new file mode 100644 index 00000000..b25ed9c7 --- /dev/null +++ b/lib/backupclient/BackupClientFileAttributes.cpp @@ -0,0 +1,1186 @@ +// -------------------------------------------------------------------------- +// +// 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(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 char *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, &st) != 0) + { + BOX_LOG_SYS_ERROR("Failed to stat file: '" << + Filename << "'"); + THROW_EXCEPTION(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::ReadAttributesLink() +// Purpose: Private function, handles standard attributes for all objects +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- +void BackupClientFileAttributes::FillAttributes(StreamableMemBlock &outputBlock, const char *Filename, 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::ReadAttributesLink() +// Purpose: Private function, handles the case where a symbolic link is needed +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- +void BackupClientFileAttributes::FillAttributesLink(StreamableMemBlock &outputBlock, const char *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, 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::ReadExtendedAttr(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 char *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, list, listBufferSize); + + if(listSize>listBufferSize) + { + delete[] list, list = NULL; + list = new char[listSize]; + listSize = ::llistxattr(Filename, 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, attrKey.c_str(), 0, 0); + if(valueSize<0) + { + BOX_LOG_SYS_ERROR("Failed to get " + "extended attributes size " + "for '" << Filename << "'"); + 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, attrKey.c_str(), buffer+xattrSize, xattrBufferSize-xattrSize); + if(valueSize<0) + { + BOX_LOG_SYS_ERROR("Failed to get " + "extended attributes for " + "'" << Filename << "'"); + 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 char *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); + if(::symlink((char*)(pattr + 1), Filename) != 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, 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, 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, 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, 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 char *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, 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 = *((uint64_t *)(digest.DigestAsData())); + return result; +} diff --git a/lib/backupclient/BackupClientFileAttributes.h b/lib/backupclient/BackupClientFileAttributes.h new file mode 100644 index 00000000..f9a0d883 --- /dev/null +++ b/lib/backupclient/BackupClientFileAttributes.h @@ -0,0 +1,78 @@ +// -------------------------------------------------------------------------- +// +// 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 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 char *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 char *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 char *Filename); + +private: + static void FillAttributes(StreamableMemBlock &outputBlock, + const char *Filename, EMU_STRUCT_STAT &st, + bool ZeroModificationTimes); + static void FillAttributesLink(StreamableMemBlock &outputBlock, const char *Filename, struct stat &st); + void WriteExtendedAttr(const char *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/backupclient/BackupClientMakeExcludeList.cpp b/lib/backupclient/BackupClientMakeExcludeList.cpp new file mode 100644 index 00000000..0615faaa --- /dev/null +++ b/lib/backupclient/BackupClientMakeExcludeList.cpp @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientMakeExcludeList.cpp +// Purpose: Makes exclude lists from bbbackupd config location entries +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "BackupClientMakeExcludeList.h" +#include "Configuration.h" +#include "ExcludeList.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList(const Configuration &, const char *, const char *) +// Purpose: Given a Configuration object corresponding to a bbackupd Location, and the +// two names of the keys for definite and regex entries, return a ExcludeList. +// Or 0 if it isn't required. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, + const char *AlwaysIncludeDefiniteName, const char *AlwaysIncludeRegexName) +{ + // Check that at least one of the entries exists + if(!rConfig.KeyExists(DefiniteName) && !rConfig.KeyExists(RegexName)) + { + // Neither exists -- return 0 as an Exclude list isn't required. + return 0; + } + + // Create the exclude list + ExcludeList *pexclude = new ExcludeList; + + try + { + // Definite names to add? + if(rConfig.KeyExists(DefiniteName)) + { + pexclude->AddDefiniteEntries(rConfig.GetKeyValue(DefiniteName)); + } + // Regular expressions to add? + if(rConfig.KeyExists(RegexName)) + { + pexclude->AddRegexEntries(rConfig.GetKeyValue(RegexName)); + } + + // Add a "always include" list? + if(AlwaysIncludeDefiniteName != 0 && AlwaysIncludeRegexName != 0) + { + // This will accept NULL as a valid argument, so safe to do this. + pexclude->SetAlwaysIncludeList( + BackupClientMakeExcludeList(rConfig, AlwaysIncludeDefiniteName, AlwaysIncludeRegexName) + ); + } + } + catch(...) + { + // Clean up + delete pexclude; + throw; + } + + return pexclude; +} + + + diff --git a/lib/backupclient/BackupClientMakeExcludeList.h b/lib/backupclient/BackupClientMakeExcludeList.h new file mode 100644 index 00000000..d5dd8af4 --- /dev/null +++ b/lib/backupclient/BackupClientMakeExcludeList.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientMakeExcludeList.h +// Purpose: Makes exclude lists from bbbackupd config location entries +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTMAKEEXCLUDELIST__H +#define BACKUPCLIENTMAKEEXCLUDELIST__H + +class ExcludeList; +class Configuration; + +ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, + const char *AlwaysIncludeDefiniteName = 0, const char *AlwaysIncludeRegexName = 0); + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList_Files(const Configuration &) +// Purpose: Create a exclude list from config file entries for files. May return 0. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +inline ExcludeList *BackupClientMakeExcludeList_Files(const Configuration &rConfig) +{ + return BackupClientMakeExcludeList(rConfig, "ExcludeFile", "ExcludeFilesRegex", "AlwaysIncludeFile", "AlwaysIncludeFilesRegex"); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList_Dirs(const Configuration &) +// Purpose: Create a exclude list from config file entries for directories. May return 0. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +inline ExcludeList *BackupClientMakeExcludeList_Dirs(const Configuration &rConfig) +{ + return BackupClientMakeExcludeList(rConfig, "ExcludeDir", "ExcludeDirsRegex", "AlwaysIncludeDir", "AlwaysIncludeDirsRegex"); +} + + +#endif // BACKUPCLIENTMAKEEXCLUDELIST__H + diff --git a/lib/backupclient/BackupClientRestore.cpp b/lib/backupclient/BackupClientRestore.cpp new file mode 100644 index 00000000..fa61bb59 --- /dev/null +++ b/lib/backupclient/BackupClientRestore.cpp @@ -0,0 +1,918 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientRestore.cpp +// Purpose: +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <string> +#include <set> +#include <limits.h> +#include <stdio.h> +#include <errno.h> + +#include "BackupClientRestore.h" +#include "autogen_BackupProtocolClient.h" +#include "CommonException.h" +#include "BackupClientFileAttributes.h" +#include "IOStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "CollectInBufferStream.h" +#include "FileStream.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES (128*1024) + +class RestoreResumeInfo +{ +public: + // constructor + RestoreResumeInfo() + : mNextLevelID(0), + mpNextLevel(0) + { + } + + // destructor + ~RestoreResumeInfo() + { + delete mpNextLevel; + mpNextLevel = 0; + } + + // Get a next level object + RestoreResumeInfo &AddLevel(int64_t ID, const std::string &rLocalName) + { + ASSERT(mpNextLevel == 0 && mNextLevelID == 0); + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = ID; + mNextLevelLocalName = rLocalName; + return *mpNextLevel; + } + + // Remove the next level info + void RemoveLevel() + { + ASSERT(mpNextLevel != 0 && mNextLevelID != 0); + delete mpNextLevel; + mpNextLevel = 0; + mNextLevelID = 0; + mNextLevelLocalName.erase(); + } + + void Save(const std::string &rFilename) const + { + // TODO: use proper buffered streams when they're done + // Build info in memory buffer + CollectInBufferStream write; + + // Save this level + SaveLevel(write); + + // Store in file + write.SetForReading(); + FileStream file(rFilename.c_str(), O_WRONLY | O_CREAT); + write.CopyStreamTo(file, IOStream::TimeOutInfinite, 8*1024 /* large buffer */); + } + + void SaveLevel(IOStream &rWrite) const + { + // Write the restored objects + int64_t numObjects = mRestoredObjects.size(); + rWrite.Write(&numObjects, sizeof(numObjects)); + for(std::set<int64_t>::const_iterator i(mRestoredObjects.begin()); i != mRestoredObjects.end(); ++i) + { + int64_t id = *i; + rWrite.Write(&id, sizeof(id)); + } + + // Next level? + if(mpNextLevel != 0) + { + // ID + rWrite.Write(&mNextLevelID, sizeof(mNextLevelID)); + // Name string + int32_t nsize = mNextLevelLocalName.size(); + rWrite.Write(&nsize, sizeof(nsize)); + rWrite.Write(mNextLevelLocalName.c_str(), nsize); + // And then the level itself + mpNextLevel->SaveLevel(rWrite); + } + else + { + // Just write a zero + int64_t zero = 0; + rWrite.Write(&zero, sizeof(zero)); + } + } + + // Not written to be efficient -- shouldn't be called very often. + bool Load(const std::string &rFilename) + { + // Delete and reset if necessary + if(mpNextLevel != 0) + { + RemoveLevel(); + } + + // Open file + FileStream file(rFilename.c_str()); + + // Load this level + return LoadLevel(file); + } + + #define CHECKED_READ(x, s) if(!rRead.ReadFullBuffer(x, s, 0)) {return false;} + bool LoadLevel(IOStream &rRead) + { + // Load the restored objects list + mRestoredObjects.clear(); + int64_t numObjects = 0; + CHECKED_READ(&numObjects, sizeof(numObjects)); + for(int64_t o = 0; o < numObjects; ++o) + { + int64_t id; + CHECKED_READ(&id, sizeof(id)); + mRestoredObjects.insert(id); + } + + // ID of next level? + int64_t nextID = 0; + CHECKED_READ(&nextID, sizeof(nextID)); + if(nextID != 0) + { + // Load the next level! + std::string name; + int32_t nsize = 0; + CHECKED_READ(&nsize, sizeof(nsize)); + char n[PATH_MAX]; + if(nsize > PATH_MAX) return false; + CHECKED_READ(n, nsize); + name.assign(n, nsize); + + // Create a new level + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = nextID; + mNextLevelLocalName = name; + + // And ask it to read itself in + if(!mpNextLevel->LoadLevel(rRead)) + { + return false; + } + } + + return true; + } + + // List of objects at this level which have been done already + std::set<int64_t> mRestoredObjects; + // Next level ID + int64_t mNextLevelID; + // Pointer to next level + RestoreResumeInfo *mpNextLevel; + // Local filename of next level + std::string mNextLevelLocalName; +}; + +// parameters structure +typedef struct +{ + bool PrintDots; + bool RestoreDeleted; + bool ContinueAfterErrors; + bool ContinuedAfterError; + std::string mRestoreResumeInfoFilename; + RestoreResumeInfo mResumeInfo; +} RestoreParams; + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestoreDir(BackupProtocolClient &, +// int64_t, const char *, bool) +// Purpose: Restore a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +static int BackupClientRestoreDir(BackupProtocolClient &rConnection, + int64_t DirectoryID, const std::string &rRemoteDirectoryName, + const std::string &rLocalDirectoryName, + RestoreParams &Params, RestoreResumeInfo &rLevel) +{ + // If we're resuming... check that we haven't got a next level to + // look at + if(rLevel.mpNextLevel != 0) + { + // Recurse immediately + std::string localDirname(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + rLevel.mNextLevelLocalName); + BackupClientRestoreDir(rConnection, rLevel.mNextLevelID, + rRemoteDirectoryName + '/' + + rLevel.mNextLevelLocalName, localDirname, + Params, *rLevel.mpNextLevel); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(rLevel.mNextLevelID); + + // Remove the level for the recursed directory + rLevel.RemoveLevel(); + } + + // Create the local directory, if not already done. + // Path and owner set later, just use restrictive owner mode. + + int exists; + + try + { + exists = ObjectExists(rLocalDirectoryName.c_str()); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(std::exception &e) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": unknown error"); + return Restore_UnknownError; + } + + switch(exists) + { + case ObjectExists_Dir: + // Do nothing + break; + case ObjectExists_File: + { + // File exists with this name, which is fun. + // Get rid of it. + BOX_WARNING("File present with name '" << + rLocalDirectoryName << "', removing " + "out of the way of restored directory. " + "Use specific restore with ID to " + "restore this object."); + if(::unlink(rLocalDirectoryName.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete " + "file '" << + rLocalDirectoryName << "'"); + return Restore_UnknownError; + } + BOX_TRACE("In restore, directory name " + "collision with file '" << + rLocalDirectoryName << "'"); + } + break; + case ObjectExists_NoObject: + // we'll create it in a second, after checking + // whether the parent directory exists + break; + default: + ASSERT(false); + break; + } + + std::string parentDirectoryName(rLocalDirectoryName); + if(parentDirectoryName[parentDirectoryName.size() - 1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + parentDirectoryName.resize(parentDirectoryName.size() - 1); + } + + size_t lastSlash = parentDirectoryName.rfind(DIRECTORY_SEPARATOR_ASCHAR); + + if(lastSlash == std::string::npos) + { + // might be a forward slash separator, + // especially in the unit tests! + lastSlash = parentDirectoryName.rfind('/'); + } + + if(lastSlash != std::string::npos) + { + // the target directory is a deep path, remove the last + // directory name and check that the resulting parent + // exists, otherwise the restore should fail. + parentDirectoryName.resize(lastSlash); + + #ifdef WIN32 + // if the path is a drive letter, then we need to + // add a a backslash to query the root directory. + if (lastSlash == 2 && parentDirectoryName[1] == ':') + { + parentDirectoryName += '\\'; + } + else if (lastSlash == 0) + { + parentDirectoryName += '\\'; + } + #endif + + int parentExists; + + try + { + parentExists = ObjectExists(parentDirectoryName.c_str()); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(std::exception &e) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": unknown error"); + return Restore_UnknownError; + } + + switch(parentExists) + { + case ObjectExists_Dir: + // this is fine, do nothing + break; + + case ObjectExists_File: + BOX_ERROR("Failed to restore: '" << + parentDirectoryName << "' " + "is a file, but should be a " + "directory."); + return Restore_TargetPathNotFound; + + case ObjectExists_NoObject: + BOX_ERROR("Failed to restore: parent '" << + parentDirectoryName << "' of target " + "directory does not exist."); + return Restore_TargetPathNotFound; + + default: + BOX_ERROR("Failed to restore: unknown " + "result from ObjectExists('" << + parentDirectoryName << "')"); + return Restore_UnknownError; + } + } + + if((exists == ObjectExists_NoObject || + exists == ObjectExists_File) && + ::mkdir(rLocalDirectoryName.c_str(), S_IRWXU) != 0) + { + BOX_LOG_SYS_ERROR("Failed to create directory '" << + rLocalDirectoryName << "'"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Save the restore info, in case it's needed later + try + { + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Fetch the directory listing from the server -- getting a + // list of files which is appropriate to the restore type + rConnection.QueryListDirectory( + DirectoryID, + Params.RestoreDeleted?(BackupProtocolClientListDirectory::Flags_Deleted):(BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING), + BackupProtocolClientListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolClientListDirectory::Flags_Deleted)), + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(rConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, rConnection.GetTimeout()); + + // Apply attributes to the directory + const StreamableMemBlock &dirAttrBlock(dir.GetAttributes()); + BackupClientFileAttributes dirAttr(dirAttrBlock); + + try + { + dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), true); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': " << e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + int64_t bytesWrittenSinceLastRestoreInfoSave = 0; + + // Process files + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) + != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) + == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localFilename(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + nm.GetClearFilename()); + + // Unlink anything which already exists: + // For resuming restores, we can't overwrite + // files already there. + if(ObjectExists(localFilename) + != ObjectExists_NoObject && + ::unlink(localFilename.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete " + "file '" << localFilename << + "'"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + BOX_TRACE("Restoring file: " << + rRemoteDirectoryName + '/' + + nm.GetClearFilename() << " (" << + en->GetSizeInBlocks() << " blocks)"); + + // Request it from the store + rConnection.QueryGetFile(DirectoryID, + en->GetObjectID()); + + // Stream containing encoded file + std::auto_ptr<IOStream> objectStream( + rConnection.ReceiveStream()); + + // Decode the file -- need to do different + // things depending on whether the directory + // entry has additional attributes + try + { + if(en->HasAttributes()) + { + // Use these attributes + const StreamableMemBlock &storeAttr(en->GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout(), &attr); + } + else + { + // Use attributes stored in file + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout()); + } + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore file '" << + localFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore file '" << + localFilename << + "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Progress display? + if(Params.PrintDots) + { + printf("."); + fflush(stdout); + } + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + + // Save restore info? + int64_t fileSize; + bool exists = false; + + try + { + exists = FileExists( + localFilename.c_str(), + &fileSize, + true /* treat links as not + existing */); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to determine " + "whether file exists: '" << + localFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to determine " + "whether file exists: '" << + localFilename << "': " + "unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + if(exists) + { + // File exists... + bytesWrittenSinceLastRestoreInfoSave + += fileSize; + + if(bytesWrittenSinceLastRestoreInfoSave > MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES) + { + // Save the restore info, in + // case it's needed later + try + { + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + return Restore_UnknownError; + } + + bytesWrittenSinceLastRestoreInfoSave = 0; + } + } + } + } + } + + // Make sure the restore info has been saved + if(bytesWrittenSinceLastRestoreInfoSave != 0) + { + // Save the restore info, in case it's needed later + try + { + Params.mResumeInfo.Save( + Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << "': " << + e.what()); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + bytesWrittenSinceLastRestoreInfoSave = 0; + } + + + // Recurse to directories + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) + != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) + == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localDirname(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + nm.GetClearFilename()); + + // Add the level for the next entry + RestoreResumeInfo &rnextLevel( + rLevel.AddLevel(en->GetObjectID(), + nm.GetClearFilename())); + + // Recurse + + BOX_TRACE("Entering directory: " << + rRemoteDirectoryName + '/' + + nm.GetClearFilename()); + + int result = BackupClientRestoreDir( + rConnection, en->GetObjectID(), + rRemoteDirectoryName + '/' + + nm.GetClearFilename(), localDirname, + Params, rnextLevel); + + if (result != Restore_Complete) + { + return result; + } + + // Remove the level for the above call + rLevel.RemoveLevel(); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + } + } + } + + // now remove the user writable flag, if we added it earlier + try + { + dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), false); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': " << e.what()); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': unknown error"); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + return Restore_Complete; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestore(BackupProtocolClient &, int64_t, +// const char *, bool, bool, bool, bool, bool) +// Purpose: Restore a directory on the server to a local +// directory on the disc. The local directory must not +// already exist. +// +// If a restore is aborted for any reason, then it may +// be resumed if Resume == true. If Resume == false +// and resumption is possible, then +// Restore_ResumePossible is returned. +// +// Set RestoreDeleted to restore a deleted directory. +// This may not give the directory structure when it +// was deleted, because files may have been deleted +// within it before it was deleted. +// +// Returns Restore_TargetExists if the target +// directory exists, but there is no restore possible. +// (Won't attempt to overwrite things.) +// +// Returns Restore_Complete on success. (Exceptions +// on error, unless ContinueAfterError is true and +// the error is recoverable, in which case it returns +// Restore_CompleteWithErrors) +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +int BackupClientRestore(BackupProtocolClient &rConnection, + int64_t DirectoryID, const char *RemoteDirectoryName, + const char *LocalDirectoryName, bool PrintDots, bool RestoreDeleted, + bool UndeleteAfterRestoreDeleted, bool Resume, + bool ContinueAfterErrors) +{ + // Parameter block + RestoreParams params; + params.PrintDots = PrintDots; + params.RestoreDeleted = RestoreDeleted; + params.ContinueAfterErrors = ContinueAfterErrors; + params.ContinuedAfterError = false; + params.mRestoreResumeInfoFilename = LocalDirectoryName; + params.mRestoreResumeInfoFilename += ".boxbackupresume"; + + // Target exists? + int targetExistance = ObjectExists(LocalDirectoryName); + + // Does any resumption information exist? + bool doingResume = false; + if(FileExists(params.mRestoreResumeInfoFilename.c_str()) && + targetExistance == ObjectExists_Dir) + { + if(!Resume) + { + // Caller didn't specify that resume should be done, + // so refuse to do it but say why. + return Restore_ResumePossible; + } + + // Attempt to load the resume info file + if(!params.mResumeInfo.Load(params.mRestoreResumeInfoFilename)) + { + // failed -- bad file, so things have gone a bit wrong + return Restore_TargetExists; + } + + // Flag as doing resume so next check isn't actually performed + doingResume = true; + } + + // Does the directory already exist? + if(targetExistance != ObjectExists_NoObject && !doingResume) + { + // Don't do anything in this case! + return Restore_TargetExists; + } + + // Restore the directory + int result = BackupClientRestoreDir(rConnection, DirectoryID, + RemoteDirectoryName, LocalDirectoryName, params, + params.mResumeInfo); + if (result != Restore_Complete) + { + return result; + } + + // Undelete the directory on the server? + if(RestoreDeleted && UndeleteAfterRestoreDeleted) + { + // Send the command + rConnection.QueryUndeleteDirectory(DirectoryID); + } + + // Finish progress display? + if(PrintDots) + { + printf("\n"); + fflush(stdout); + } + + // Delete the resume information file + ::unlink(params.mRestoreResumeInfoFilename.c_str()); + + return params.ContinuedAfterError ? Restore_CompleteWithErrors + : Restore_Complete; +} + diff --git a/lib/backupclient/BackupClientRestore.h b/lib/backupclient/BackupClientRestore.h new file mode 100644 index 00000000..311a15bd --- /dev/null +++ b/lib/backupclient/BackupClientRestore.h @@ -0,0 +1,36 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientRestore.h +// Purpose: Functions to restore files from a backup store +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSCLIENTRESTORE_H +#define BACKUPSCLIENTRESTORE__H + +class BackupProtocolClient; + +enum +{ + Restore_Complete = 0, + Restore_ResumePossible, + Restore_TargetExists, + Restore_TargetPathNotFound, + Restore_UnknownError, + Restore_CompleteWithErrors, +}; + +int BackupClientRestore(BackupProtocolClient &rConnection, + int64_t DirectoryID, + const char *RemoteDirectoryName, + const char *LocalDirectoryName, + bool PrintDots = false, + bool RestoreDeleted = false, + bool UndeleteAfterRestoreDeleted = false, + bool Resume = false, + bool ContinueAfterErrors = false); + +#endif // BACKUPSCLIENTRESTORE__H + diff --git a/lib/backupclient/BackupDaemonConfigVerify.cpp b/lib/backupclient/BackupDaemonConfigVerify.cpp new file mode 100644 index 00000000..dfef5b03 --- /dev/null +++ b/lib/backupclient/BackupDaemonConfigVerify.cpp @@ -0,0 +1,132 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonConfigVerify.cpp +// Purpose: Configuration file definition for bbackupd +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupDaemonConfigVerify.h" +#include "Daemon.h" +#include "BoxPortsAndFiles.h" + +#include "MemLeakFindOn.h" + + +static const ConfigurationVerifyKey backuplocationkeys[] = +{ + ConfigurationVerifyKey("ExcludeFile", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeFilesRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeDir", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeDirsRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeFile", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeFilesRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeDir", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeDirsRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("Path", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +static const ConfigurationVerify backuplocations[] = +{ + { + "*", + 0, + backuplocationkeys, + ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyserverkeys[] = +{ + DAEMON_VERIFY_SERVER_KEYS +}; + +static const ConfigurationVerify verifyserver[] = +{ + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists, + 0 + }, + { + "BackupLocations", + backuplocations, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyrootkeys[] = +{ + ConfigurationVerifyKey("AccountNumber", + ConfigTest_Exists | ConfigTest_IsUint32), + ConfigurationVerifyKey("UpdateStoreInterval", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("MinimumFileAge", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("MaxUploadWait", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("MaxFileTimeInFuture", ConfigTest_IsInt, 172800), + // file is uploaded if the file is this much in the future + // (2 days default) + ConfigurationVerifyKey("AutomaticBackup", ConfigTest_IsBool, true), + + ConfigurationVerifyKey("SyncAllowScript", 0), + // script that returns "now" if backup is allowed now, or a number + // of seconds to wait before trying again if not + + ConfigurationVerifyKey("MaximumDiffingTime", ConfigTest_IsInt), + ConfigurationVerifyKey("DeleteRedundantLocationsAfter", + ConfigTest_IsInt, 172800), + + ConfigurationVerifyKey("FileTrackingSizeThreshold", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("DiffingUploadSizeThreshold", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("StoreHostname", ConfigTest_Exists), + ConfigurationVerifyKey("StorePort", ConfigTest_IsInt, + BOX_PORT_BBSTORED), + ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), + // extended log to syslog + ConfigurationVerifyKey("ExtendedLogFile", 0), + // extended log to a file + ConfigurationVerifyKey("LogAllFileAccess", ConfigTest_IsBool, false), + // enable logging reasons why each file is backed up or not + ConfigurationVerifyKey("LogFile", 0), + // enable logging to a file + ConfigurationVerifyKey("LogFileLevel", 0), + // set the level of verbosity of file logging + ConfigurationVerifyKey("CommandSocket", 0), + // not compulsory to have this + ConfigurationVerifyKey("KeepAliveTime", ConfigTest_IsInt), + ConfigurationVerifyKey("StoreObjectInfoFile", 0), + // optional + + ConfigurationVerifyKey("NotifyScript", 0), + // optional script to run when backup needs attention, eg store full + + ConfigurationVerifyKey("NotifyAlways", ConfigTest_IsBool, false), + // option to disable the suppression of duplicate notifications + + ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists), + ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists), + ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists), + ConfigurationVerifyKey("KeysFile", ConfigTest_Exists), + ConfigurationVerifyKey("DataDirectory", + ConfigTest_Exists | ConfigTest_LastEntry), +}; + +const ConfigurationVerify BackupDaemonConfigVerify = +{ + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; diff --git a/lib/backupclient/BackupDaemonConfigVerify.h b/lib/backupclient/BackupDaemonConfigVerify.h new file mode 100644 index 00000000..fefa703c --- /dev/null +++ b/lib/backupclient/BackupDaemonConfigVerify.h @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonConfigVerify.h +// Purpose: Configuration file definition for bbackupd +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMONCONFIGVERIFY__H +#define BACKUPDAEMONCONFIGVERIFY__H + +#include "Configuration.h" + +extern const ConfigurationVerify BackupDaemonConfigVerify; + +#endif // BACKUPDAEMONCONFIGVERIFY__H + diff --git a/lib/backupclient/BackupStoreConstants.h b/lib/backupclient/BackupStoreConstants.h new file mode 100644 index 00000000..2c33fd8f --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreDirectory.cpp b/lib/backupclient/BackupStoreDirectory.cpp new file mode 100644 index 00000000..0d06da34 --- /dev/null +++ b/lib/backupclient/BackupStoreDirectory.cpp @@ -0,0 +1,568 @@ +// -------------------------------------------------------------------------- +// +// 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 yeild 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(BackupStoreException, BadDirectoryFormat) + } + + // 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, box_time_t AttributesModTime) +{ + Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags, AttributesModTime); + 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/backupclient/BackupStoreDirectory.h b/lib/backupclient/BackupStoreDirectory.h new file mode 100644 index 00000000..958eee81 --- /dev/null +++ b/lib/backupclient/BackupStoreDirectory.h @@ -0,0 +1,268 @@ +// -------------------------------------------------------------------------- +// +// 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" + + 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, box_time_t AttributesModTime); + 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 AddUnattactedObject(const BackupStoreFilename &rName, box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags); + bool NameInUse(const BackupStoreFilename &rName); + // Don't use these functions in normal code! + + // For testing + void TESTONLY_SetObjectID(int64_t ObjectID) {mObjectID = ObjectID;} + + // Debug and diagonistics + 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/backupclient/BackupStoreException.h b/lib/backupclient/BackupStoreException.h new file mode 100644 index 00000000..981dfa60 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreException.txt b/lib/backupclient/BackupStoreException.txt new file mode 100644 index 00000000..528a8c94 --- /dev/null +++ b/lib/backupclient/BackupStoreException.txt @@ -0,0 +1,71 @@ +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. diff --git a/lib/backupclient/BackupStoreFile.cpp b/lib/backupclient/BackupStoreFile.cpp new file mode 100644 index 00000000..17e145a3 --- /dev/null +++ b/lib/backupclient/BackupStoreFile.cpp @@ -0,0 +1,1556 @@ +// -------------------------------------------------------------------------- +// +// 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<IOStream> BackupStoreFile::EncodeFile(const char *Filename, + int64_t ContainerID, const BackupStoreFilename &rStoreFilename, + int64_t *pModificationTime, ReadLoggingStream::Logger* pLogger, + RunStatusProvider* pRunStatusProvider) +{ + // Create the stream + std::auto_ptr<IOStream> stream(new BackupStoreFileEncodeStream); + + // Do the initial setup + ((BackupStoreFileEncodeStream*)stream.get())->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/backupclient/BackupStoreFile.h b/lib/backupclient/BackupStoreFile.h new file mode 100644 index 00000000..f4c60919 --- /dev/null +++ b/lib/backupclient/BackupStoreFile.h @@ -0,0 +1,228 @@ +// -------------------------------------------------------------------------- +// +// 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" +#include "RunStatusProvider.h" + +typedef struct +{ + int64_t mBytesInEncodedFiles; + int64_t mBytesAlreadyOnServer; + int64_t mTotalFileStreamSize; +} BackupStoreFileStats; + +// 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 +// 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<IOStream> EncodeFile(const char *Filename, + int64_t ContainerID, const BackupStoreFilename &rStoreFilename, + int64_t *pModificationTime = 0, + ReadLoggingStream::Logger* pLogger = NULL, + RunStatusProvider* pRunStatusProvider = NULL); + static std::auto_ptr<IOStream> EncodeFileDiff + ( + const char *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(unsigned long) >= sizeof(void*)); // make sure casting the right pointer size + uint8_t adjustment = BACKUPSTOREFILE_CODING_BLOCKSIZE + - (uint8_t)(((unsigned long)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(unsigned long) >= sizeof(void*)); // make sure casting the right pointer size + ASSERT((uint8_t)(((unsigned long)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/backupclient/BackupStoreFileCmbDiff.cpp b/lib/backupclient/BackupStoreFileCmbDiff.cpp new file mode 100644 index 00000000..1a88fa3f --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileCmbIdx.cpp b/lib/backupclient/BackupStoreFileCmbIdx.cpp new file mode 100644 index 00000000..c8bcc3b9 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileCombine.cpp b/lib/backupclient/BackupStoreFileCombine.cpp new file mode 100644 index 00000000..baa331f0 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileCryptVar.cpp b/lib/backupclient/BackupStoreFileCryptVar.cpp new file mode 100644 index 00000000..e826de4e --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileCryptVar.h b/lib/backupclient/BackupStoreFileCryptVar.h new file mode 100644 index 00000000..566813c8 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileDiff.cpp b/lib/backupclient/BackupStoreFileDiff.cpp new file mode 100644 index 00000000..5705c3aa --- /dev/null +++ b/lib/backupclient/BackupStoreFileDiff.cpp @@ -0,0 +1,1046 @@ +// -------------------------------------------------------------------------- +// +// 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<IOStream> BackupStoreFile::EncodeFileDiff +( + const char *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, &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<IOStream> stream(new BackupStoreFileEncodeStream); + + // Do the initial setup + ((BackupStoreFileEncodeStream*)stream.get())->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(), + "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/backupclient/BackupStoreFileEncodeStream.cpp b/lib/backupclient/BackupStoreFileEncodeStream.cpp new file mode 100644 index 00000000..54c2463d --- /dev/null +++ b/lib/backupclient/BackupStoreFileEncodeStream.cpp @@ -0,0 +1,715 @@ +// -------------------------------------------------------------------------- +// +// 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), + 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 char *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); + + // 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/backupclient/BackupStoreFileEncodeStream.h b/lib/backupclient/BackupStoreFileEncodeStream.h new file mode 100644 index 00000000..c5fa780a --- /dev/null +++ b/lib/backupclient/BackupStoreFileEncodeStream.h @@ -0,0 +1,135 @@ +// -------------------------------------------------------------------------- +// +// 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 char *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(); + +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 + // 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/backupclient/BackupStoreFileRevDiff.cpp b/lib/backupclient/BackupStoreFileRevDiff.cpp new file mode 100644 index 00000000..509eef61 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFileWire.h b/lib/backupclient/BackupStoreFileWire.h new file mode 100644 index 00000000..49e94aa5 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFilename.cpp b/lib/backupclient/BackupStoreFilename.cpp new file mode 100644 index 00000000..72cd1acd --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFilename.h b/lib/backupclient/BackupStoreFilename.h new file mode 100644 index 00000000..80db9516 --- /dev/null +++ b/lib/backupclient/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/backupclient/BackupStoreFilenameClear.cpp b/lib/backupclient/BackupStoreFilenameClear.cpp new file mode 100644 index 00000000..e529d8d3 --- /dev/null +++ b/lib/backupclient/BackupStoreFilenameClear.cpp @@ -0,0 +1,335 @@ +// -------------------------------------------------------------------------- +// +// 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; +} +#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_TRACE("**** 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/backupclient/BackupStoreFilenameClear.h b/lib/backupclient/BackupStoreFilenameClear.h new file mode 100644 index 00000000..d4c45701 --- /dev/null +++ b/lib/backupclient/BackupStoreFilenameClear.h @@ -0,0 +1,60 @@ +// -------------------------------------------------------------------------- +// +// 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; +#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/backupclient/BackupStoreObjectDump.cpp b/lib/backupclient/BackupStoreObjectDump.cpp new file mode 100644 index 00000000..654317c1 --- /dev/null +++ b/lib/backupclient/BackupStoreObjectDump.cpp @@ -0,0 +1,227 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreObjectDump.cpp +// Purpose: Implementations of dumping objects to stdout/TRACE +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <stdarg.h> +#include <map> + +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileWire.h" +#include "autogen_BackupStoreException.h" +#include "BackupStoreFilename.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreObjectMagic.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void OutputLine(FILE *, bool, const char *, ...) +// Purpose: Output a line for the object dumping, to file and/or trace... +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +static void OutputLine(FILE *file, bool ToTrace, const char *format, ...) +{ + char text[512]; + int r = 0; + va_list ap; + va_start(ap, format); + r = vsnprintf(text, sizeof(text), format, ap); + va_end(ap); + + if(file != 0) + { + ::fprintf(file, "%s", text); + } + if(ToTrace) + { + BOX_TRACE(text); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) +// Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) +// Dump the contents to a file, or trace. +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +void BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) +{ + FILE *file = (FILE*)clibFileHandle; + + OutputLine(file, ToTrace, "Directory object.\nObject ID: %llx\nContainer ID: %llx\nNumber entries: %d\n"\ + "Attributes mod time: %llx\nAttributes size: %d\n", mObjectID, mContainerID, mEntries.size(), + mAttributesModTime, mAttributes.GetSize()); + + // So repeated filenames can be illustrated, even though they can't be decoded + std::map<std::string, int> nameNum; + int nameNumI = 0; + + // Dump items + OutputLine(file, ToTrace, "Items:\nID Size AttrHash AtSz NSz NIdx Flags\n"); + for(std::vector<Entry*>::const_iterator i(mEntries.begin()); i != mEntries.end(); ++i) + { + // Choose file name index number for this file + std::map<std::string, int>::iterator nn(nameNum.find((*i)->GetName().GetEncodedFilename())); + int ni = nameNumI; + if(nn != nameNum.end()) + { + ni = nn->second; + } + else + { + nameNum[(*i)->GetName().GetEncodedFilename()] = nameNumI; + ++nameNumI; + } + + // Do dependencies + char depends[128]; + depends[0] = '\0'; + int depends_l = 0; + if((*i)->GetDependsNewer() != 0) + { +#ifdef _MSC_VER + depends_l += ::sprintf(depends + depends_l, " depNew(%I64x)", (*i)->GetDependsNewer()); +#else + depends_l += ::sprintf(depends + depends_l, " depNew(%llx)", (long long)((*i)->GetDependsNewer())); +#endif + } + if((*i)->GetDependsOlder() != 0) + { +#ifdef _MSC_VER + depends_l += ::sprintf(depends + depends_l, " depOld(%I64x)", (*i)->GetDependsOlder()); +#else + depends_l += ::sprintf(depends + depends_l, " depOld(%llx)", (long long)((*i)->GetDependsOlder())); +#endif + } + + // Output item + int16_t f = (*i)->GetFlags(); +#ifdef WIN32 + OutputLine(file, ToTrace, + "%06I64x %4I64d %016I64x %4d %3d %4d%s%s%s%s%s%s\n", +#else + OutputLine(file, ToTrace, + "%06llx %4lld %016llx %4d %3d %4d%s%s%s%s%s%s\n", +#endif + (*i)->GetObjectID(), + (*i)->GetSizeInBlocks(), + (*i)->GetAttributesHash(), + (*i)->GetAttributes().GetSize(), + (*i)->GetName().GetEncodedFilename().size(), + ni, + ((f & BackupStoreDirectory::Entry::Flags_File)?" file":""), + ((f & BackupStoreDirectory::Entry::Flags_Dir)?" dir":""), + ((f & BackupStoreDirectory::Entry::Flags_Deleted)?" del":""), + ((f & BackupStoreDirectory::Entry::Flags_OldVersion)?" old":""), + ((f & BackupStoreDirectory::Entry::Flags_RemoveASAP)?" removeASAP":""), + depends); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreFile::DumpFile(void *, bool, IOStream &) +// Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) +// Dump the contents to a file, or trace. +// Created: 4/5/04 +// +// -------------------------------------------------------------------------- +void BackupStoreFile::DumpFile(void *clibFileHandle, bool ToTrace, IOStream &rFile) +{ + FILE *file = (FILE*)clibFileHandle; + + // Read header + file_StreamFormat hdr; + if(!rFile.ReadFullBuffer(&hdr, sizeof(hdr), + 0 /* not interested in bytes read if this fails */, IOStream::TimeOutInfinite)) + { + // Couldn't read header + THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) + } + + // Check and output header info + if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V1) + && hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V0)) + { + OutputLine(file, ToTrace, "File header doesn't have the correct magic, aborting dump\n"); + return; + } + + OutputLine(file, ToTrace, "File object.\nContainer ID: %llx\nModification time: %llx\n"\ + "Max block clear size: %d\nOptions: %08x\nNum blocks: %d\n", box_ntoh64(hdr.mContainerID), + box_ntoh64(hdr.mModificationTime), ntohl(hdr.mMaxBlockClearSize), ntohl(hdr.mOptions), + box_ntoh64(hdr.mNumBlocks)); + + // Read the next two objects + BackupStoreFilename fn; + fn.ReadFromStream(rFile, IOStream::TimeOutInfinite); + OutputLine(file, ToTrace, "Filename size: %d\n", + fn.GetEncodedFilename().size()); + + BackupClientFileAttributes attr; + attr.ReadFromStream(rFile, IOStream::TimeOutInfinite); + OutputLine(file, ToTrace, "Attributes size: %d\n", attr.GetSize()); + + // Dump the blocks + rFile.Seek(0, IOStream::SeekType_Absolute); + BackupStoreFile::MoveStreamPositionToBlockIndex(rFile); + + // Read in header + file_BlockIndexHeader bhdr; + rFile.ReadFullBuffer(&bhdr, sizeof(bhdr), 0); + if(bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) + && bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0)) + { + OutputLine(file, ToTrace, "WARNING: Block header doesn't have the correct magic\n"); + } + // number of blocks + int64_t nblocks = box_ntoh64(bhdr.mNumBlocks); + OutputLine(file, ToTrace, "Other file ID (for block refs): %llx\nNum blocks (in blk hdr): %lld\n", + box_ntoh64(bhdr.mOtherFileID), nblocks); + + // Dump info about each block + OutputLine(file, ToTrace, "======== ===== ==========\n Index Where EncSz/Idx\n"); + int64_t nnew = 0, nold = 0; + for(int64_t b = 0; b < nblocks; ++b) + { + file_BlockIndexEntry en; + if(!rFile.ReadFullBuffer(&en, sizeof(en), 0)) + { + OutputLine(file, ToTrace, "Didn't manage to read block %lld from file\n", b); + continue; + } + int64_t s = box_ntoh64(en.mEncodedSize); + if(s > 0) + { + nnew++; + BOX_TRACE(std::setw(8) << b << " this s=" << + std::setw(8) << s); + } + else + { + nold++; + BOX_TRACE(std::setw(8) << b << " other i=" << + std::setw(8) << 0 - s); + } + } + BOX_TRACE("======== ===== =========="); +} + diff --git a/lib/backupclient/BackupStoreObjectMagic.h b/lib/backupclient/BackupStoreObjectMagic.h new file mode 100644 index 00000000..7ee600a2 --- /dev/null +++ b/lib/backupclient/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/backupclient/Makefile.extra b/lib/backupclient/Makefile.extra new file mode 100644 index 00000000..df3319df --- /dev/null +++ b/lib/backupclient/Makefile.extra @@ -0,0 +1,16 @@ + +MAKEPROTOCOL = ../../lib/server/makeprotocol.pl + +GEN_CMD_SRV = $(MAKEPROTOCOL) Client ../../bin/bbstored/backupprotocol.txt + +# AUTOGEN SEEDING +autogen_BackupProtocolClient.cpp autogen_BackupProtocolClient.h: $(MAKEPROTOCOL) ../../bin/bbstored/backupprotocol.txt + $(_PERL) $(GEN_CMD_SRV) + + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_BackupStoreException.h autogen_BackupStoreException.cpp: $(MAKEEXCEPTION) BackupStoreException.txt + $(_PERL) $(MAKEEXCEPTION) BackupStoreException.txt + diff --git a/lib/backupclient/RunStatusProvider.h b/lib/backupclient/RunStatusProvider.h new file mode 100644 index 00000000..89f361ca --- /dev/null +++ b/lib/backupclient/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/BackupStoreAccountDatabase.cpp b/lib/backupstore/BackupStoreAccountDatabase.cpp new file mode 100644 index 00000000..72a813d5 --- /dev/null +++ b/lib/backupstore/BackupStoreAccountDatabase.cpp @@ -0,0 +1,373 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccountDatabase.cpp +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <string> +#include <map> +#include <stdio.h> +#include <sys/stat.h> + +#include "BackupStoreAccountDatabase.h" +#include "Guards.h" +#include "FdGetLine.h" +#include "BackupStoreException.h" +#include "CommonException.h" +#include "FileModificationTime.h" + +#include "MemLeakFindOn.h" + +class _BackupStoreAccountDatabase +{ +public: + std::string mFilename; + std::map<int32_t, BackupStoreAccountDatabase::Entry> mDatabase; + box_time_t mModificationTime; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *) +// Purpose: Constructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *Filename) + : pImpl(new _BackupStoreAccountDatabase) +{ + pImpl->mFilename = Filename; + pImpl->mModificationTime = 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::~BackupStoreAccountDatabase() +// Purpose: Destructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::~BackupStoreAccountDatabase() +{ + delete pImpl; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry() +// Purpose: Default constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry() + : mID(-1), + mDiscSet(-1) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry(int32_t, int) +// Purpose: Constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry(int32_t ID, int DiscSet) + : mID(ID), + mDiscSet(DiscSet) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry(const Entry &) +// Purpose: Copy constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry(const Entry &rEntry) + : mID(rEntry.mID), + mDiscSet(rEntry.mDiscSet) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::~Entry() +// Purpose: Destructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::~Entry() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Read(const char *) +// Purpose: Read in a database from disc +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreAccountDatabase> BackupStoreAccountDatabase::Read(const char *Filename) +{ + // Database object to use + std::auto_ptr<BackupStoreAccountDatabase> db(new BackupStoreAccountDatabase(Filename)); + + // Read in the file + db->ReadFile(); + + // Return to called + return db; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::ReadFile() +// Purpose: Read the file off disc +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::ReadFile() const +{ + // Open file + FileHandleGuard<> file(pImpl->mFilename.c_str()); + + // Clear existing entries + pImpl->mDatabase.clear(); + + // Read in lines + FdGetLine getLine(file); + + while(!getLine.IsEOF()) + { + // Read and split up line + std::string l(getLine.GetLine(true)); + + if(!l.empty()) + { + // Check... + int32_t id; + int discSet; + if(::sscanf(l.c_str(), "%x:%d", &id, &discSet) != 2) + { + THROW_EXCEPTION(BackupStoreException, BadAccountDatabaseFile) + } + + // Make a new entry + pImpl->mDatabase[id] = Entry(id, discSet); + } + } + + // Store the modification time of the file + pImpl->mModificationTime = GetDBFileModificationTime(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::CheckUpToDate() +// Purpose: Private. Ensure that the in memory database matches the one on disc +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::CheckUpToDate() const +{ + if(pImpl->mModificationTime != GetDBFileModificationTime()) + { + // File has changed -- load it in again + ReadFile(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetDBFileModificationTime() +// Purpose: Get the current modification time of the database +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +box_time_t BackupStoreAccountDatabase::GetDBFileModificationTime() const +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(pImpl->mFilename.c_str(), &st) == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + + return FileModificationTime(st); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Write() +// Purpose: Write the database back to disc after modifying it +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::Write() +{ + // Open file for writing + // Would use this... + // FileHandleGuard<O_WRONLY | O_TRUNC> file(pImpl->mFilename.c_str()); + // but gcc fails randomly on it on some platforms. Weird. + + int file = ::open(pImpl->mFilename.c_str(), O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(file == -1) + { + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + + try + { + // Then write each entry + for(std::map<int32_t, BackupStoreAccountDatabase::Entry>::const_iterator i(pImpl->mDatabase.begin()); + i != pImpl->mDatabase.end(); ++i) + { + // Write out the entry + char line[256]; // more than enough for a couple of integers in string form + int s = ::sprintf(line, "%x:%d\n", i->second.GetID(), i->second.GetDiscSet()); + if(::write(file, line, s) != s) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } + + ::close(file); + } + catch(...) + { + ::close(file); + throw; + } + + // Done. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::EntryExists(int32_t) +// Purpose: Does an entry exist in the database? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool BackupStoreAccountDatabase::EntryExists(int32_t ID) const +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + return pImpl->mDatabase.find(ID) != pImpl->mDatabase.end(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetEntry(int32_t) +// Purpose: Retrieve an entry +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::GetEntry( + int32_t ID) const +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::const_iterator i(pImpl->mDatabase.find(ID)); + if(i == pImpl->mDatabase.end()) + { + THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) + } + + return i->second; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::AddEntry(int32_t, int) +// Purpose: Add a new entry to the database +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::AddEntry( + int32_t ID, int DiscSet) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + pImpl->mDatabase[ID] = Entry(ID, DiscSet); + return pImpl->mDatabase[ID]; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::DeleteEntry(int32_t) +// Purpose: Delete an entry from the database +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::DeleteEntry(int32_t ID) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::iterator i(pImpl->mDatabase.find(ID)); + if(i == pImpl->mDatabase.end()) + { + THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) + } + + pImpl->mDatabase.erase(i); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetAllAccountIDs(std::vector<int32_t>) +// Purpose: +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::GetAllAccountIDs(std::vector<int32_t> &rIDsOut) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + // Delete everything in the output list + rIDsOut.clear(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::iterator i(pImpl->mDatabase.begin()); + for(; i != pImpl->mDatabase.end(); ++i) + { + rIDsOut.push_back(i->first); + } +} + + + + diff --git a/lib/backupstore/BackupStoreAccountDatabase.h b/lib/backupstore/BackupStoreAccountDatabase.h new file mode 100644 index 00000000..79573242 --- /dev/null +++ b/lib/backupstore/BackupStoreAccountDatabase.h @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccountDatabase.h +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREACCOUNTDATABASE__H +#define BACKUPSTOREACCOUNTDATABASE__H + +#include <memory> +#include <vector> + +#include "BoxTime.h" + +class _BackupStoreAccountDatabase; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreAccountDatabase +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +class BackupStoreAccountDatabase +{ +public: + friend class _BackupStoreAccountDatabase; // to stop compiler warnings + ~BackupStoreAccountDatabase(); +private: + BackupStoreAccountDatabase(const char *Filename); + BackupStoreAccountDatabase(const BackupStoreAccountDatabase &); +public: + + static std::auto_ptr<BackupStoreAccountDatabase> Read(const char *Filename); + void Write(); + + class Entry + { + public: + Entry(); + Entry(int32_t ID, int DiscSet); + Entry(const Entry &rEntry); + ~Entry(); + + int32_t GetID() const {return mID;} + int GetDiscSet() const {return mDiscSet;} + + private: + int32_t mID; + int mDiscSet; + }; + + bool EntryExists(int32_t ID) const; + Entry GetEntry(int32_t ID) const; + Entry AddEntry(int32_t ID, int DiscSet); + void DeleteEntry(int32_t ID); + + // This interface should change in the future. But for now it'll do. + void GetAllAccountIDs(std::vector<int32_t> &rIDsOut); + +private: + void ReadFile() const; // const in concept only + void CheckUpToDate() const; // const in concept only + box_time_t GetDBFileModificationTime() const; + +private: + mutable _BackupStoreAccountDatabase *pImpl; +}; + +#endif // BACKUPSTOREACCOUNTDATABASE__H + diff --git a/lib/backupstore/BackupStoreAccounts.cpp b/lib/backupstore/BackupStoreAccounts.cpp new file mode 100644 index 00000000..5c7e4d38 --- /dev/null +++ b/lib/backupstore/BackupStoreAccounts.cpp @@ -0,0 +1,170 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccounts.cpp +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> + +#include "BoxPortsAndFiles.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreRefCountDatabase.h" +#include "RaidFileWrite.h" +#include "BackupStoreInfo.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreConstants.h" +#include "UnixUser.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &) +// Purpose: Constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase) + : mrDatabase(rDatabase) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::~BackupStoreAccounts() +// Purpose: Destructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +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. +// If rAsUsername is not empty, then the account information will be written under the +// username specified. +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccounts::Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername) +{ + // Create the entry in the database + BackupStoreAccountDatabase::Entry Entry(mrDatabase.AddEntry(ID, + DiscSet)); + + { + // Become the user specified in the config file? + std::auto_ptr<UnixUser> user; + if(!rAsUsername.empty()) + { + // Username specified, change... + user.reset(new UnixUser(rAsUsername.c_str())); + user->ChangeProcessUser(true /* temporary */); + // Change will be undone at the end of this function + } + + // Get directory name + std::string dirName(MakeAccountRootDir(ID, DiscSet)); + + // Create a directory on disc + RaidFileWrite::CreateDirectory(DiscSet, dirName, true /* recursive */); + + // Create an info file + BackupStoreInfo::CreateNew(ID, dirName, DiscSet, SizeSoftLimit, SizeHardLimit); + + // And an empty directory + BackupStoreDirectory rootDir(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); + int64_t rootDirSize = 0; + // Write it, knowing the directory scheme + { + RaidFileWrite rf(DiscSet, dirName + "o01"); + rf.Open(); + rootDir.WriteToStream(rf); + rootDirSize = rf.GetDiscUsageInBlocks(); + rf.Commit(true); + } + + // Update the store info to reflect the size of the root directory + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, dirName, DiscSet, false /* ReadWrite */)); + info->ChangeBlocksUsed(rootDirSize); + info->ChangeBlocksInDirectories(rootDirSize); + + // Save it back + info->Save(); + + // Create the refcount database + BackupStoreRefCountDatabase::CreateNew(Entry); + std::auto_ptr<BackupStoreRefCountDatabase> refcount( + BackupStoreRefCountDatabase::Load(Entry, false)); + refcount->AddReference(BACKUPSTORE_ROOT_DIRECTORY_ID); + } + + // As the original user... + // Write the database back + mrDatabase.Write(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::GetAccountRoot(int32_t, std::string &, int &) +// Purpose: Gets the root of an account, returning the info via references +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccounts::GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const +{ + // Find the account + const BackupStoreAccountDatabase::Entry &en(mrDatabase.GetEntry(ID)); + + rRootDirOut = MakeAccountRootDir(ID, en.GetDiscSet()); + rDiscSetOut = en.GetDiscSet(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::MakeAccountRootDir(int32_t, int) +// Purpose: Private. Generates a root directory name for the account +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +std::string BackupStoreAccounts::MakeAccountRootDir(int32_t ID, int DiscSet) +{ + char accid[64]; // big enough! + ::sprintf(accid, "%08x" DIRECTORY_SEPARATOR, ID); + return std::string(std::string(BOX_RAIDFILE_ROOT_BBSTORED + DIRECTORY_SEPARATOR) + accid); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::AccountExists(int32_t) +// Purpose: Does an account exist? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool BackupStoreAccounts::AccountExists(int32_t ID) +{ + return mrDatabase.EntryExists(ID); +} + + diff --git a/lib/backupstore/BackupStoreAccounts.h b/lib/backupstore/BackupStoreAccounts.h new file mode 100644 index 00000000..224d7353 --- /dev/null +++ b/lib/backupstore/BackupStoreAccounts.h @@ -0,0 +1,52 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccounts.h +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREACCOUNTS__H +#define BACKUPSTOREACCOUNTS__H + +#include <string> + +#include "BackupStoreAccountDatabase.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreAccounts +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +class BackupStoreAccounts +{ +public: + BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase); + ~BackupStoreAccounts(); +private: + BackupStoreAccounts(const BackupStoreAccounts &rToCopy); + +public: + void Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername); + + bool AccountExists(int32_t ID); + void GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const; + static std::string GetAccountRoot(const + BackupStoreAccountDatabase::Entry &rEntry) + { + return MakeAccountRootDir(rEntry.GetID(), rEntry.GetDiscSet()); + } + +private: + static std::string MakeAccountRootDir(int32_t ID, int DiscSet); + +private: + BackupStoreAccountDatabase &mrDatabase; +}; + +#endif // BACKUPSTOREACCOUNTS__H + diff --git a/lib/backupstore/BackupStoreCheck.cpp b/lib/backupstore/BackupStoreCheck.cpp new file mode 100644 index 00000000..7598094e --- /dev/null +++ b/lib/backupstore/BackupStoreCheck.cpp @@ -0,0 +1,776 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck.cpp +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "BackupStoreCheck.h" +#include "StoreStructure.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "autogen_BackupStoreException.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreFile.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreConstants.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::BackupStoreCheck(const std::string &, int, int32_t, bool, bool) +// Purpose: Constructor +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet) + : mStoreRoot(rStoreRoot), + mDiscSetNumber(DiscSetNumber), + mAccountID(AccountID), + mFixErrors(FixErrors), + mQuiet(Quiet), + mNumberErrorsFound(0), + mLastIDInInfo(0), + mpInfoLastBlock(0), + mInfoLastBlockEntries(0), + mLostDirNameSerial(0), + mLostAndFoundDirectoryID(0), + mBlocksUsed(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0), + mBlocksInDirectories(0) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::~BackupStoreCheck() +// Purpose: Destructor +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::~BackupStoreCheck() +{ + // Clean up + FreeInfo(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::Check() +// Purpose: Perform the check on the given account +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::Check() +{ + // Lock the account + { + std::string writeLockFilename; + StoreStructure::MakeWriteLockFilename(mStoreRoot, mDiscSetNumber, writeLockFilename); + + bool gotLock = false; + int triesLeft = 8; + do + { + gotLock = mAccountLock.TryAndGetLock(writeLockFilename.c_str(), 0600 /* restrictive file permissions */); + + if(!gotLock) + { + --triesLeft; + ::sleep(1); + } + } while(!gotLock && triesLeft > 0); + + if(!gotLock) + { + // Couldn't lock the account -- just stop now + if(!mQuiet) + { + BOX_ERROR("Failed to lock the account -- did not check.\nTry again later after the client has disconnected.\nAlternatively, forcibly kill the server."); + } + THROW_EXCEPTION(BackupStoreException, CouldNotLockStoreAccount) + } + } + + if(!mQuiet && mFixErrors) + { + BOX_NOTICE("Will fix errors encountered during checking."); + } + + // Phase 1, check objects + if(!mQuiet) + { + BOX_INFO("Checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << "..."); + BOX_INFO("Phase 1, check objects..."); + } + CheckObjects(); + + // Phase 2, check directories + if(!mQuiet) + { + BOX_INFO("Phase 2, check directories..."); + } + CheckDirectories(); + + // Phase 3, check root + if(!mQuiet) + { + BOX_INFO("Phase 3, check root..."); + } + CheckRoot(); + + // Phase 4, check unattached objects + if(!mQuiet) + { + BOX_INFO("Phase 4, fix unattached objects..."); + } + CheckUnattachedObjects(); + + // Phase 5, fix bad info + if(!mQuiet) + { + BOX_INFO("Phase 5, fix unrecovered inconsistencies..."); + } + FixDirsWithWrongContainerID(); + FixDirsWithLostDirs(); + + // Phase 6, regenerate store info + if(!mQuiet) + { + BOX_INFO("Phase 6, regenerate store info..."); + } + WriteNewStoreInfo(); + +// DUMP_OBJECT_INFO + + if(mNumberErrorsFound > 0) + { + BOX_WARNING("Finished checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << ": " << + mNumberErrorsFound << " errors found"); + if(!mFixErrors) + { + BOX_WARNING("No changes to the store account " + "have been made."); + } + if(!mFixErrors && mNumberErrorsFound > 0) + { + BOX_WARNING("Run again with fix option to " + "fix these errors"); + } + if(mFixErrors && mNumberErrorsFound > 0) + { + BOX_WARNING("You should now use bbackupquery " + "on the client machine to examine the store."); + if(mLostAndFoundDirectoryID != 0) + { + BOX_WARNING("A lost+found directory was " + "created in the account root.\n" + "This contains files and directories " + "which could not be matched to " + "existing directories.\n"\ + "bbackupd will delete this directory " + "in a few days time."); + } + } + } + else + { + BOX_NOTICE("Finished checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << ": " + "no errors found"); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: static TwoDigitHexToInt(const char *, int &) +// Purpose: Convert a two digit hex string to an int, returning whether it's valid or not +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +static inline bool TwoDigitHexToInt(const char *String, int &rNumberOut) +{ + int n = 0; + // Char 0 + if(String[0] >= '0' && String[0] <= '9') + { + n = (String[0] - '0') << 4; + } + else if(String[0] >= 'a' && String[0] <= 'f') + { + n = ((String[0] - 'a') + 0xa) << 4; + } + else + { + return false; + } + // Char 1 + if(String[1] >= '0' && String[1] <= '9') + { + n |= String[1] - '0'; + } + else if(String[1] >= 'a' && String[1] <= 'f') + { + n |= (String[1] - 'a') + 0xa; + } + else + { + return false; + } + + // Return a valid number + rNumberOut = n; + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjects() +// Purpose: Read in the contents of the directory, recurse to other levels, +// checking objects for sanity and readability +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckObjects() +{ + // Maximum start ID of directories -- worked out by looking at disc contents, not trusting anything + int64_t maxDir = 0; + + // Find the maximum directory starting ID + { + // Make sure the starting root dir doesn't end with '/'. + std::string start(mStoreRoot); + if(start.size() > 0 && start[start.size() - 1] == '/') + { + start.resize(start.size() - 1); + } + + maxDir = CheckObjectsScanDir(0, 1, mStoreRoot); + BOX_TRACE("Max dir starting ID is " << + BOX_FORMAT_OBJECTID(maxDir)); + } + + // Then go through and scan all the objects within those directories + for(int64_t d = 0; d <= maxDir; d += (1<<STORE_ID_SEGMENT_LENGTH)) + { + CheckObjectsDir(d); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjectsScanDir(int64_t, int, int, const std::string &) +// Purpose: Read in the contents of the directory, recurse to other levels, +// return the maximum starting ID of any directory found. +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckObjectsScanDir(int64_t StartID, int Level, const std::string &rDirName) +{ + //TRACE2("Scan directory for max dir starting ID %s, StartID %lld\n", rDirName.c_str(), StartID); + + int64_t maxID = StartID; + + // Read in all the directories, and recurse downwards + { + std::vector<std::string> dirs; + RaidFileRead::ReadDirectoryContents(mDiscSetNumber, rDirName, + RaidFileRead::DirReadType_DirsOnly, dirs); + + for(std::vector<std::string>::const_iterator i(dirs.begin()); i != dirs.end(); ++i) + { + // Check to see if it's the right name + int n = 0; + if((*i).size() == 2 && TwoDigitHexToInt((*i).c_str(), n) + && n < (1<<STORE_ID_SEGMENT_LENGTH)) + { + // Next level down + int64_t mi = CheckObjectsScanDir(StartID | (n << (Level * STORE_ID_SEGMENT_LENGTH)), Level + 1, + rDirName + DIRECTORY_SEPARATOR + *i); + // Found a greater starting ID? + if(mi > maxID) + { + maxID = mi; + } + } + else + { + BOX_WARNING("Spurious or invalid directory " << + rDirName << DIRECTORY_SEPARATOR << + (*i) << " found, " << + (mFixErrors?"deleting":"delete manually")); + ++mNumberErrorsFound; + } + } + } + + return maxID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjectsDir(int64_t) +// Purpose: Check all the files within this directory which has the given starting ID. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckObjectsDir(int64_t StartID) +{ + // Make directory name -- first generate the filename of an entry in it + std::string dirName; + StoreStructure::MakeObjectFilename(StartID, mStoreRoot, mDiscSetNumber, dirName, false /* don't make sure the dir exists */); + // Check expectations + ASSERT(dirName.size() > 4 && + dirName[dirName.size() - 4] == DIRECTORY_SEPARATOR_ASCHAR); + // Remove the filename from it + dirName.resize(dirName.size() - 4); // four chars for "/o00" + + // Check directory exists + if(!RaidFileRead::DirectoryExists(mDiscSetNumber, dirName)) + { + BOX_WARNING("RaidFile dir " << dirName << " does not exist"); + return; + } + + // Read directory contents + std::vector<std::string> files; + RaidFileRead::ReadDirectoryContents(mDiscSetNumber, dirName, + RaidFileRead::DirReadType_FilesOnly, files); + + // Array of things present + bool idsPresent[(1<<STORE_ID_SEGMENT_LENGTH)]; + for(int l = 0; l < (1<<STORE_ID_SEGMENT_LENGTH); ++l) + { + idsPresent[l] = false; + } + + // Parse each entry, building up a list of object IDs which are present in the dir. + // This is done so that whatever order is retured from the directory, objects are scanned + // in order. + // Filename must begin with a 'o' and be three characters long, otherwise it gets deleted. + for(std::vector<std::string>::const_iterator i(files.begin()); i != files.end(); ++i) + { + bool fileOK = true; + int n = 0; + if((*i).size() == 3 && (*i)[0] == 'o' && TwoDigitHexToInt((*i).c_str() + 1, n) + && n < (1<<STORE_ID_SEGMENT_LENGTH)) + { + // Filename is valid, mark as existing + idsPresent[n] = true; + } + else + { + // info file in root dir is OK! + if(StartID != 0 || ::strcmp("info", (*i).c_str()) != 0) + { + fileOK = false; + } + } + + if(!fileOK) + { + // Unexpected or bad file, delete it + BOX_WARNING("Spurious file " << dirName << + DIRECTORY_SEPARATOR << (*i) << " found" << + (mFixErrors?", deleting":"")); + ++mNumberErrorsFound; + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, dirName + DIRECTORY_SEPARATOR + *i); + del.Delete(); + } + } + } + + // Check all the objects found in this directory + for(int i = 0; i < (1<<STORE_ID_SEGMENT_LENGTH); ++i) + { + if(idsPresent[i]) + { + // Check the object is OK, and add entry + char leaf[8]; + ::sprintf(leaf, DIRECTORY_SEPARATOR "o%02x", i); + if(!CheckAndAddObject(StartID | i, dirName + leaf)) + { + // File was bad, delete it + BOX_WARNING("Corrupted file " << dirName << + leaf << " found" << + (mFixErrors?", deleting":"")); + ++mNumberErrorsFound; + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, dirName + leaf); + del.Delete(); + } + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// 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) +{ + // Info on object... + bool isFile = true; + int64_t containerID = -1; + int64_t size = -1; + + try + { + // Open file + 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 + uint32_t signature; + if(file->Read(&signature, sizeof(signature)) != sizeof(signature)) + { + // Too short, can't read signature from it + return false; + } + // Seek back to beginning + file->Seek(0, IOStream::SeekType_Absolute); + + // Then... check depending on the type + switch(ntohl(signature)) + { + case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: +#endif + // File... check + containerID = CheckFile(ObjectID, *file); + break; + + case OBJECTMAGIC_DIR_MAGIC_VALUE: + isFile = false; + containerID = CheckDirInitial(ObjectID, *file); + break; + + default: + // Unknown signature. Bad file. Very bad file. + return false; + break; + } + + // Add to usage counts + mBlocksUsed += size; + if(!isFile) + { + mBlocksInDirectories += size; + } + } + catch(...) + { + // Error caught, not a good file then, let it be deleted + return false; + } + + // Got a container ID? (ie check was successful) + if(containerID == -1) + { + return false; + } + + // Add to list of IDs known about + AddID(ObjectID, containerID, size, isFile); + + // Report success + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckFile(int64_t, IOStream &) +// Purpose: Do check on file, return original container ID if OK, or -1 on error +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream) +{ + // Check that it's not the root directory ID. Having a file as the root directory would be bad. + if(ObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID) + { + // Get that dodgy thing deleted! + BOX_ERROR("Have file as root directory. This is bad."); + return -1; + } + + // Check the format of the file, and obtain the container ID + int64_t originalContainerID = -1; + if(!BackupStoreFile::VerifyEncodedFileFormat(rStream, 0 /* don't want diffing from ID */, + &originalContainerID)) + { + // Didn't verify + return -1; + } + + return originalContainerID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckDirInitial(int64_t, IOStream &) +// Purpose: Do initial check on directory, return container ID if OK, or -1 on error +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckDirInitial(int64_t ObjectID, IOStream &rStream) +{ + // Simply attempt to read in the directory + BackupStoreDirectory dir; + dir.ReadFromStream(rStream, IOStream::TimeOutInfinite); + + // Check object ID + if(dir.GetObjectID() != ObjectID) + { + // Wrong object ID + return -1; + } + + // Return container ID + return dir.GetContainerID(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckDirectories() +// Purpose: Check the directories +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckDirectories() +{ + // Phase 1 did this: + // Checked that all the directories are readable + // Built a list of all directories and files which exist on the store + // + // This phase will check all the files in the directories, make + // a note of all directories which are missing, and do initial fixing. + + // Scan all objects + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + if(flags & Flags_IsDir) + { + // Found a directory. Read it in. + std::string filename; + StoreStructure::MakeObjectFilename(pblock->mID[e], mStoreRoot, mDiscSetNumber, filename, false /* no dir creation */); + BackupStoreDirectory dir; + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Flag for modifications + bool isModified = false; + + // Check for validity + if(dir.CheckAndFix()) + { + // Wasn't quite right, and has been modified + BOX_WARNING("Directory ID " << + BOX_FORMAT_OBJECTID(pblock->mID[e]) << + " has bad structure"); + ++mNumberErrorsFound; + isModified = true; + } + + // Go through, and check that everything in that directory exists and is valid + std::vector<int64_t> toDelete; + + 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) + { + // Found. Get flags + uint8_t iflags = GetFlags(piBlock, iIndex); + + // Is the type the same? + if(((iflags & Flags_IsDir) == Flags_IsDir) + != ((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) == BackupStoreDirectory::Entry::Flags_Dir)) + { + // Entry is of wrong type + BOX_WARNING("Directory ID " << + BOX_FORMAT_OBJECTID(pblock->mID[e]) << + " references object " << + BOX_FORMAT_OBJECTID(en->GetObjectID()) << + " which has a different type than expected."); + badEntry = true; + } + else + { + // Check that the entry is not already contained. + if(iflags & Flags_IsContained) + { + BOX_WARNING("Directory ID " << + BOX_FORMAT_OBJECTID(pblock->mID[e]) << + " references object " << + BOX_FORMAT_OBJECTID(en->GetObjectID()) << + " which is already contained."); + badEntry = true; + } + else + { + // Not already contained -- mark as contained + SetFlags(piBlock, iIndex, iflags | Flags_IsContained); + + // Check that the container ID of the object is correct + if(piBlock->mContainer[iIndex] != pblock->mID[e]) + { + // Needs fixing... + if(iflags & Flags_IsDir) + { + // Add to will fix later list + BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " has wrong container ID."); + mDirsWithWrongContainerID.push_back(en->GetObjectID()); + } + else + { + // This is OK for files, they might move + BOX_WARNING("File ID " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " has different container ID, probably moved"); + } + + // Fix entry for now + piBlock->mContainer[iIndex] = pblock->mID[e]; + } + } + } + + // Check the object size, if it's OK and a file + if(!badEntry && !((iflags & Flags_IsDir) == Flags_IsDir)) + { + if(en->GetSizeInBlocks() != piBlock->mObjectSizeInBlocks[iIndex]) + { + // Correct + en->SetSizeInBlocks(piBlock->mObjectSizeInBlocks[iIndex]); + // Mark as changed + isModified = true; + // Tell user + BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " has wrong size for object " << BOX_FORMAT_OBJECTID(en->GetObjectID())); + } + } + } + else + { + // Item can't be found. Is it a directory? + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) + { + // Store the directory for later attention + mDirsWhichContainLostDirs[en->GetObjectID()] = pblock->mID[e]; + } + else + { + // Just remove the entry + badEntry = true; + BOX_WARNING("Directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " references object " << BOX_FORMAT_OBJECTID(en->GetObjectID()) << " which does not exist."); + } + } + + // Is this entry worth keeping? + if(badEntry) + { + toDelete.push_back(en->GetObjectID()); + } + else + { + // Add to sizes? + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) + { + mBlocksInOldFiles += en->GetSizeInBlocks(); + } + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) + { + mBlocksInDeletedFiles += en->GetSizeInBlocks(); + } + } + } + + if(toDelete.size() > 0) + { + // Delete entries from directory + for(std::vector<int64_t>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) + { + dir.DeleteEntry(*d); + } + + // Mark as modified + isModified = true; + + // Check the directory again, now that entries have been removed + dir.CheckAndFix(); + + // Errors found + ++mNumberErrorsFound; + } + + if(isModified && mFixErrors) + { + BOX_WARNING("Fixing directory ID " << BOX_FORMAT_OBJECTID(pblock->mID[e])); + + // Save back to disc + RaidFileWrite fixed(mDiscSetNumber, filename); + fixed.Open(true /* allow overwriting */); + dir.WriteToStream(fixed); + // Commit it + fixed.Commit(true /* convert to raid representation now */); + } + } + } + } + +} + + diff --git a/lib/backupstore/BackupStoreCheck.h b/lib/backupstore/BackupStoreCheck.h new file mode 100644 index 00000000..1d5c1b1e --- /dev/null +++ b/lib/backupstore/BackupStoreCheck.h @@ -0,0 +1,199 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck.h +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTORECHECK__H +#define BACKUPSTORECHECK__H + +#include <string> +#include <map> +#include <vector> +#include <set> + +#include "NamedLock.h" +class IOStream; +class BackupStoreFilename; + +/* + +The following problems can be fixed: + + * Spurious files deleted + * Corrupted files deleted + * Root ID as file, deleted + * Dirs with wrong object id inside, deleted + * Direcetory entries pointing to non-existant files, deleted + * Doubly references files have second reference deleted + * Wrong directory container IDs fixed + * Missing root recreated + * Reattach files which exist, but aren't referenced + - files go into directory original directory, if it still exists + - missing directories are inferred, and recreated + - or if all else fails, go into lost+found + - file dir entries take the original name and mod time + - directories go into lost+found + * Container IDs on directories corrected + * Inside directories, + - only one object per name has old version clear + - IDs aren't duplicated + * Bad store info files regenerated + * Bad sizes of files in directories fixed + +*/ + + +// Size of blocks in the list of IDs +#ifdef BOX_RELEASE_BUILD + #define BACKUPSTORECHECK_BLOCK_SIZE (64*1024) +#else + #define BACKUPSTORECHECK_BLOCK_SIZE 8 +#endif + +// The object ID type -- can redefine to uint32_t to produce a lower memory version for smaller stores +typedef int64_t BackupStoreCheck_ID_t; +// Can redefine the size type for lower memory usage too +typedef int64_t BackupStoreCheck_Size_t; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreCheck +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +class BackupStoreCheck +{ +public: + BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet); + ~BackupStoreCheck(); +private: + // no copying + BackupStoreCheck(const BackupStoreCheck &); + BackupStoreCheck &operator=(const BackupStoreCheck &); +public: + + // Do the exciting things + void Check(); + + bool ErrorsFound() {return mNumberErrorsFound > 0;} + +private: + enum + { + // Bit mask + Flags_IsDir = 1, + Flags_IsContained = 2, + // Mask + Flags__MASK = 3, + // Number of bits + Flags__NumFlags = 2, + // Items per uint8_t + Flags__NumItemsPerEntry = 4 // ie 8 / 2 + }; + + typedef struct + { + // Note use arrays within the block, rather than the more obvious array of + // objects, to be more memory efficient -- think alignment of the byte values. + uint8_t mFlags[BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry]; + BackupStoreCheck_ID_t mID[BACKUPSTORECHECK_BLOCK_SIZE]; + BackupStoreCheck_ID_t mContainer[BACKUPSTORECHECK_BLOCK_SIZE]; + BackupStoreCheck_Size_t mObjectSizeInBlocks[BACKUPSTORECHECK_BLOCK_SIZE]; + } IDBlock; + + // Phases of the check + void CheckObjects(); + void CheckDirectories(); + void CheckRoot(); + void CheckUnattachedObjects(); + void FixDirsWithWrongContainerID(); + void FixDirsWithLostDirs(); + void WriteNewStoreInfo(); + + // Checking functions + 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); + int64_t CheckFile(int64_t ObjectID, IOStream &rStream); + int64_t CheckDirInitial(int64_t ObjectID, IOStream &rStream); + + // Fixing functions + bool TryToRecreateDirectory(int64_t MissingDirectoryID); + void InsertObjectIntoDirectory(int64_t ObjectID, int64_t DirectoryID, bool IsDirectory); + int64_t GetLostAndFoundDirID(); + void CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID); + + // Data handling + void FreeInfo(); + void AddID(BackupStoreCheck_ID_t ID, BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile); + IDBlock *LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut); + inline void SetFlags(IDBlock *pBlock, int32_t Index, uint8_t Flags) + { + ASSERT(pBlock != 0); + ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); + ASSERT(Flags < (1 << Flags__NumFlags)); + + pBlock->mFlags[Index / Flags__NumItemsPerEntry] + |= (Flags << ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)); + } + inline uint8_t GetFlags(IDBlock *pBlock, int32_t Index) + { + ASSERT(pBlock != 0); + ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); + + return (pBlock->mFlags[Index / Flags__NumItemsPerEntry] >> ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)) & Flags__MASK; + } + +#ifndef BOX_RELEASE_BUILD + void DumpObjectInfo(); + #define DUMP_OBJECT_INFO DumpObjectInfo(); +#else + #define DUMP_OBJECT_INFO +#endif + +private: + std::string mStoreRoot; + int mDiscSetNumber; + int32_t mAccountID; + bool mFixErrors; + bool mQuiet; + + int64_t mNumberErrorsFound; + + // Lock for the store account + NamedLock mAccountLock; + + // Storage for ID data + typedef std::map<BackupStoreCheck_ID_t, IDBlock*> Info_t; + Info_t mInfo; + BackupStoreCheck_ID_t mLastIDInInfo; + IDBlock *mpInfoLastBlock; + int32_t mInfoLastBlockEntries; + + // List of stuff to fix + std::vector<BackupStoreCheck_ID_t> mDirsWithWrongContainerID; + // This is a map of lost dir ID -> existing dir ID + std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t> mDirsWhichContainLostDirs; + + // Set of extra directories added + std::set<BackupStoreCheck_ID_t> mDirsAdded; + + // Misc stuff + int32_t mLostDirNameSerial; + int64_t mLostAndFoundDirectoryID; + + // Usage + int64_t mBlocksUsed; + int64_t mBlocksInOldFiles; + int64_t mBlocksInDeletedFiles; + int64_t mBlocksInDirectories; +}; + +#endif // BACKUPSTORECHECK__H + diff --git a/lib/backupstore/BackupStoreCheck2.cpp b/lib/backupstore/BackupStoreCheck2.cpp new file mode 100644 index 00000000..bcb5c5e9 --- /dev/null +++ b/lib/backupstore/BackupStoreCheck2.cpp @@ -0,0 +1,916 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck2.cpp +// Purpose: More backup store checking +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.h> + +#include "BackupStoreCheck.h" +#include "StoreStructure.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "autogen_BackupStoreException.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileWire.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreConstants.h" +#include "BackupStoreInfo.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckRoot() +// Purpose: Check the root directory exists. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckRoot() +{ + int32_t index = 0; + IDBlock *pblock = LookupID(BACKUPSTORE_ROOT_DIRECTORY_ID, index); + + if(pblock != 0) + { + // Found it. Which is lucky. Mark it as contained. + SetFlags(pblock, index, Flags_IsContained); + } + else + { + BOX_WARNING("Root directory doesn't exist"); + + ++mNumberErrorsFound; + + if(mFixErrors) + { + // Create a new root directory + CreateBlankDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CreateBlankDirectory(int64_t, int64_t) +// Purpose: Creates a blank directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID) +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + BackupStoreDirectory dir(DirectoryID, ContainingDirID); + + // Serialise to disc + std::string filename; + StoreStructure::MakeObjectFilename(DirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); + RaidFileWrite obj(mDiscSetNumber, filename); + obj.Open(false /* don't allow overwriting */); + dir.WriteToStream(obj); + int64_t size = obj.GetDiscUsageInBlocks(); + obj.Commit(true /* convert to raid now */); + + // Record the fact we've done this + mDirsAdded.insert(DirectoryID); + + // Add to sizes + mBlocksUsed += size; + mBlocksInDirectories += size; +} + +class BackupStoreDirectoryFixer +{ + private: + BackupStoreDirectory mDirectory; + std::string mFilename; + std::string mStoreRoot; + int mDiscSetNumber; + + public: + BackupStoreDirectoryFixer(std::string storeRoot, int discSetNumber, + int64_t ID); + void InsertObject(int64_t ObjectID, bool IsDirectory, + int32_t lostDirNameSerial); + ~BackupStoreDirectoryFixer(); +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckUnattachedObjects() +// Purpose: Check for objects which aren't attached to anything +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckUnattachedObjects() +{ + typedef std::map<int64_t, BackupStoreDirectoryFixer*> fixers_t; + typedef std::pair<int64_t, BackupStoreDirectoryFixer*> fixer_pair_t; + fixers_t fixers; + + // Scan all objects, finding ones which have no container + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + if((flags & Flags_IsContained) == 0) + { + // Unattached object... + BOX_WARNING("Object " << + BOX_FORMAT_OBJECTID(pblock->mID[e]) << + " is unattached."); + ++mNumberErrorsFound; + + // What's to be done? + int64_t putIntoDirectoryID = 0; + + if((flags & Flags_IsDir) == Flags_IsDir) + { + // Directory. Just put into lost and found. + putIntoDirectoryID = GetLostAndFoundDirID(); + } + else + { + // File. Only attempt to attach it somewhere if it isn't a patch + { + 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 */); + // 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. + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + BackupStoreFile::VerifyEncodedFileFormat(*file, &diffFromObjectID); + } + + // If not zero, then it depends on another file, which may or may not be available. + // Just delete it to be safe. + if(diffFromObjectID != 0) + { + BOX_WARNING("Object " << BOX_FORMAT_OBJECTID(pblock->mID[e]) << " is unattached, and is a patch. Deleting, cannot reliably recover."); + + // Delete this object instead + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, filename); + del.Delete(); + } + + // Move on to next item + continue; + } + } + + // Files contain their original filename, so perhaps the orginal directory still exists, + // or we can infer the existance of a directory? + // Look for a matching entry in the mDirsWhichContainLostDirs map. + // Can't do this with a directory, because the name just wouldn't be known, which is + // pretty useless as bbackupd would just delete it. So better to put it in lost+found + // where the admin can do something about it. + int32_t dirindex; + IDBlock *pdirblock = LookupID(pblock->mContainer[e], dirindex); + if(pdirblock != 0) + { + // Something with that ID has been found. Is it a directory? + if(GetFlags(pdirblock, dirindex) & Flags_IsDir) + { + // Directory exists, add to that one + putIntoDirectoryID = pblock->mContainer[e]; + } + else + { + // Not a directory. Use lost and found dir + putIntoDirectoryID = GetLostAndFoundDirID(); + } + } + else if(mDirsAdded.find(pblock->mContainer[e]) != mDirsAdded.end() + || TryToRecreateDirectory(pblock->mContainer[e])) + { + // The directory reappeared, or was created somehow elsewhere + putIntoDirectoryID = pblock->mContainer[e]; + } + else + { + putIntoDirectoryID = GetLostAndFoundDirID(); + } + } + ASSERT(putIntoDirectoryID != 0); + + if (!mFixErrors) + { + continue; + } + + BackupStoreDirectoryFixer* pFixer; + fixers_t::iterator fi = + fixers.find(putIntoDirectoryID); + if (fi == fixers.end()) + { + // no match, create a new one + pFixer = new BackupStoreDirectoryFixer( + mStoreRoot, mDiscSetNumber, + putIntoDirectoryID); + fixers.insert(fixer_pair_t( + putIntoDirectoryID, pFixer)); + } + else + { + pFixer = fi->second; + } + + int32_t lostDirNameSerial = 0; + + if(flags & Flags_IsDir) + { + lostDirNameSerial = mLostDirNameSerial++; + } + + // Add it to the directory + pFixer->InsertObject(pblock->mID[e], + ((flags & Flags_IsDir) == Flags_IsDir), + lostDirNameSerial); + } + } + } + + // clean up all the fixers. Deleting them commits them automatically. + for (fixers_t::iterator i = fixers.begin(); i != fixers.end(); i++) + { + BackupStoreDirectoryFixer* pFixer = i->second; + delete pFixer; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::TryToRecreateDirectory(int64_t) +// Purpose: Recreate a missing directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreCheck::TryToRecreateDirectory(int64_t MissingDirectoryID) +{ + // During the directory checking phase, a map of "missing directory" to + // containing directory was built. If we can find it here, then it's + // something which can be recreated! + std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t>::iterator missing( + mDirsWhichContainLostDirs.find(MissingDirectoryID)); + if(missing == mDirsWhichContainLostDirs.end()) + { + // Not a missing directory, can't recreate. + return false; + } + + // Can recreate this! Wooo! + if(!mFixErrors) + { + BOX_WARNING("Missing directory " << + BOX_FORMAT_OBJECTID(MissingDirectoryID) << + " could be recreated."); + mDirsAdded.insert(MissingDirectoryID); + return true; + } + + BOX_WARNING("Recreating missing directory " << + BOX_FORMAT_OBJECTID(MissingDirectoryID)); + + // Create a blank directory + BackupStoreDirectory dir(MissingDirectoryID, missing->second /* containing dir ID */); + // Note that this directory already contains a directory entry pointing to + // this dir, so it doesn't have to be added. + + // Serialise to disc + std::string filename; + StoreStructure::MakeObjectFilename(MissingDirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(false /* don't allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + + // Record the fact we've done this + mDirsAdded.insert(MissingDirectoryID); + + // Remove the entry from the map, so this doesn't happen again + mDirsWhichContainLostDirs.erase(missing); + + return true; +} + +BackupStoreDirectoryFixer::BackupStoreDirectoryFixer(std::string storeRoot, + int discSetNumber, int64_t ID) +: mStoreRoot(storeRoot), + mDiscSetNumber(discSetNumber) +{ + // Generate filename + StoreStructure::MakeObjectFilename(ID, mStoreRoot, mDiscSetNumber, + mFilename, false /* don't make sure the dir exists */); + + // Read it in + std::auto_ptr<RaidFileRead> file( + RaidFileRead::Open(mDiscSetNumber, mFilename)); + mDirectory.ReadFromStream(*file, IOStream::TimeOutInfinite); +} + +void BackupStoreDirectoryFixer::InsertObject(int64_t ObjectID, bool IsDirectory, + int32_t lostDirNameSerial) +{ + // Data for the object + BackupStoreFilename objectStoreFilename; + int64_t modTime = 100; // something which isn't zero or a special time + int32_t sizeInBlocks = 0; // suitable for directories + + if(IsDirectory) + { + // Directory -- simply generate a name for it. + char name[32]; + ::sprintf(name, "dir%08x", lostDirNameSerial); + objectStoreFilename.SetAsClearFilename(name); + } + else + { + // Files require a little more work... + // Open file + std::string fileFilename; + StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, + mDiscSetNumber, fileFilename, + false /* don't make sure the dir exists */); + std::auto_ptr<RaidFileRead> file( + RaidFileRead::Open(mDiscSetNumber, fileFilename)); + + // Fill in size information + sizeInBlocks = file->GetDiscUsageInBlocks(); + + // Read in header + file_StreamFormat hdr; + if(file->Read(&hdr, sizeof(hdr)) != sizeof(hdr) || + (ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 +#endif + )) + { + // This should never happen, everything has been + // checked before. + THROW_EXCEPTION(BackupStoreException, Internal) + } + // This tells us nice things + modTime = box_ntoh64(hdr.mModificationTime); + // And the filename comes next + objectStoreFilename.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Add a new entry in an appropriate place + mDirectory.AddUnattactedObject(objectStoreFilename, modTime, + ObjectID, sizeInBlocks, + IsDirectory?(BackupStoreDirectory::Entry::Flags_Dir):(BackupStoreDirectory::Entry::Flags_File)); +} + +BackupStoreDirectoryFixer::~BackupStoreDirectoryFixer() +{ + // Fix any flags which have been broken, which there's a good chance of doing + mDirectory.CheckAndFix(); + + // Write it out + RaidFileWrite root(mDiscSetNumber, mFilename); + root.Open(true /* allow overwriting */); + mDirectory.WriteToStream(root); + root.Commit(true /* convert to raid now */); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::GetLostAndFoundDirID() +// Purpose: Returns the ID of the lost and found directory, creating it if necessary +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::GetLostAndFoundDirID() +{ + // Already allocated it? + if(mLostAndFoundDirectoryID != 0) + { + return mLostAndFoundDirectoryID; + } + + if(!mFixErrors) + { + // The result will never be used anyway if errors aren't being fixed + return 1; + } + + // Load up the root directory + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(BACKUPSTORE_ROOT_DIRECTORY_ID, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Find a suitable name + BackupStoreFilename lostAndFound; + int n = 0; + while(true) + { + char name[32]; + ::sprintf(name, "lost+found%d", n++); + lostAndFound.SetAsClearFilename(name); + if(!dir.NameInUse(lostAndFound)) + { + // Found a name which can be used + BOX_WARNING("Lost and found dir has name " << name); + break; + } + } + + // Allocate an ID + int64_t id = mLastIDInInfo + 1; + + // Create a blank directory + CreateBlankDirectory(id, BACKUPSTORE_ROOT_DIRECTORY_ID); + + // Add an entry for it + dir.AddEntry(lostAndFound, 0, id, 0, BackupStoreDirectory::Entry::Flags_Dir, 0); + + // Write out root dir + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + + // Store + mLostAndFoundDirectoryID = id; + + // Tell caller + return mLostAndFoundDirectoryID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FixDirsWithWrongContainerID() +// Purpose: Rewrites container IDs where required +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FixDirsWithWrongContainerID() +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + // Run through things which need fixing + for(std::vector<BackupStoreCheck_ID_t>::iterator i(mDirsWithWrongContainerID.begin()); + i != mDirsWithWrongContainerID.end(); ++i) + { + int32_t index = 0; + IDBlock *pblock = LookupID(*i, index); + if(pblock == 0) continue; + + // Load in + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(*i, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Adjust container ID + dir.SetContainerID(pblock->mContainer[index]); + + // Write it out + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FixDirsWithLostDirs() +// Purpose: Fix directories +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FixDirsWithLostDirs() +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + // Run through things which need fixing + for(std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t>::iterator i(mDirsWhichContainLostDirs.begin()); + i != mDirsWhichContainLostDirs.end(); ++i) + { + int32_t index = 0; + IDBlock *pblock = LookupID(i->second, index); + if(pblock == 0) continue; + + // Load in + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(i->second, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Delete the dodgy entry + dir.DeleteEntry(i->first); + + // Fix it up + dir.CheckAndFix(); + + // Write it out + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::WriteNewStoreInfo() +// Purpose: Regenerate store info +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::WriteNewStoreInfo() +{ + // Attempt to load the existing store info file + std::auto_ptr<BackupStoreInfo> poldInfo; + try + { + poldInfo.reset(BackupStoreInfo::Load(mAccountID, mStoreRoot, mDiscSetNumber, true /* read only */).release()); + } + catch(...) + { + BOX_WARNING("Load of existing store info failed, regenerating."); + ++mNumberErrorsFound; + } + + // Minimum soft and hard limits + int64_t minSoft = ((mBlocksUsed * 11) / 10) + 1024; + int64_t minHard = ((minSoft * 11) / 10) + 1024; + + // Need to do anything? + if(poldInfo.get() != 0 && mNumberErrorsFound == 0 && poldInfo->GetAccountID() == mAccountID) + { + // 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. + return; + } + + // NOTE: We will always build a new store info, so the client store marker gets changed. + + // Work out the new limits + int64_t softLimit = minSoft; + int64_t hardLimit = minHard; + 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."); + } + + // Object ID + int64_t lastObjID = mLastIDInInfo; + if(mLostAndFoundDirectoryID != 0) + { + mLastIDInInfo++; + } + + // Build a new store info + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::CreateForRegeneration( + mAccountID, + mStoreRoot, + mDiscSetNumber, + lastObjID, + mBlocksUsed, + mBlocksInOldFiles, + mBlocksInDeletedFiles, + mBlocksInDirectories, + softLimit, + hardLimit)); + + // Save to disc? + if(mFixErrors) + { + info->Save(); + BOX_NOTICE("New store info file written successfully."); + } +} + +#define FMT_OID(x) BOX_FORMAT_OBJECTID(x) +#define FMT_i BOX_FORMAT_OBJECTID((*i)->GetObjectID()) + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::CheckAndFix() +// Purpose: Check the directory for obvious logical problems, and fix them. +// Return true if the directory was changed. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreDirectory::CheckAndFix() +{ + bool changed = false; + + // Check that if a file depends on a new version, that version is in this directory + { + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + int64_t dependsNewer = (*i)->GetDependsNewer(); + if(dependsNewer != 0) + { + BackupStoreDirectory::Entry *newerEn = FindEntryByID(dependsNewer); + if(newerEn == 0) + { + // Depends on something, but it isn't there. + BOX_TRACE("Entry id " << FMT_i << + " removed because depends " + "on newer version " << + FMT_OID(dependsNewer) << + " which doesn't exist"); + + // Remove + delete *i; + mEntries.erase(i); + + // Start again at the beginning of the vector, the iterator is now invalid + i = mEntries.begin(); + + // Mark as changed + changed = true; + } + else + { + // Check that newerEn has it marked + if(newerEn->GetDependsOlder() != (*i)->GetObjectID()) + { + // Wrong entry + BOX_TRACE("Entry id " << + FMT_OID(dependsNewer) << + ", correcting DependsOlder to " << + FMT_i << + ", was " << + FMT_OID(newerEn->GetDependsOlder())); + newerEn->SetDependsOlder((*i)->GetObjectID()); + // Mark as changed + changed = true; + } + } + } + } + } + + // Check that if a file has a dependency marked, it exists, and remove it if it doesn't + { + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + int64_t dependsOlder = (*i)->GetDependsOlder(); + if(dependsOlder != 0 && FindEntryByID(dependsOlder) == 0) + { + // Has an older version marked, but this doesn't exist. Remove this mark + BOX_TRACE("Entry id " << FMT_i << + " was marked as depended on by " << + FMT_OID(dependsOlder) << ", " + "which doesn't exist, dependency " + "info cleared"); + + (*i)->SetDependsOlder(0); + + // Mark as changed + changed = true; + } + } + } + + bool ch = false; + do + { + // Reset change marker + ch = false; + + // Search backwards -- so see newer versions first + std::vector<Entry*>::iterator i(mEntries.end()); + if(i == mEntries.begin()) + { + // Directory is empty, stop now + return changed; // changed flag + } + + // Records of things seen + std::set<int64_t> idsEncountered; + std::set<std::string> filenamesEncountered; + + do + { + // Look at previous + --i; + + bool removeEntry = false; + if((*i) == 0) + { + BOX_TRACE("Remove because null pointer found"); + removeEntry = true; + } + else + { + bool isDir = (((*i)->GetFlags() & Entry::Flags_Dir) == Entry::Flags_Dir); + + // Check mutually exclusive flags + if(isDir && (((*i)->GetFlags() & Entry::Flags_File) == Entry::Flags_File)) + { + // Bad! Unset the file flag + BOX_TRACE("Entry " << FMT_i << + ": File flag and dir flag both set"); + (*i)->RemoveFlags(Entry::Flags_File); + changed = true; + } + + // Check... + if(idsEncountered.find((*i)->GetObjectID()) != idsEncountered.end()) + { + // ID already seen, or type doesn't match + BOX_TRACE("Entry " << FMT_i << + ": Remove because ID already seen"); + removeEntry = true; + } + else + { + // Haven't already seen this ID, remember it + idsEncountered.insert((*i)->GetObjectID()); + + // Check to see if the name has already been encountered -- if not, then it + // needs to have the old version flag set + if(filenamesEncountered.find((*i)->GetName().GetEncodedFilename()) != filenamesEncountered.end()) + { + // Seen before -- check old version flag set + if(((*i)->GetFlags() & Entry::Flags_OldVersion) != Entry::Flags_OldVersion + && ((*i)->GetFlags() & Entry::Flags_Deleted) == 0) + { + // Not set, set it + BOX_TRACE("Entry " << FMT_i << + ": Set old flag"); + (*i)->AddFlags(Entry::Flags_OldVersion); + changed = true; + } + } + else + { + // Check old version flag NOT set + if(((*i)->GetFlags() & Entry::Flags_OldVersion) == Entry::Flags_OldVersion) + { + // Set, unset it + BOX_TRACE("Entry " << FMT_i << + ": Old flag unset"); + (*i)->RemoveFlags(Entry::Flags_OldVersion); + changed = true; + } + + // Remember filename + filenamesEncountered.insert((*i)->GetName().GetEncodedFilename()); + } + } + } + + if(removeEntry) + { + // Mark something as changed, in loop + ch = true; + + // Mark something as globally changed + changed = true; + + // erase the thing from the list + Entry *pentry = (*i); + mEntries.erase(i); + + // And delete the entry object + delete pentry; + + // Stop going around this loop, as the iterator is now invalid + break; + } + } while(i != mEntries.begin()); + + } while(ch != false); + + return changed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::AddUnattactedObject(...) +// Purpose: Adds an object which is currently unattached. Assume that CheckAndFix() will be called afterwards. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreDirectory::AddUnattactedObject(const BackupStoreFilename &rName, + box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags) +{ + Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags, + ModificationTime /* use as attr mod time too */); + try + { + // Want to order this just before the first object which has a higher ID, + // which is the place it's most likely to be correct. + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + if((*i)->GetObjectID() > ObjectID) + { + // Found a good place to insert it + break; + } + } + if(i == mEntries.end()) + { + mEntries.push_back(pnew); + } + else + { + mEntries.insert(i, 1 /* just the one copy */, pnew); + } + } + catch(...) + { + delete pnew; + throw; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::NameInUse(const BackupStoreFilename &) +// Purpose: Returns true if the name is currently in use in the directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreDirectory::NameInUse(const BackupStoreFilename &rName) +{ + for(std::vector<Entry*>::iterator i(mEntries.begin()); i != mEntries.end(); ++i) + { + if((*i)->GetName() == rName) + { + return true; + } + } + + return false; +} + + diff --git a/lib/backupstore/BackupStoreCheckData.cpp b/lib/backupstore/BackupStoreCheckData.cpp new file mode 100644 index 00000000..fed0c3f1 --- /dev/null +++ b/lib/backupstore/BackupStoreCheckData.cpp @@ -0,0 +1,208 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheckData.cpp +// Purpose: Data handling for store checking +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <memory> + +#include "BackupStoreCheck.h" +#include "autogen_BackupStoreException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FreeInfo() +// Purpose: Free all the data stored +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FreeInfo() +{ + // Free all the blocks + for(Info_t::iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + ::free(i->second); + } + + // Clear the contents of the map + mInfo.clear(); + + // Reset the last ID, just in case + mpInfoLastBlock = 0; + mInfoLastBlockEntries = 0; + mLastIDInInfo = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::AddID(BackupStoreCheck_ID_t, BackupStoreCheck_ID_t, bool) +// Purpose: Add an ID to the list +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::AddID(BackupStoreCheck_ID_t ID, + BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile) +{ + // Check ID is OK. + if(ID <= mLastIDInInfo) + { + THROW_EXCEPTION(BackupStoreException, InternalAlgorithmErrorCheckIDNotMonotonicallyIncreasing) + } + + // Can this go in the current block? + if(mpInfoLastBlock == 0 || mInfoLastBlockEntries >= BACKUPSTORECHECK_BLOCK_SIZE) + { + // No. Allocate a new one + IDBlock *pblk = (IDBlock*)::malloc(sizeof(IDBlock)); + if(pblk == 0) + { + throw std::bad_alloc(); + } + // Zero all the flags entries + for(int z = 0; z < (BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry); ++z) + { + pblk->mFlags[z] = 0; + } + // Store in map + mInfo[ID] = pblk; + // Allocated and stored OK, setup for use + mpInfoLastBlock = pblk; + mInfoLastBlockEntries = 0; + } + ASSERT(mpInfoLastBlock != 0 && mInfoLastBlockEntries < BACKUPSTORECHECK_BLOCK_SIZE); + + // Add to block + mpInfoLastBlock->mID[mInfoLastBlockEntries] = ID; + mpInfoLastBlock->mContainer[mInfoLastBlockEntries] = Container; + mpInfoLastBlock->mObjectSizeInBlocks[mInfoLastBlockEntries] = ObjectSize; + SetFlags(mpInfoLastBlock, mInfoLastBlockEntries, IsFile?(0):(Flags_IsDir)); + + // Increment size + ++mInfoLastBlockEntries; + + // Store last ID + mLastIDInInfo = ID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::LookupID(BackupStoreCheck_ID_t, int32_t +// Purpose: Look up an ID. Return the block it's in, or zero if not found, and the +// index within that block if the thing is found. +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::IDBlock *BackupStoreCheck::LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut) +{ + IDBlock *pblock = 0; + + // Find the lower matching block who's first entry is not less than ID + Info_t::const_iterator ib(mInfo.lower_bound(ID)); + + // Was there a block + if(ib == mInfo.end()) + { + // Block wasn't found... could be in last block + pblock = mpInfoLastBlock; + } + else + { + // Found it as first entry? + if(ib->first == ID) + { + rIndexOut = 0; + return ib->second; + } + + // Go back one block as it's not the first entry in this one + if(ib == mInfo.begin()) + { + // Was first block, can't go back + return 0; + } + // Go back... + --ib; + + // So, the ID will be in this block, if it's in anything + pblock = ib->second; + } + + ASSERT(pblock != 0); + if(pblock == 0) return 0; + + // How many entries are there in the block + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + // Do binary search within block + int high = bentries; + int low = -1; + while(high - low > 1) + { + int i = (high + low) / 2; + if(ID <= pblock->mID[i]) + { + high = i; + } + else + { + low = i; + } + } + if(ID == pblock->mID[high]) + { + // Found + rIndexOut = high; + return pblock; + } + + // Not found + return 0; +} + + +#ifndef BOX_RELEASE_BUILD +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::DumpObjectInfo() +// Purpose: Debug only. Trace out all object info. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::DumpObjectInfo() +{ + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + BOX_TRACE("BLOCK @ " << BOX_FORMAT_HEX32(pblock) << + ", " << bentries << " entries"); + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + BOX_TRACE(std::hex << + "id " << pblock->mID[e] << + ", c " << pblock->mContainer[e] << + ", " << ((flags & Flags_IsDir)?"dir":"file") << + ", " << ((flags & Flags_IsContained) ? + "contained":"unattached")); + } + } +} +#endif + diff --git a/lib/backupstore/BackupStoreConfigVerify.cpp b/lib/backupstore/BackupStoreConfigVerify.cpp new file mode 100644 index 00000000..cc6efcf5 --- /dev/null +++ b/lib/backupstore/BackupStoreConfigVerify.cpp @@ -0,0 +1,57 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreConfigVerify.h +// Purpose: Configuration definition for the backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupStoreConfigVerify.h" +#include "ServerTLS.h" +#include "BoxPortsAndFiles.h" + +#include "MemLeakFindOn.h" + +static const ConfigurationVerifyKey verifyserverkeys[] = +{ + SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) + // no default listen addresses +}; + +static const ConfigurationVerify verifyserver[] = +{ + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyrootkeys[] = +{ + ConfigurationVerifyKey("AccountDatabase", ConfigTest_Exists), + ConfigurationVerifyKey("TimeBetweenHousekeeping", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), + // make value "yes" to enable in config file + + #ifdef WIN32 + ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry) + #else + ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry, + BOX_FILE_RAIDFILE_DEFAULT_CONFIG) + #endif +}; + +const ConfigurationVerify BackupConfigFileVerify = +{ + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; diff --git a/lib/backupstore/BackupStoreConfigVerify.h b/lib/backupstore/BackupStoreConfigVerify.h new file mode 100644 index 00000000..815cfaed --- /dev/null +++ b/lib/backupstore/BackupStoreConfigVerify.h @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreConfigVerify.h +// Purpose: Configuration definition for the backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTORECONFIGVERIFY__H +#define BACKUPSTORECONFIGVERIFY__H + +#include "Configuration.h" + +extern const ConfigurationVerify BackupConfigFileVerify; + +#endif // BACKUPSTORECONFIGVERIFY__H + diff --git a/lib/backupstore/BackupStoreInfo.cpp b/lib/backupstore/BackupStoreInfo.cpp new file mode 100644 index 00000000..1d55fdf0 --- /dev/null +++ b/lib/backupstore/BackupStoreInfo.cpp @@ -0,0 +1,593 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreInfo.cpp +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <algorithm> + +#include "BackupStoreInfo.h" +#include "BackupStoreException.h" +#include "RaidFileWrite.h" +#include "RaidFileRead.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 + +// ****************** +// make sure the defaults in CreateNew are modified! +// ****************** +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; + +#define INFO_MAGIC_VALUE 0x34832476 + +// Use default packing +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "EndStructPackForWire.h" +#else +END_STRUCTURE_PACKING_FOR_WIRE +#endif + +#ifdef BOX_RELEASE_BUILD + #define NUM_DELETED_DIRS_BLOCK 256 +#else + #define NUM_DELETED_DIRS_BLOCK 2 +#endif + +#define INFO_FILENAME "info" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::BackupStoreInfo() +// Purpose: Default constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreInfo::BackupStoreInfo() + : mAccountID(-1), + mDiscSet(-1), + mReadOnly(true), + mIsModified(false), + mClientStoreMarker(0), + mLastObjectIDUsed(-1), + mBlocksUsed(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::~BackupStoreInfo +// Purpose: Destructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreInfo::~BackupStoreInfo() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CreateNew(int32_t, const std::string &, int) +// Purpose: Create a new info file on disc +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit) +{ + // Initial header (is entire file) + info_StreamFormat hdr = { + htonl(INFO_MAGIC_VALUE), // mMagicValue + htonl(AccountID), // mAccountID + 0, // mClientStoreMarker + box_hton64(1), // mLastObjectIDUsed (which is the root directory) + 0, // mBlocksUsed + 0, // mBlocksInOldFiles + 0, // mBlocksInDeletedFiles + 0, // mBlocksInDirectories + box_hton64(BlockSoftLimit), // mBlocksSoftLimit + box_hton64(BlockHardLimit), // mBlocksHardLimit + 0, // mCurrentMarkNumber + 0, // mOptionsPresent + 0 // mNumberDeletedDirectories + }; + + // Generate the filename + ASSERT(rRootDir[rRootDir.size() - 1] == '/' || + rRootDir[rRootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR); + std::string fn(rRootDir + INFO_FILENAME); + + // Open the file for writing + RaidFileWrite rf(DiscSet, fn); + rf.Open(false); // no overwriting, as this is a new file + + // Write header + rf.Write(&hdr, sizeof(hdr)); + + // Commit it to disc, converting it to RAID now + rf.Commit(true); + + // Done. +} + +// -------------------------------------------------------------------------- +// +// Function +// 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 + DIRECTORY_SEPARATOR 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 a header + info_StreamFormat hdr; + if(!rf->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) + } + + // Check it + if(ntohl(hdr.mMagicValue) != INFO_MAGIC_VALUE || (int32_t)ntohl(hdr.mAccountID) != AccountID) + { + THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) + } + + // Make new object + std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo); + + // Put in basic location info + info->mAccountID = AccountID; + info->mDiscSet = DiscSet; + info->mFilename = fn; + info->mReadOnly = ReadOnly; + + // 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 + int64_t numDelObj = box_ntoh64(hdr.mNumberDeletedDirectories); + + // Then load them in + if(numDelObj > 0) + { + int64_t objs[NUM_DELETED_DIRS_BLOCK]; + + int64_t toload = numDelObj; + while(toload > 0) + { + // How many in this one? + int b = (toload > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(toload)); + + if(!rf->ReadFullBuffer(objs, b * sizeof(int64_t), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) + } + + // Add them + for(int t = 0; t < b; ++t) + { + info->mDeletedDirectories.push_back(box_ntoh64(objs[t])); + } + + // Number loaded + toload -= b; + } + } + + // Final check + if(static_cast<int64_t>(info->mDeletedDirectories.size()) != numDelObj) + { + THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) + } + + // return it to caller + return info; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CreateForRegeneration(...) +// Purpose: Return an object which can be used to save for regeneration. +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- +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); + + // Make new object + std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo); + + // Put in basic info + info->mAccountID = AccountID; + info->mDiscSet = DiscSet; + info->mFilename = fn; + info->mReadOnly = false; + + // Insert info starting info + info->mClientStoreMarker = 0; + info->mLastObjectIDUsed = LastObjectID; + info->mBlocksUsed = BlocksUsed; + info->mBlocksInOldFiles = BlocksInOldFiles; + info->mBlocksInDeletedFiles = BlocksInDeletedFiles; + info->mBlocksInDirectories = BlocksInDirectories; + info->mBlocksSoftLimit = BlockSoftLimit; + info->mBlocksHardLimit = BlockHardLimit; + + // return it to caller + return info; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::Save() +// Purpose: Save modified info back to disc +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::Save() +{ + // Make sure we're initialised (although should never come to this) + if(mFilename.empty() || mAccountID == -1 || mDiscSet == -1) + { + THROW_EXCEPTION(BackupStoreException, Internal) + } + + // Can we do this? + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + // Then... open a write file + RaidFileWrite rf(mDiscSet, mFilename); + rf.Open(true); // allow overwriting + + // Make header + info_StreamFormat hdr; + hdr.mMagicValue = htonl(INFO_MAGIC_VALUE); + hdr.mAccountID = htonl(mAccountID); + hdr.mClientStoreMarker = box_hton64(mClientStoreMarker); + hdr.mLastObjectIDUsed = box_hton64(mLastObjectIDUsed); + hdr.mBlocksUsed = box_hton64(mBlocksUsed); + hdr.mBlocksInOldFiles = box_hton64(mBlocksInOldFiles); + hdr.mBlocksInDeletedFiles = box_hton64(mBlocksInDeletedFiles); + hdr.mBlocksInDirectories = box_hton64(mBlocksInDirectories); + hdr.mBlocksSoftLimit = box_hton64(mBlocksSoftLimit); + hdr.mBlocksHardLimit = box_hton64(mBlocksHardLimit); + hdr.mCurrentMarkNumber = 0; + hdr.mOptionsPresent = 0; + hdr.mNumberDeletedDirectories = box_hton64(mDeletedDirectories.size()); + + // Write header + rf.Write(&hdr, sizeof(hdr)); + + // Write the deleted object list + if(mDeletedDirectories.size() > 0) + { + int64_t objs[NUM_DELETED_DIRS_BLOCK]; + + int tosave = mDeletedDirectories.size(); + std::vector<int64_t>::iterator i(mDeletedDirectories.begin()); + while(tosave > 0) + { + // How many in this one? + int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave)); + + // Add them + for(int t = 0; t < b; ++t) + { + ASSERT(i != mDeletedDirectories.end()); + objs[t] = box_hton64((*i)); + i++; + } + + // Write + rf.Write(objs, b * sizeof(int64_t)); + + // Number saved + tosave -= b; + } + } + + // Commit it to disc, converting it to RAID now + rf.Commit(true); + + // Mark is as not modified + mIsModified = false; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksUsed(int32_t) +// Purpose: Change number of blocks used, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + if((mBlocksUsed + Delta) < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) + } + + mBlocksUsed += Delta; + + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInOldFiles(int32_t) +// Purpose: Change number of blocks in old files, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + if((mBlocksInOldFiles + Delta) < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) + } + + mBlocksInOldFiles += Delta; + + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInDeletedFiles(int32_t) +// Purpose: Change number of blocks in deleted files, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + if((mBlocksInDeletedFiles + Delta) < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) + } + + mBlocksInDeletedFiles += Delta; + + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInDirectories(int32_t) +// Purpose: Change number of blocks in directories, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + if((mBlocksInDirectories + Delta) < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoBlockDeltaMakesValueNegative) + } + + mBlocksInDirectories += Delta; + + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CorrectAllUsedValues(int64_t, int64_t, int64_t, int64_t) +// Purpose: Set all the usage counts to specific values -- use for correcting in housekeeping +// if something when wrong during the backup connection, and the store info wasn't +// saved back to disc. +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + // Set the values + mBlocksUsed = Used; + mBlocksInOldFiles = InOldFiles; + mBlocksInDeletedFiles = InDeletedFiles; + mBlocksInDirectories = InDirectories; + + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::AddDeletedDirectory(int64_t) +// Purpose: Add a directory ID to the deleted list +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::AddDeletedDirectory(int64_t DirID) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mDeletedDirectories.push_back(DirID); + + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::RemovedDeletedDirectory(int64_t) +// Purpose: Remove a directory from the deleted list +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::RemovedDeletedDirectory(int64_t DirID) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + std::vector<int64_t>::iterator i(std::find(mDeletedDirectories.begin(), mDeletedDirectories.end(), DirID)); + if(i == mDeletedDirectories.end()) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoDirNotInList) + } + mDeletedDirectories.erase(i); + + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeLimits(int64_t, int64_t) +// Purpose: Change the soft and hard limits +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mBlocksSoftLimit = BlockSoftLimit; + mBlocksHardLimit = BlockHardLimit; + + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::AllocateObjectID() +// Purpose: Allocate an ID for a new object in the store. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreInfo::AllocateObjectID() +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + if(mLastObjectIDUsed < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotInitialised) + } + + // Return the next object ID + return ++mLastObjectIDUsed; + + mIsModified = true; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::SetClientStoreMarker(int64_t) +// Purpose: Sets the client store marker +// Created: 2003/10/29 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::SetClientStoreMarker(int64_t ClientStoreMarker) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mClientStoreMarker = ClientStoreMarker; + + mIsModified = true; +} + + + diff --git a/lib/backupstore/BackupStoreInfo.h b/lib/backupstore/BackupStoreInfo.h new file mode 100644 index 00000000..a94ca9d6 --- /dev/null +++ b/lib/backupstore/BackupStoreInfo.h @@ -0,0 +1,111 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreInfo.h +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREINFO__H +#define BACKUPSTOREINFO__H + +#include <memory> +#include <string> +#include <vector> + +class BackupStoreCheck; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreInfo +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +class BackupStoreInfo +{ + friend class BackupStoreCheck; +public: + ~BackupStoreInfo(); +private: + // Creation through static functions only + BackupStoreInfo(); + // No copying allowed + BackupStoreInfo(const BackupStoreInfo &); + +public: + // Create a New account, saving a blank info object to the disc + static void CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit); + + // Load it from the store + static std::auto_ptr<BackupStoreInfo> Load(int32_t AccountID, const std::string &rRootDir, int DiscSet, bool ReadOnly, int64_t *pRevisionID = 0); + + // Has info been modified? + bool IsModified() const {return mIsModified;} + + // Save modified infomation back to store + void Save(); + + // Data access functions + int32_t GetAccountID() const {return mAccountID;} + int64_t GetLastObjectIDUsed() const {return mLastObjectIDUsed;} + int64_t GetBlocksUsed() const {return mBlocksUsed;} + 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;} + bool IsReadOnly() const {return mReadOnly;} + int GetDiscSetNumber() const {return mDiscSet;} + + // Data modification functions + void ChangeBlocksUsed(int64_t Delta); + void ChangeBlocksInOldFiles(int64_t Delta); + void ChangeBlocksInDeletedFiles(int64_t Delta); + void ChangeBlocksInDirectories(int64_t Delta); + void CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories); + void AddDeletedDirectory(int64_t DirID); + void RemovedDeletedDirectory(int64_t DirID); + void ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit); + + // Object IDs + int64_t AllocateObjectID(); + + // Client marker set and get + int64_t GetClientStoreMarker() {return mClientStoreMarker;} + void SetClientStoreMarker(int64_t ClientStoreMarker); + +private: + static std::auto_ptr<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); + +private: + // Location information + int32_t mAccountID; + int mDiscSet; + std::string mFilename; + bool mReadOnly; + bool mIsModified; + + // Client infomation + int64_t mClientStoreMarker; + + // Account information + int64_t mLastObjectIDUsed; + int64_t mBlocksUsed; + int64_t mBlocksInOldFiles; + int64_t mBlocksInDeletedFiles; + int64_t mBlocksInDirectories; + int64_t mBlocksSoftLimit; + int64_t mBlocksHardLimit; + std::vector<int64_t> mDeletedDirectories; +}; + + +#endif // BACKUPSTOREINFO__H + + diff --git a/lib/backupstore/BackupStoreRefCountDatabase.cpp b/lib/backupstore/BackupStoreRefCountDatabase.cpp new file mode 100644 index 00000000..f6db2ca4 --- /dev/null +++ b/lib/backupstore/BackupStoreRefCountDatabase.cpp @@ -0,0 +1,321 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreRefCountDatabase.cpp +// Purpose: Backup store object reference count database storage +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <algorithm> + +#include "BackupStoreRefCountDatabase.h" +#include "BackupStoreException.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "RaidFileController.h" +#include "RaidFileUtil.h" +#include "RaidFileException.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define REFCOUNT_MAGIC_VALUE 0x52656643 // RefC +#define REFCOUNT_FILENAME "refcount" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::BackupStoreRefCountDatabase() +// Purpose: Default constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::BackupStoreRefCountDatabase(const + BackupStoreAccountDatabase::Entry& rAccount) +: mAccount(rAccount), + mFilename(GetFilename(rAccount)), + mReadOnly(true), + mIsModified(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase +// Purpose: Destructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase() +{ +} + +std::string BackupStoreRefCountDatabase::GetFilename(const + BackupStoreAccountDatabase::Entry& rAccount) +{ + std::string RootDir = BackupStoreAccounts::GetAccountRoot(rAccount); + ASSERT(RootDir[RootDir.size() - 1] == '/' || + RootDir[RootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR); + + std::string fn(RootDir + "refcount.db"); + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(rAccount.GetDiscSet())); + return RaidFileUtil::MakeWriteFileName(rdiscSet, fn); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::Create(int32_t, +// const std::string &, int, bool) +// Purpose: Create a new database, overwriting an existing +// one only if AllowOverwrite is true. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreRefCountDatabase::Create(const + BackupStoreAccountDatabase::Entry& rAccount, bool AllowOverwrite) +{ + // Initial header + refcount_StreamFormat hdr; + hdr.mMagicValue = htonl(REFCOUNT_MAGIC_VALUE); + hdr.mAccountID = htonl(rAccount.GetID()); + + // Generate the filename + std::string Filename = GetFilename(rAccount); + + // Open the file for writing + if (FileExists(Filename) && !AllowOverwrite) + { + BOX_ERROR("Attempted to overwrite refcount database file: " << + Filename); + THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile); + } + + int flags = O_CREAT | O_BINARY | O_RDWR; + if (!AllowOverwrite) + { + flags |= O_EXCL; + } + + std::auto_ptr<FileStream> DatabaseFile(new FileStream(Filename, flags)); + + // Write header + DatabaseFile->Write(&hdr, sizeof(hdr)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::Load(int32_t AccountID, +// BackupStoreAccountDatabase& rAccountDatabase, +// bool ReadOnly); +// Purpose: Loads the info from disc, given the root +// information. Can be marked as read only. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreRefCountDatabase> BackupStoreRefCountDatabase::Load( + const BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly) +{ + // Generate the filename + std::string filename = GetFilename(rAccount); + int flags = ReadOnly ? O_RDONLY : O_RDWR; + + // Open the file for read/write + std::auto_ptr<FileStream> dbfile(new FileStream(filename, + flags | O_BINARY)); + + // Read in a header + refcount_StreamFormat hdr; + if(!dbfile->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) + } + + // Check it + if(ntohl(hdr.mMagicValue) != REFCOUNT_MAGIC_VALUE || + (int32_t)ntohl(hdr.mAccountID) != rAccount.GetID()) + { + THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) + } + + // Make new object + std::auto_ptr<BackupStoreRefCountDatabase> refcount(new BackupStoreRefCountDatabase(rAccount)); + + // Put in basic location info + refcount->mReadOnly = ReadOnly; + refcount->mapDatabaseFile = dbfile; + + // return it to caller + return refcount; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::Save() +// Purpose: Save modified info back to disc +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +/* +void BackupStoreRefCountDatabase::Save() +{ + // Make sure we're initialised (although should never come to this) + if(mFilename.empty() || mAccount.GetID() == 0) + { + THROW_EXCEPTION(BackupStoreException, Internal) + } + + // Can we do this? + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + // Then... open a write file + RaidFileWrite rf(mAccount.GetDiscSet(), mFilename); + rf.Open(true); // allow overwriting + + // Make header + info_StreamFormat hdr; + hdr.mMagicValue = htonl(INFO_MAGIC_VALUE); + hdr.mAccountID = htonl(mAccountID); + hdr.mClientStoreMarker = box_hton64(mClientStoreMarker); + hdr.mLastObjectIDUsed = box_hton64(mLastObjectIDUsed); + hdr.mBlocksUsed = box_hton64(mBlocksUsed); + hdr.mBlocksInOldFiles = box_hton64(mBlocksInOldFiles); + hdr.mBlocksInDeletedFiles = box_hton64(mBlocksInDeletedFiles); + hdr.mBlocksInDirectories = box_hton64(mBlocksInDirectories); + hdr.mBlocksSoftLimit = box_hton64(mBlocksSoftLimit); + hdr.mBlocksHardLimit = box_hton64(mBlocksHardLimit); + hdr.mCurrentMarkNumber = 0; + hdr.mOptionsPresent = 0; + hdr.mNumberDeletedDirectories = box_hton64(mDeletedDirectories.size()); + + // Write header + rf.Write(&hdr, sizeof(hdr)); + + // Write the deleted object list + if(mDeletedDirectories.size() > 0) + { + int64_t objs[NUM_DELETED_DIRS_BLOCK]; + + int tosave = mDeletedDirectories.size(); + std::vector<int64_t>::iterator i(mDeletedDirectories.begin()); + while(tosave > 0) + { + // How many in this one? + int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave)); + + // Add them + for(int t = 0; t < b; ++t) + { + ASSERT(i != mDeletedDirectories.end()); + objs[t] = box_hton64((*i)); + i++; + } + + // Write + rf.Write(objs, b * sizeof(int64_t)); + + // Number saved + tosave -= b; + } + } + + // Commit it to disc, converting it to RAID now + rf.Commit(true); + + // Mark is as not modified + mIsModified = false; +} +*/ + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::GetRefCount(int64_t +// ObjectID) +// Purpose: Get the number of references to the specified object +// out of the database +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::refcount_t +BackupStoreRefCountDatabase::GetRefCount(int64_t ObjectID) const +{ + IOStream::pos_type offset = GetOffset(ObjectID); + + if (GetSize() < offset + GetEntrySize()) + { + BOX_ERROR("attempted read of unknown refcount for object " << + BOX_FORMAT_OBJECTID(ObjectID)); + THROW_EXCEPTION(BackupStoreException, + UnknownObjectRefCountRequested); + } + + mapDatabaseFile->Seek(offset, SEEK_SET); + + refcount_t refcount; + if (mapDatabaseFile->Read(&refcount, sizeof(refcount)) != + sizeof(refcount)) + { + BOX_LOG_SYS_ERROR("short read on refcount database: " << + mFilename); + THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo); + } + + return ntohl(refcount); +} + +int64_t BackupStoreRefCountDatabase::GetLastObjectIDUsed() const +{ + return (GetSize() - sizeof(refcount_StreamFormat)) / + sizeof(refcount_t); +} + +void BackupStoreRefCountDatabase::AddReference(int64_t ObjectID) +{ + refcount_t refcount; + + if (ObjectID > GetLastObjectIDUsed()) + { + // new object, assume no previous references + refcount = 0; + } + else + { + // read previous value from database + refcount = GetRefCount(ObjectID); + } + + refcount++; + + SetRefCount(ObjectID, refcount); +} + +void BackupStoreRefCountDatabase::SetRefCount(int64_t ObjectID, + refcount_t NewRefCount) +{ + IOStream::pos_type offset = GetOffset(ObjectID); + mapDatabaseFile->Seek(offset, SEEK_SET); + refcount_t RefCountNetOrder = htonl(NewRefCount); + mapDatabaseFile->Write(&RefCountNetOrder, sizeof(RefCountNetOrder)); +} + +bool BackupStoreRefCountDatabase::RemoveReference(int64_t ObjectID) +{ + refcount_t refcount = GetRefCount(ObjectID); // must exist in database + ASSERT(refcount > 0); + refcount--; + SetRefCount(ObjectID, refcount); + return (refcount > 0); +} + diff --git a/lib/backupstore/BackupStoreRefCountDatabase.h b/lib/backupstore/BackupStoreRefCountDatabase.h new file mode 100644 index 00000000..93c79afb --- /dev/null +++ b/lib/backupstore/BackupStoreRefCountDatabase.h @@ -0,0 +1,128 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreRefCountDatabase.h +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREREFCOUNTDATABASE__H +#define BACKUPSTOREREFCOUNTDATABASE__H + +#include <memory> +#include <string> +#include <vector> + +#include "BackupStoreAccountDatabase.h" +#include "FileStream.h" + +class BackupStoreCheck; +class BackupStoreContext; + +// set packing to one byte +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "BeginStructPackForWire.h" +#else +BEGIN_STRUCTURE_PACKING_FOR_WIRE +#endif + +typedef struct +{ + uint32_t mMagicValue; // also the version number + uint32_t mAccountID; +} refcount_StreamFormat; + +// Use default packing +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "EndStructPackForWire.h" +#else +END_STRUCTURE_PACKING_FOR_WIRE +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreRefCountDatabase +// Purpose: Backup store reference count database storage +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- +class BackupStoreRefCountDatabase +{ + friend class BackupStoreCheck; + friend class BackupStoreContext; + friend class HousekeepStoreAccount; + +public: + ~BackupStoreRefCountDatabase(); +private: + // Creation through static functions only + BackupStoreRefCountDatabase(const BackupStoreAccountDatabase::Entry& + rAccount); + // No copying allowed + BackupStoreRefCountDatabase(const BackupStoreRefCountDatabase &); + +public: + // Create a new database for a new account. This method will refuse + // to overwrite any existing file. + static void CreateNew(const BackupStoreAccountDatabase::Entry& rAccount) + { + Create(rAccount, false); + } + + // Load it from the store + static std::auto_ptr<BackupStoreRefCountDatabase> Load(const + BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly); + + typedef uint32_t refcount_t; + + // Data access functions + refcount_t GetRefCount(int64_t ObjectID) const; + int64_t GetLastObjectIDUsed() const; + + // Data modification functions + void AddReference(int64_t ObjectID); + // RemoveReference returns false if refcount drops to zero + bool RemoveReference(int64_t ObjectID); + +private: + // Create a new database for an existing account. Used during + // account checking if opening the old database throws an exception. + // This method will overwrite any existing file. + static void CreateForRegeneration(const + BackupStoreAccountDatabase::Entry& rAccount) + { + Create(rAccount, true); + } + + static void Create(const BackupStoreAccountDatabase::Entry& rAccount, + bool AllowOverwrite); + + static std::string GetFilename(const BackupStoreAccountDatabase::Entry& + rAccount); + IOStream::pos_type GetSize() const + { + return mapDatabaseFile->GetPosition() + + mapDatabaseFile->BytesLeftToRead(); + } + IOStream::pos_type GetEntrySize() const + { + return sizeof(refcount_t); + } + IOStream::pos_type GetOffset(int64_t ObjectID) const + { + return ((ObjectID - 1) * GetEntrySize()) + + sizeof(refcount_StreamFormat); + } + void SetRefCount(int64_t ObjectID, refcount_t NewRefCount); + + // Location information + BackupStoreAccountDatabase::Entry mAccount; + std::string mFilename; + bool mReadOnly; + bool mIsModified; + std::auto_ptr<FileStream> mapDatabaseFile; +}; + +#endif // BACKUPSTOREREFCOUNTDATABASE__H diff --git a/lib/backupstore/StoreStructure.cpp b/lib/backupstore/StoreStructure.cpp new file mode 100644 index 00000000..45a1ce91 --- /dev/null +++ b/lib/backupstore/StoreStructure.cpp @@ -0,0 +1,95 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreStructure.cpp +// Purpose: +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "StoreStructure.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "RaidFileController.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StoreStructure::MakeObjectFilename(int64_t, const std::string &, int, std::string &, bool) +// Purpose: Builds the object filename for a given object, given a root. Optionally ensure that the +// directory exists. +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void StoreStructure::MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists) +{ + const static char *hex = "0123456789abcdef"; + + // Set output to root string + rFilenameOut = rStoreRoot; + + // get the id value from the stored object ID so we can do + // bitwise operations on it. + uint64_t id = (uint64_t)ObjectID; + + // get leafname, shift the bits which make up the leafname off + unsigned int leafname(id & STORE_ID_SEGMENT_MASK); + id >>= STORE_ID_SEGMENT_LENGTH; + + // build pathname + while(id != 0) + { + // assumes that the segments are no bigger than 8 bits + int v = id & STORE_ID_SEGMENT_MASK; + rFilenameOut += hex[(v & 0xf0) >> 4]; + rFilenameOut += hex[v & 0xf]; + rFilenameOut += DIRECTORY_SEPARATOR_ASCHAR; + + // shift the bits we used off the pathname + id >>= STORE_ID_SEGMENT_LENGTH; + } + + // Want to make sure this exists? + if(EnsureDirectoryExists) + { + if(!RaidFileRead::DirectoryExists(DiscSet, rFilenameOut)) + { + // Create it + RaidFileWrite::CreateDirectory(DiscSet, rFilenameOut, true /* recusive */); + } + } + + // append the filename + rFilenameOut += 'o'; + rFilenameOut += hex[(leafname & 0xf0) >> 4]; + rFilenameOut += hex[leafname & 0xf]; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StoreStructure::MakeWriteLockFilename(const std::string &, int, std::string &) +// Purpose: Generate the on disc filename of the write lock file +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void StoreStructure::MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut) +{ + // Find the disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(DiscSet)); + + // Make the filename + std::string writeLockFile(rdiscSet[0] + DIRECTORY_SEPARATOR + rStoreRoot + "write.lock"); + + // Return it to the caller + rFilenameOut = writeLockFile; +} + + diff --git a/lib/backupstore/StoreStructure.h b/lib/backupstore/StoreStructure.h new file mode 100644 index 00000000..ffbe83dd --- /dev/null +++ b/lib/backupstore/StoreStructure.h @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreStructure.h +// Purpose: Functions for placing files in the store +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef STORESTRUCTURE__H +#define STORESTRUCTURE__H + +#include <string> + +#ifdef BOX_RELEASE_BUILD + #define STORE_ID_SEGMENT_LENGTH 8 + #define STORE_ID_SEGMENT_MASK 0xff +#else + // Debug we'll use lots and lots of directories to stress things + #define STORE_ID_SEGMENT_LENGTH 2 + #define STORE_ID_SEGMENT_MASK 0x03 +#endif + + +namespace StoreStructure +{ + void MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists); + void MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut); +}; + +#endif // STORESTRUCTURE__H + diff --git a/lib/common/Archive.h b/lib/common/Archive.h new file mode 100644 index 00000000..b70f12c4 --- /dev/null +++ b/lib/common/Archive.h @@ -0,0 +1,161 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Archive.h +// Purpose: Backup daemon state archive +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- + +#ifndef ARCHIVE__H +#define ARCHIVE__H + +#include <vector> +#include <string> +#include <memory> + +#include "IOStream.h" +#include "Guards.h" + +#define ARCHIVE_GET_SIZE(hdr) (( ((uint8_t)((hdr)[0])) | ( ((uint8_t)((hdr)[1])) << 8)) >> 2) + +#define ARCHIVE_MAGIC_VALUE_RECURSE 0x4449525F +#define ARCHIVE_MAGIC_VALUE_NOOP 0x5449525F + +class Archive +{ +public: + Archive(IOStream &Stream, int Timeout) + : mrStream(Stream) + { + mTimeout = Timeout; + } +private: + // no copying + Archive(const Archive &); + Archive & operator=(const Archive &); +public: + ~Archive() + { + } + // + // + // + void Write(bool Item) + { + Write((int) Item); + } + void Write(int Item) + { + int32_t privItem = htonl(Item); + mrStream.Write(&privItem, sizeof(privItem)); + } + void Write(int64_t Item) + { + int64_t privItem = box_hton64(Item); + mrStream.Write(&privItem, sizeof(privItem)); + } + void Write(uint64_t Item) + { + uint64_t privItem = box_hton64(Item); + mrStream.Write(&privItem, sizeof(privItem)); + } + void Write(uint8_t Item) + { + int privItem = Item; + Write(privItem); + } + void Write(const std::string &Item) + { + int size = Item.size(); + Write(size); + mrStream.Write(Item.c_str(), size); + } + // + // + // + void Read(bool &rItemOut) + { + int privItem; + Read(privItem); + + if (privItem) + { + rItemOut = true; + } + else + { + rItemOut = false; + } + } + void Read(int &rItemOut) + { + int32_t privItem; + if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) + } + rItemOut = ntohl(privItem); + } + void Read(int64_t &rItemOut) + { + int64_t privItem; + if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) + } + rItemOut = box_ntoh64(privItem); + } + void Read(uint64_t &rItemOut) + { + uint64_t privItem; + if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) + } + rItemOut = box_ntoh64(privItem); + } + void Read(uint8_t &rItemOut) + { + int privItem; + Read(privItem); + rItemOut = privItem; + } + void Read(std::string &rItemOut) + { + int size; + Read(size); + + // Assume most strings are relatively small + char buf[256]; + if(size < (int) sizeof(buf)) + { + // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us + if(!mrStream.ReadFullBuffer(buf, size, 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) + } + // assign to this string, storing the header and the extra payload + rItemOut.assign(buf, size); + } + else + { + // Block of memory to hold it + MemoryBlockGuard<char*> dataB(size); + char *ppayload = dataB; + + // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us + if(!mrStream.ReadFullBuffer(ppayload, size, 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead) + } + // assign to this string, storing the header and the extra pPayload + rItemOut.assign(ppayload, size); + } + } +private: + IOStream &mrStream; + int mTimeout; +}; + +#endif // ARCHIVE__H diff --git a/lib/common/BannerText.h b/lib/common/BannerText.h new file mode 100644 index 00000000..e40224da --- /dev/null +++ b/lib/common/BannerText.h @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BannerText.h +// Purpose: Banner text for daemons and utilities +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef BANNERTEXT__H +#define BANNERTEXT__H + +#define BANNER_TEXT(UtilityName) \ + "Box " UtilityName " v" BOX_VERSION ", (c) Ben Summers and " \ + "contributors 2003-2010" + +#endif // BANNERTEXT__H + diff --git a/lib/common/BeginStructPackForWire.h b/lib/common/BeginStructPackForWire.h new file mode 100644 index 00000000..e73bb886 --- /dev/null +++ b/lib/common/BeginStructPackForWire.h @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BeginStructPackForWire.h +// Purpose: Begin structure packing for wire +// Created: 25/11/03 +// +// -------------------------------------------------------------------------- + +// No header guard -- this is intentional + +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS + +#pragma pack(1) + +#else + + logical error -- check BoxPlatform.h and including file + +#endif + + + diff --git a/lib/common/Box.h b/lib/common/Box.h new file mode 100644 index 00000000..158fab7b --- /dev/null +++ b/lib/common/Box.h @@ -0,0 +1,185 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Box.h +// Purpose: Main header file for the Box project +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef BOX__H +#define BOX__H + +// Use the same changes as gcc3 for gcc4 +#ifdef PLATFORM_GCC4 + #define PLATFORM_GCC3 +#endif + +#include "BoxPlatform.h" + +// 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 + +// Show backtraces on exceptions in release builds until further notice +// (they are only logged at TRACE level anyway) +#ifdef HAVE_EXECINFO_H + #define SHOW_BACKTRACE_ON_EXCEPTION +#endif + +#ifdef SHOW_BACKTRACE_ON_EXCEPTION + #include "Utils.h" + #define OPTIONAL_DO_BACKTRACE DumpStackBacktrace(); +#else + #define OPTIONAL_DO_BACKTRACE +#endif + +#include "CommonException.h" +#include "Logging.h" + +#ifndef BOX_RELEASE_BUILD + + extern bool AssertFailuresToSyslog; + #define ASSERT_FAILS_TO_SYSLOG_ON {AssertFailuresToSyslog = true;} + void BoxDebugAssertFailed(const char *cond, const char *file, int line); + #define ASSERT(cond) \ + { \ + if(!(cond)) \ + { \ + BoxDebugAssertFailed(#cond, __FILE__, __LINE__); \ + THROW_EXCEPTION_MESSAGE(CommonException, \ + AssertFailed, #cond); \ + } \ + } + + // Note that syslog tracing is independent of BoxDebugTraceOn, + // but stdout tracing is not + extern bool BoxDebugTraceToSyslog; + #define TRACE_TO_SYSLOG(x) {BoxDebugTraceToSyslog = x;} + extern bool BoxDebugTraceToStdout; + #define TRACE_TO_STDOUT(x) {BoxDebugTraceToStdout = x;} + + extern bool BoxDebugTraceOn; + int BoxDebug_printf(const char *format, ...); + int BoxDebugTrace(const char *format, ...); + + #ifndef PLATFORM_DISABLE_MEM_LEAK_TESTING + #define BOX_MEMORY_LEAK_TESTING + #endif + + // Exception names + #define EXCEPTION_CODENAMES_EXTENDED + +#else + #define ASSERT_FAILS_TO_SYSLOG_ON + #define ASSERT(cond) + + #define TRACE_TO_SYSLOG(x) {} + #define TRACE_TO_STDOUT(x) {} + + // Box Backup builds release get extra information for exception logging + #define EXCEPTION_CODENAMES_EXTENDED + #define EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION + +#endif + +#ifdef BOX_MEMORY_LEAK_TESTING + // Memory leak testing + #include "MemLeakFinder.h" + #define DEBUG_NEW new(__FILE__,__LINE__) + #define MEMLEAKFINDER_NOT_A_LEAK(x) memleakfinder_notaleak(x); + #define MEMLEAKFINDER_NO_LEAKS MemLeakSuppressionGuard _guard; + #define MEMLEAKFINDER_INIT memleakfinder_init(); + #define MEMLEAKFINDER_START {memleakfinder_global_enable = true;} + #define MEMLEAKFINDER_STOP {memleakfinder_global_enable = false;} +#else + #define DEBUG_NEW new + #define MEMLEAKFINDER_NOT_A_LEAK(x) + #define MEMLEAKFINDER_NO_LEAKS + #define MEMLEAKFINDER_INIT + #define MEMLEAKFINDER_START + #define MEMLEAKFINDER_STOP +#endif + +#define THROW_EXCEPTION(type, subtype) \ + { \ + if(!HideExceptionMessageGuard::ExceptionsHidden()) \ + { \ + OPTIONAL_DO_BACKTRACE \ + BOX_WARNING("Exception thrown: " \ + #type "(" #subtype ") " \ + "at " __FILE__ "(" << __LINE__ << ")") \ + } \ + throw type(type::subtype); \ + } + +#define THROW_EXCEPTION_MESSAGE(type, subtype, message) \ + { \ + std::ostringstream _box_throw_line; \ + _box_throw_line << message; \ + if(!HideExceptionMessageGuard::ExceptionsHidden()) \ + { \ + OPTIONAL_DO_BACKTRACE \ + BOX_WARNING("Exception thrown: " \ + #type "(" #subtype ") (" << message << \ + ") at " __FILE__ "(" << __LINE__ << ")") \ + } \ + throw type(type::subtype, _box_throw_line.str()); \ + } + +// extra macros for converting to network byte order +#ifdef HAVE_NETINET_IN_H + #include <netinet/in.h> +#endif + +// Always define a swap64 function, as it's useful. +inline uint64_t box_swap64(uint64_t x) +{ + return ((x & 0xff) << 56 | + (x & 0xff00LL) << 40 | + (x & 0xff0000LL) << 24 | + (x & 0xff000000LL) << 8 | + (x & 0xff00000000LL) >> 8 | + (x & 0xff0000000000LL) >> 24 | + (x & 0xff000000000000LL) >> 40 | + (x & 0xff00000000000000LL) >> 56); +} + +#ifdef WORDS_BIGENDIAN + #define box_hton64(x) (x) + #define box_ntoh64(x) (x) +#elif defined(HAVE_BSWAP64) + #ifdef HAVE_SYS_ENDIAN_H + #include <sys/endian.h> + #endif + #ifdef HAVE_ASM_BYTEORDER_H + #include <asm/byteorder.h> + #endif + + #define box_hton64(x) BSWAP64(x) + #define box_ntoh64(x) BSWAP64(x) +#else + #define box_hton64(x) box_swap64(x) + #define box_ntoh64(x) box_swap64(x) +#endif + +// overloaded auto-conversion functions +inline uint64_t hton(uint64_t in) { return box_hton64(in); } +inline uint32_t hton(uint32_t in) { return htonl(in); } +inline uint16_t hton(uint16_t in) { return htons(in); } +inline uint8_t hton(uint8_t in) { return in; } +inline int64_t hton(int64_t in) { return box_hton64(in); } +inline int32_t hton(int32_t in) { return htonl(in); } +inline int16_t hton(int16_t in) { return htons(in); } +inline int8_t hton(int8_t in) { return in; } +inline uint64_t ntoh(uint64_t in) { return box_ntoh64(in); } +inline uint32_t ntoh(uint32_t in) { return ntohl(in); } +inline uint16_t ntoh(uint16_t in) { return ntohs(in); } +inline uint8_t ntoh(uint8_t in) { return in; } +inline int64_t ntoh(int64_t in) { return box_ntoh64(in); } +inline int32_t ntoh(int32_t in) { return ntohl(in); } +inline int16_t ntoh(int16_t in) { return ntohs(in); } +inline int8_t ntoh(int8_t in) { return in; } + +#endif // BOX__H + diff --git a/lib/common/BoxConfig-MSVC.h b/lib/common/BoxConfig-MSVC.h new file mode 100644 index 00000000..bb3ffb30 --- /dev/null +++ b/lib/common/BoxConfig-MSVC.h @@ -0,0 +1,402 @@ +/* lib/common/BoxConfig.h. Generated by configure. */ +/* lib/common/BoxConfig.h.in. Generated from configure.ac by autoheader. */ +/* Hacked by hand to work for MSVC by Chris Wilson */ + +/* Define to major version for BDB_VERSION */ +/* #undef BDB_VERSION_MAJOR */ + +/* Define to minor version for BDB_VERSION */ +/* #undef BDB_VERSION_MINOR */ + +/* Define to point version for BDB_VERSION */ +/* #undef BDB_VERSION_POINT */ + +/* Name of the 64 bit endian swapping function */ +/* #undef BSWAP64 */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +#define CLOSEDIR_VOID 1 + +/* Define to 1 if non-aligned int16 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT16 */ + +/* Define to 1 if non-aligned int32 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT32 */ + +/* Define to 1 if non-aligned int64 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT64 */ + +/* Define to 1 if you have the <asm/byteorder.h> header file. */ +/* #undef HAVE_ASM_BYTEORDER_H */ + +/* Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian + swapping function */ +/* #undef HAVE_BSWAP64 */ + +/* Define to 1 if you have the <db.h> header file. */ +/* #undef HAVE_DB_H */ + +/* Define to 1 if you have the declaration of `F_SETLK', and to 0 if you + don't. */ +#define HAVE_DECL_F_SETLK 0 + +/* Define to 1 if you have the declaration of `INFTIM', and to 0 if you don't. + */ +#define HAVE_DECL_INFTIM 0 + +/* Define to 1 if you have the declaration of `O_EXLOCK', and to 0 if you + don't. */ +#define HAVE_DECL_O_EXLOCK 0 + +/* Define to 1 if you have the declaration of `SO_PEERCRED', and to 0 if you + don't. */ +#define HAVE_DECL_SO_PEERCRED 0 + +/* Define to 1 if you have the declaration of `XATTR_NOFOLLOW', and to 0 if + you don't. */ +#define HAVE_DECL_XATTR_NOFOLLOW 0 + +/* Define to 1 if you have the declaration of `O_BINARY', and to 0 if you + don't. */ +#define HAVE_DECL_O_BINARY 1 + +/* Define to 1 if #define of pragmas works */ +/* #undef HAVE_DEFINE_PRAGMA */ + +/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_DIRENT_H */ + +/* Define to 1 if you have the <editline/readline.h> header file. */ +/* #undef HAVE_EDITLINE_READLINE_H */ + +/* define if the compiler supports exceptions */ +#define HAVE_EXCEPTIONS + +/* Define to 1 if you have the <execinfo.h> header file. */ +/* #undef HAVE_EXECINFO_H */ + +/* Define to 1 if you have the `flock' function. */ +/* #undef HAVE_FLOCK */ + +/* Define to 1 if you have the `getmntent' function. */ +/* #undef HAVE_GETMNTENT */ + +/* Define to 1 if you have the `getpeereid' function. */ +/* #undef HAVE_GETPEEREID */ + +/* Define to 1 if you have the `getpid' function. */ +// #define HAVE_GETPID 1 + +/* Define to 1 if you have the `getxattr' function. */ +/* #undef HAVE_GETXATTR */ + +/* Define to 1 if you have the <history.h> header file. */ +/* #undef HAVE_HISTORY_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +// #define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef HAVE_KQUEUE */ + +/* Define to 1 if you have the `lchown' function. */ +/* #undef HAVE_LCHOWN */ + +/* Define to 1 if you have the `lgetxattr' function. */ +/* #undef HAVE_LGETXATTR */ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#define HAVE_LIBCRYPTO 1 + +/* Define if you have a readline compatible library */ +/* #undef HAVE_LIBREADLINE */ + +/* Define to 1 if you have the `ssl' library (-lssl). */ +#define HAVE_LIBSSL 1 + +/* Define to 1 if you have the `z' library (-lz). */ +#define HAVE_LIBZ 1 + +/* Define to 1 if you have the `listxattr' function. */ +/* #undef HAVE_LISTXATTR */ + +/* Define to 1 if you have the `llistxattr' function. */ +/* #undef HAVE_LLISTXATTR */ + +/* Define to 1 if syscall lseek requires a dummy middle parameter */ +/* #undef HAVE_LSEEK_DUMMY_PARAM */ + +/* Define to 1 if you have the `lsetxattr' function. */ +/* #undef HAVE_LSETXATTR */ + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <mntent.h> header file. */ +/* #undef HAVE_MNTENT_H */ + +/* Define to 1 if this platform supports mounts */ +/* #undef HAVE_MOUNTS */ + +/* define if the compiler implements namespaces */ +#define HAVE_NAMESPACES + +/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the <netinet/in.h> header file. */ +/* #undef HAVE_NETINET_IN_H */ + +/* Define to 1 if SSL is pre-0.9.7 */ +/* #undef HAVE_OLD_SSL */ + +/* Define to 1 if you have the <openssl/ssl.h> header file. */ +#define HAVE_OPENSSL_SSL_H 1 + +/* Define to 1 if you have the <process.h> header file. */ +#define HAVE_PROCESS_H 1 + +/* Define to 1 if you have the <pwd.h> header file. */ +/* #undef HAVE_PWD_H */ + +/* Define to 1 (and set RANDOM_DEVICE) if a random device is available */ +/* #undef HAVE_RANDOM_DEVICE */ + +/* Define to 1 if you have the <readline.h> header file. */ +/* #undef HAVE_READLINE_H */ + +/* Define if your readline library has add_history */ +/* #undef HAVE_READLINE_HISTORY */ + +/* Define to 1 if you have the <readline/history.h> header file. */ +/* #undef HAVE_READLINE_HISTORY_H */ + +/* Define to 1 if you have the <readline/readline.h> header file. */ +/* #undef HAVE_READLINE_READLINE_H */ + +/* Define to 1 if you have the <regex.h> header file. */ +/* #undef HAVE_REGEX_H */ +#define HAVE_PCREPOSIX_H 1 +#define HAVE_REGEX_SUPPORT 1 + +/* Define to 1 if you have the `setproctitle' function. */ +/* #undef HAVE_SETPROCTITLE */ + +/* Define to 1 if you have the `setxattr' function. */ +/* #undef HAVE_SETXATTR */ + +/* Define to 1 if you have the <signal.h> header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if SSL is available */ +#define HAVE_SSL 1 + +/* Define to 1 if you have the `statfs' function. */ +/* #undef HAVE_STATFS */ + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if stdbool.h conforms to C99. */ +#define HAVE_STDBOOL_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +// #define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if `d_type' is member of `struct dirent'. */ +/* #undef HAVE_STRUCT_DIRENT_D_TYPE */ + +/* Define to 1 if `mnt_dir' is member of `struct mntent'. */ +/* #undef HAVE_STRUCT_MNTENT_MNT_DIR */ + +/* Define to 1 if `mnt_mountp' is member of `struct mnttab'. */ +/* #undef HAVE_STRUCT_MNTTAB_MNT_MOUNTP */ + +/* Define to 1 if `sin_len' is member of `struct sockaddr_in'. */ +/* #undef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + +/* Define to 1 if `f_mntonname' is member of `struct statfs'. */ +/* #undef HAVE_STRUCT_STATFS_F_MNTONNAME */ + +/* Define to 1 if `st_flags' is member of `struct stat'. */ +/* #undef HAVE_STRUCT_STAT_ST_FLAGS */ + +/* Define to 1 if `st_mtimespec' is member of `struct stat'. */ +/* #undef HAVE_STRUCT_STAT_ST_MTIMESPEC */ + +/* Define to 1 if you have the `syscall' function. */ +/* #undef HAVE_SYSCALL */ + +/* Define to 1 if you have the <syslog.h> header file. */ +/* #undef HAVE_SYSLOG_H */ + +/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the <sys/endian.h> header file. */ +/* #undef HAVE_SYS_ENDIAN_H */ + +/* Define to 1 if you have the <sys/mnttab.h> header file. */ +/* #undef HAVE_SYS_MNTTAB_H */ + +/* Define to 1 if you have the <sys/mount.h> header file. */ +/* #undef HAVE_SYS_MOUNT_H */ + +/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the <sys/param.h> header file. */ +// #define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/socket.h> header file. */ +/* #undef HAVE_SYS_SOCKET_H */ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/syscall.h> header file. */ +/* #undef HAVE_SYS_SYSCALL_H */ + +/* Define to 1 if you have the <sys/time.h> header file. */ +// #define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +// #define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <sys/wait.h> header file. */ +/* #undef HAVE_SYS_WAIT_H */ + +/* Define to 1 if you have the <sys/xattr.h> header file. */ +/* #undef HAVE_SYS_XATTR_H */ + +/* Define to 1 if you have the <time.h> header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +// #define HAVE_UNISTD_H 1 + +/* Define to 1 if the system has the type `u_int16_t'. */ +/* #undef HAVE_U_INT16_T */ + +/* Define to 1 if the system has the type `u_int32_t'. */ +/* #undef HAVE_U_INT32_T */ + +/* Define to 1 if the system has the type `u_int64_t'. */ +/* #undef HAVE_U_INT64_T */ + +/* Define to 1 if the system has the type `u_int8_t'. */ +/* #undef HAVE_U_INT8_T */ + +/* Define to 1 if struct dirent.d_type is valid */ +/* #undef HAVE_VALID_DIRENT_D_TYPE */ + +/* Define to 1 if the system has the type `_Bool'. */ +/* #undef HAVE__BOOL */ + +/* Define to 1 if you have the `__syscall' function. */ +/* #undef HAVE___SYSCALL */ + +/* Define to 1 if __syscall is available but needs a definition */ +/* #undef HAVE___SYSCALL_NEED_DEFN */ + +/* max value of long long calculated by configure */ +/* #undef LLONG_MAX */ + +/* min value of long long calculated by configure */ +/* #undef LLONG_MIN */ + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +/* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "box@fluffy.co.uk" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "Box Backup" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "Box Backup 0.11" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "box-backup" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.09" + +/* Define to the filename of the random device (and set HAVE_RANDOM_DEVICE) */ +/* #undef RANDOM_DEVICE */ + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* TMP directory name */ +#define TEMP_DIRECTORY_NAME "/tmp" + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to 1 if your <sys/time.h> declares `struct tm'. */ +/* #undef TM_IN_SYS_TIME */ + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +/* #undef WORDS_BIGENDIAN */ + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Define to 1 if __USE_MALLOC is required work around STL memory leaks */ +/* #undef __USE_MALLOC */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `int' if <sys/types.h> doesn't define. */ +#define gid_t int + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef mode_t */ + +/* Define to `long' if <sys/types.h> does not define. */ +/* #undef off_t */ + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned' if <sys/types.h> does not define. */ +/* #undef size_t */ + +/* Define to `int' if <sys/types.h> doesn't define. */ +#define uid_t int diff --git a/lib/common/BoxException.cpp b/lib/common/BoxException.cpp new file mode 100644 index 00000000..2503ca63 --- /dev/null +++ b/lib/common/BoxException.cpp @@ -0,0 +1,21 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxException.cpp +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BoxException.h" + +#include "MemLeakFindOn.h" + +BoxException::BoxException() +{ +} + +BoxException::~BoxException() throw () +{ +} diff --git a/lib/common/BoxException.h b/lib/common/BoxException.h new file mode 100644 index 00000000..a8f5d7a6 --- /dev/null +++ b/lib/common/BoxException.h @@ -0,0 +1,38 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxException.h +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#ifndef BOXEXCEPTION__H +#define BOXEXCEPTION__H + +#include <exception> +#include <string> + +// -------------------------------------------------------------------------- +// +// Class +// Name: BoxException +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +class BoxException : public std::exception +{ +public: + BoxException(); + ~BoxException() throw (); + + virtual unsigned int GetType() const throw() = 0; + virtual unsigned int GetSubType() const throw() = 0; + +private: +}; + + +#endif // BOXEXCEPTION__H + diff --git a/lib/common/BoxPlatform.h b/lib/common/BoxPlatform.h new file mode 100644 index 00000000..617aa031 --- /dev/null +++ b/lib/common/BoxPlatform.h @@ -0,0 +1,201 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxPlatform.h +// Purpose: Specifies what each platform supports in more detail, and includes +// extra files to get basic support for types. +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- + +#ifndef BOXPLATFORM__H +#define BOXPLATFORM__H + +#ifdef WIN32 +#define DIRECTORY_SEPARATOR "\\" +#define DIRECTORY_SEPARATOR_ASCHAR '\\' +#else +#define DIRECTORY_SEPARATOR "/" +#define DIRECTORY_SEPARATOR_ASCHAR '/' +#endif + +#define PLATFORM_DEV_NULL "/dev/null" + +#ifdef _MSC_VER +#include "BoxConfig-MSVC.h" +#include "BoxVersion.h" +#else +#include "BoxConfig.h" +#endif + +#ifdef WIN32 + #ifdef __MSVCRT_VERSION__ + #if __MSVCRT_VERSION__ < 0x0601 + #error Must include Box.h before sys/types.h + #endif + #else + // need msvcrt version 6.1 or higher for _gmtime64() + // must define this before importing <sys/types.h> + #define __MSVCRT_VERSION__ 0x0601 + #endif +#endif + +#ifdef HAVE_SYS_TYPES_H + #include <sys/types.h> +#endif +#ifdef HAVE_INTTYPES_H + #include <inttypes.h> +#else + #ifdef HAVE_STDINT_H + #include <stdint.h> + #endif +#endif + +// Slight hack; disable interception in raidfile test on Darwin and Windows +#if defined __APPLE__ || defined WIN32 + // TODO: Replace with autoconf test + #define PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE +#endif + +// Disable memory testing under Darwin, it just doesn't like it very much. +#ifdef __APPLE__ + // TODO: We really should get some decent leak detection code. + #define PLATFORM_DISABLE_MEM_LEAK_TESTING +#endif + +// Darwin also has a weird idea of permissions and dates on symlinks: +// perms are fixed at creation time by your umask, and dates can't be +// changed. This breaks unit tests if we try to compare these things. +// See: http://lists.apple.com/archives/darwin-kernel/2006/Dec/msg00057.html +#ifdef __APPLE__ + #define PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE +#endif + +// Find out if credentials on UNIX sockets can be obtained +#ifdef HAVE_GETPEEREID + // +#elif HAVE_DECL_SO_PEERCRED + // +#elif defined HAVE_UCRED_H && HAVE_GETPEERUCRED + // +#else + #define PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET +#endif + +#ifdef HAVE_DEFINE_PRAGMA + // set packing to one bytes (can't use push/pop on gcc) + #define BEGIN_STRUCTURE_PACKING_FOR_WIRE #pragma pack(1) + + // Use default packing + #define END_STRUCTURE_PACKING_FOR_WIRE #pragma pack() +#else + #define STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#endif + +// Handle differing xattr APIs +#ifdef HAVE_SYS_XATTR_H + #if !defined(HAVE_LLISTXATTR) && defined(HAVE_LISTXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define llistxattr(a,b,c) listxattr(a,b,c,XATTR_NOFOLLOW) + #endif + #if !defined(HAVE_LGETXATTR) && defined(HAVE_GETXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define lgetxattr(a,b,c,d) getxattr(a,b,c,d,0,XATTR_NOFOLLOW) + #endif + #if !defined(HAVE_LSETXATTR) && defined(HAVE_SETXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define lsetxattr(a,b,c,d,e) setxattr(a,b,c,d,0,(e)|XATTR_NOFOLLOW) + #endif +#endif + +#if defined WIN32 && !defined __MINGW32__ + typedef __int8 int8_t; + typedef __int16 int16_t; + typedef __int32 int32_t; + typedef __int64 int64_t; + + typedef unsigned __int8 u_int8_t; + typedef unsigned __int16 u_int16_t; + typedef unsigned __int32 u_int32_t; + typedef unsigned __int64 u_int64_t; + + #define HAVE_U_INT8_T + #define HAVE_U_INT16_T + #define HAVE_U_INT32_T + #define HAVE_U_INT64_T +#endif // WIN32 && !__MINGW32__ + +// Define missing types +#ifndef HAVE_UINT8_T + typedef u_int8_t uint8_t; +#endif + +#ifndef HAVE_UINT16_T + typedef u_int16_t uint16_t; +#endif + +#ifndef HAVE_UINT32_T + typedef u_int32_t uint32_t; +#endif + +#ifndef HAVE_UINT64_T + typedef u_int64_t uint64_t; +#endif + +#ifndef HAVE_U_INT8_T + typedef uint8_t u_int8_t; +#endif + +#ifndef HAVE_U_INT16_T + typedef uint16_t u_int16_t; +#endif + +#ifndef HAVE_U_INT32_T + typedef uint32_t u_int32_t; +#endif + +#ifndef HAVE_U_INT64_T + typedef uint64_t u_int64_t; +#endif + +#if !HAVE_DECL_INFTIM + #define INFTIM -1 +#endif + +// for Unix compatibility with Windows :-) +#ifndef O_BINARY + #define O_BINARY 0 +#endif + +#ifdef WIN32 + typedef u_int64_t InodeRefType; +#else + typedef ino_t InodeRefType; +#endif + +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN +#endif + +#include "emu.h" + +#ifdef WIN32 + #define INVALID_FILE INVALID_HANDLE_VALUE + typedef HANDLE tOSFileHandle; +#else + #define INVALID_FILE -1 + typedef int tOSFileHandle; +#endif + +// Solaris has no dirfd(x) macro or function, and we need one for +// intercept tests. We cannot define macros with arguments directly +// using AC_DEFINE, so do it here instead of in configure.ac. + +#if ! defined PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE && ! HAVE_DECL_DIRFD + #ifdef HAVE_DIR_D_FD + #define dirfd(x) (x)->d_fd + #elif defined HAVE_DIR_DD_FD + #define dirfd(x) (x)->dd_fd + #else + #error No way to get file descriptor from DIR structure + #endif +#endif + +#endif // BOXPLATFORM__H diff --git a/lib/common/BoxPortsAndFiles.h.in b/lib/common/BoxPortsAndFiles.h.in new file mode 100644 index 00000000..41bad0ba --- /dev/null +++ b/lib/common/BoxPortsAndFiles.h.in @@ -0,0 +1,44 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxPortsAndFiles.h +// Purpose: Central list of which tcp/ip ports and hardcoded file locations +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BOXPORTSANDFILES__H +#define BOXPORTSANDFILES__H + +#define BOX_PORT_BASE 2200 + + +// Backup store daemon +#define BOX_PORT_BBSTORED (BOX_PORT_BASE+1) +#define BOX_PORT_BBSTORED_TEST 22011 + +// directory within the RAIDFILE root for the backup store daemon +#define BOX_RAIDFILE_ROOT_BBSTORED "backup" + +// configuration file paths +#ifdef WIN32 + // no default config file path, use these macros to call + // GetDefaultConfigFilePath() instead. + + #define BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE \ + GetDefaultConfigFilePath("bbackupd.conf").c_str() + #define BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE \ + GetDefaultConfigFilePath("raidfile.conf").c_str() + #define BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE \ + GetDefaultConfigFilePath("bbstored.conf").c_str() +#else +#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" +#endif + +#endif // BOXPORTSANDFILES__H + diff --git a/lib/common/BoxTime.cpp b/lib/common/BoxTime.cpp new file mode 100644 index 00000000..d05c0a6c --- /dev/null +++ b/lib/common/BoxTime.cpp @@ -0,0 +1,96 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTime.cpp +// Purpose: Time for the box +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#ifdef HAVE_TIME_H + #include <time.h> +#endif + +#include <errno.h> +#include <string.h> + +#include "BoxTime.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: GetCurrentBoxTime() +// Purpose: Returns the current time as a box time. +// (1 sec precision, or better if supported by system) +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +box_time_t GetCurrentBoxTime() +{ + #ifdef HAVE_GETTIMEOFDAY + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) + { + BOX_LOG_SYS_ERROR("Failed to gettimeofday(), " + "dropping precision"); + } + else + { + box_time_t timeNow = (tv.tv_sec * MICRO_SEC_IN_SEC_LL) + + tv.tv_usec; + return timeNow; + } + #endif + + return SecondsToBoxTime(time(0)); +} + +std::string FormatTime(box_time_t time, bool includeDate, bool showMicros) +{ + std::ostringstream buf; + + time_t seconds = BoxTimeToSeconds(time); + int micros = BoxTimeToMicroSeconds(time) % MICRO_SEC_IN_SEC; + + struct tm tm_now, *tm_ptr = &tm_now; + + #ifdef WIN32 + if ((tm_ptr = localtime(&seconds)) != NULL) + #else + if (localtime_r(&seconds, &tm_now) != NULL) + #endif + { + buf << std::setfill('0'); + + if (includeDate) + { + buf << std::setw(4) << (tm_ptr->tm_year + 1900) << "-" << + std::setw(2) << (tm_ptr->tm_mon + 1) << "-" << + std::setw(2) << (tm_ptr->tm_mday) << " "; + } + + buf << std::setw(2) << tm_ptr->tm_hour << ":" << + std::setw(2) << tm_ptr->tm_min << ":" << + std::setw(2) << tm_ptr->tm_sec; + + if (showMicros) + { + buf << "." << std::setw(6) << micros; + } + } + else + { + buf << strerror(errno); + } + + return buf.str(); +} + diff --git a/lib/common/BoxTime.h b/lib/common/BoxTime.h new file mode 100644 index 00000000..6681bbbd --- /dev/null +++ b/lib/common/BoxTime.h @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTime.h +// Purpose: How time is represented +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BOXTIME__H +#define BOXTIME__H + +// Time is presented as an unsigned 64 bit integer, in microseconds +typedef uint64_t box_time_t; + +#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) +#define MILLI_SEC_IN_NANO_SEC (1000) +#define MILLI_SEC_IN_NANO_SEC_LL (1000LL) + +box_time_t GetCurrentBoxTime(); + +inline box_time_t SecondsToBoxTime(time_t Seconds) +{ + return ((box_time_t)Seconds * MICRO_SEC_IN_SEC_LL); +} +inline time_t BoxTimeToSeconds(box_time_t Time) +{ + return Time / MICRO_SEC_IN_SEC_LL; +} +inline uint64_t BoxTimeToMilliSeconds(box_time_t Time) +{ + return Time / MILLI_SEC_IN_NANO_SEC_LL; +} +inline uint64_t BoxTimeToMicroSeconds(box_time_t Time) +{ + return Time; +} + +std::string FormatTime(box_time_t time, bool includeDate, + bool showMicros = false); + +#endif // BOXTIME__H diff --git a/lib/common/BoxTimeToText.cpp b/lib/common/BoxTimeToText.cpp new file mode 100644 index 00000000..dbeefe1b --- /dev/null +++ b/lib/common/BoxTimeToText.cpp @@ -0,0 +1,76 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToText.cpp +// Purpose: Convert box time to text +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> +#include <time.h> +#include <stdio.h> + +#include "BoxTimeToText.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxTimeToISO8601String(box_time_t, bool) +// Purpose: Convert a 64 bit box time to a ISO 8601 compliant +// string, either in local or UTC time +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +std::string BoxTimeToISO8601String(box_time_t Time, bool localTime) +{ + time_t timeInSecs = BoxTimeToSeconds(Time); + char str[128]; // more than enough space + +#ifdef WIN32 + struct tm *time; + __time64_t winTime = timeInSecs; + + if(localTime) + { + time = _localtime64(&winTime); + } + else + { + time = _gmtime64(&winTime); + } + + if(time == NULL) + { + // ::sprintf(str, "%016I64x ", bob); + return std::string("unable to convert time"); + } + + sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time->tm_year + 1900, + time->tm_mon + 1, time->tm_mday, time->tm_hour, + time->tm_min, time->tm_sec); +#else // ! WIN32 + struct tm time; + + if(localTime) + { + localtime_r(&timeInSecs, &time); + } + else + { + gmtime_r(&timeInSecs, &time); + } + + sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time.tm_year + 1900, + time.tm_mon + 1, time.tm_mday, time.tm_hour, + time.tm_min, time.tm_sec); +#endif // WIN32 + + return std::string(str); +} + + diff --git a/lib/common/BoxTimeToText.h b/lib/common/BoxTimeToText.h new file mode 100644 index 00000000..21fa5d57 --- /dev/null +++ b/lib/common/BoxTimeToText.h @@ -0,0 +1,19 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToText.h +// Purpose: Convert box time to text +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BOXTIMETOTEXT__H +#define BOXTIMETOTEXT__H + +#include <string> +#include "BoxTime.h" + +std::string BoxTimeToISO8601String(box_time_t Time, bool localTime); + +#endif // BOXTIMETOTEXT__H + diff --git a/lib/common/BoxTimeToUnix.h b/lib/common/BoxTimeToUnix.h new file mode 100644 index 00000000..f8a8797e --- /dev/null +++ b/lib/common/BoxTimeToUnix.h @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToUnix.h +// Purpose: Convert times in 64 bit values to UNIX structures +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- + +#ifndef FILEMODIFICATIONTIMETOTIMEVAL__H +#define FILEMODIFICATIONTIMETOTIMEVAL__H + +#ifdef WIN32 +#include <time.h> +#else +#include <sys/time.h> +#endif + +#include "BoxTime.h" + +inline void BoxTimeToTimeval(box_time_t Time, struct timeval &tv) +{ + tv.tv_sec = (long)(Time / MICRO_SEC_IN_SEC_LL); + tv.tv_usec = (long)(Time % MICRO_SEC_IN_SEC_LL); +} + +inline void BoxTimeToTimespec(box_time_t Time, struct timespec &tv) +{ + tv.tv_sec = (time_t)(Time / MICRO_SEC_IN_SEC_LL); + tv.tv_nsec = ((long)(Time % MICRO_SEC_IN_SEC_LL)) * NANO_SEC_IN_USEC; +} + +#endif // FILEMODIFICATIONTIMETOTIMEVAL__H + diff --git a/lib/common/BufferedStream.cpp b/lib/common/BufferedStream.cpp new file mode 100644 index 00000000..b58253f3 --- /dev/null +++ b/lib/common/BufferedStream.cpp @@ -0,0 +1,207 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedStream.cpp +// Purpose: Buffering read-only wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BufferedStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::BufferedStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +BufferedStream::BufferedStream(IOStream& rSource) +: mrSource(rSource), mBufferSize(0), mBufferPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int BufferedStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if (mBufferSize == mBufferPosition) + { + // buffer is empty, fill it. + + int numBytesRead = mrSource.Read(mBuffer, sizeof(mBuffer), + Timeout); + + if (numBytesRead < 0) + { + return numBytesRead; + } + + mBufferSize = numBytesRead; + } + + int sizeToReturn = mBufferSize - mBufferPosition; + + if (sizeToReturn > NBytes) + { + sizeToReturn = NBytes; + } + + memcpy(pBuffer, mBuffer + mBufferPosition, sizeToReturn); + mBufferPosition += sizeToReturn; + + if (mBufferPosition == mBufferSize) + { + // clear out the buffer + mBufferSize = 0; + mBufferPosition = 0; + } + + return sizeToReturn; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedStream::BytesLeftToRead() +{ + return mrSource.BytesLeftToRead() + mBufferSize - mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedStream::GetPosition() const +{ + return mrSource.GetPosition() - mBufferSize + mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + switch (SeekType) + { + case SeekType_Absolute: + { + // just go there + mrSource.Seek(Offset, SeekType); + } + break; + + case SeekType_Relative: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to subtract that amount from the seek + // to seek forward that much less, putting the + // real pointer in the right place. + mrSource.Seek(Offset - mBufferSize + mBufferPosition, + SeekType); + } + break; + + case SeekType_End: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to add that amount to the seek + // to seek backwards that much more, putting the + // real pointer in the right place. + mrSource.Seek(Offset + mBufferSize - mBufferPosition, + SeekType); + } + } + + // always clear the buffer for now (may be slightly wasteful) + mBufferSize = 0; + mBufferPosition = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedStream::StreamDataLeft() +{ + return mrSource.StreamDataLeft(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedStream::StreamClosed() +{ + return mrSource.StreamClosed(); +} + diff --git a/lib/common/BufferedStream.h b/lib/common/BufferedStream.h new file mode 100644 index 00000000..079c482a --- /dev/null +++ b/lib/common/BufferedStream.h @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedStream.h +// Purpose: Buffering read-only wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#ifndef BUFFEREDSTREAM__H +#define BUFFEREDSTREAM__H + +#include "IOStream.h" + +class BufferedStream : public IOStream +{ +private: + IOStream& mrSource; + char mBuffer[4096]; + int mBufferSize; + int mBufferPosition; + +public: + BufferedStream(IOStream& rSource); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + BufferedStream(const BufferedStream &rToCopy) + : mrSource(rToCopy.mrSource) { /* do not call */ } +}; + +#endif // BUFFEREDSTREAM__H + + diff --git a/lib/common/BufferedWriteStream.cpp b/lib/common/BufferedWriteStream.cpp new file mode 100644 index 00000000..797be00d --- /dev/null +++ b/lib/common/BufferedWriteStream.cpp @@ -0,0 +1,181 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedWriteStream.cpp +// Purpose: Buffering write-only wrapper around IOStreams +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BufferedWriteStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::BufferedWriteStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +BufferedWriteStream::BufferedWriteStream(IOStream& rSink) +: mrSink(rSink), mBufferPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Read(void *, int) +// Purpose: Reads bytes from the file - throws exception +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int BufferedWriteStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedWriteStream::BytesLeftToRead() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Write(const void *pBuffer, int NBytes) +{ + int numBytesRemain = NBytes; + + do + { + int maxWritable = sizeof(mBuffer) - mBufferPosition; + int numBytesToWrite = (numBytesRemain < maxWritable) ? + numBytesRemain : maxWritable; + + if(numBytesToWrite > 0) + { + memcpy(mBuffer + mBufferPosition, pBuffer, + numBytesToWrite); + mBufferPosition += numBytesToWrite; + pBuffer = ((const char *)pBuffer) + numBytesToWrite; + numBytesRemain -= numBytesToWrite; + } + + if(numBytesRemain > 0) + { + Flush(); + } + } + while(numBytesRemain > 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedWriteStream::GetPosition() const +{ + return mrSink.GetPosition() + mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + // Always flush the buffer before seeking + Flush(); + + mrSink.Seek(Offset, SeekType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Flush(); +// Purpose: Write out current buffer contents and invalidate +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Flush(int Timeout) +{ + if(mBufferPosition > 0) + { + mrSink.Write(mBuffer, mBufferPosition); + } + + mBufferPosition = 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Close() +{ + Flush(); + mrSink.Close(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedWriteStream::StreamDataLeft() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedWriteStream::StreamClosed() +{ + return mrSink.StreamClosed(); +} + diff --git a/lib/common/BufferedWriteStream.h b/lib/common/BufferedWriteStream.h new file mode 100644 index 00000000..7a1c8c17 --- /dev/null +++ b/lib/common/BufferedWriteStream.h @@ -0,0 +1,44 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedWriteStream.h +// Purpose: Buffering write-only wrapper around IOStreams +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- + +#ifndef BUFFEREDWRITESTREAM__H +#define BUFFEREDWRITESTREAM__H + +#include "IOStream.h" + +class BufferedWriteStream : public IOStream +{ +private: + IOStream& mrSink; + char mBuffer[4096]; + int mBufferPosition; + +public: + BufferedWriteStream(IOStream& rSource); + virtual ~BufferedWriteStream() { Close(); } + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Flush(int Timeout = IOStream::TimeOutInfinite); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + BufferedWriteStream(const BufferedWriteStream &rToCopy) + : mrSink(rToCopy.mrSink) { /* do not call */ } +}; + +#endif // BUFFEREDWRITESTREAM__H + + diff --git a/lib/common/CollectInBufferStream.cpp b/lib/common/CollectInBufferStream.cpp new file mode 100644 index 00000000..90e2e7bc --- /dev/null +++ b/lib/common/CollectInBufferStream.cpp @@ -0,0 +1,274 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CollectInBufferStream.cpp +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "CollectInBufferStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +#define INITIAL_BUFFER_SIZE 1024 +#define MAX_BUFFER_ADDITION (1024*64) + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::CollectInBufferStream() +// Purpose: Constructor +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +CollectInBufferStream::CollectInBufferStream() + : mBuffer(INITIAL_BUFFER_SIZE), + mBufferSize(INITIAL_BUFFER_SIZE), + mBytesInBuffer(0), + mReadPosition(0), + mInWritePhase(true) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::~CollectInBufferStream() +// Purpose: Destructor +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +CollectInBufferStream::~CollectInBufferStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Read(void *, int, int) +// Purpose: As interface. But only works in read phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +int CollectInBufferStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Adjust to number of bytes left + if(NBytes > (mBytesInBuffer - mReadPosition)) + { + NBytes = (mBytesInBuffer - mReadPosition); + } + ASSERT(NBytes >= 0); + if(NBytes <= 0) return 0; // careful now + + // Copy in the requested number of bytes and adjust the read pointer + ::memcpy(pBuffer, ((char*)mBuffer) + mReadPosition, NBytes); + mReadPosition += NBytes; + + return NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::BytesLeftToRead() +// Purpose: As interface. But only works in read phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type CollectInBufferStream::BytesLeftToRead() +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + return (mBytesInBuffer - mReadPosition); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Write(void *, int) +// Purpose: As interface. But only works in write phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Write(const void *pBuffer, int NBytes) +{ + if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Enough space in the buffer + if((mBytesInBuffer + NBytes) > mBufferSize) + { + // Need to reallocate... what's the block size we'll use? + int allocateBlockSize = mBufferSize; + if(allocateBlockSize > MAX_BUFFER_ADDITION) + { + allocateBlockSize = MAX_BUFFER_ADDITION; + } + + // Write it the easy way. Although it's not the most efficient... + int newSize = mBufferSize; + while(newSize < (mBytesInBuffer + NBytes)) + { + newSize += allocateBlockSize; + } + + // Reallocate buffer + mBuffer.Resize(newSize); + + // Store new size + mBufferSize = newSize; + } + + // Copy in data and adjust counter + ::memcpy(((char*)mBuffer) + mBytesInBuffer, pBuffer, NBytes); + mBytesInBuffer += NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetPosition() +// Purpose: In write phase, returns the number of bytes written, in read +// phase, the number of bytes to go +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type CollectInBufferStream::GetPosition() const +{ + return mInWritePhase?mBytesInBuffer:mReadPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Seek(pos_type, int) +// Purpose: As interface. But read phase only. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Seek(pos_type Offset, int SeekType) +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + int newPos = 0; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newPos = Offset; + break; + case IOStream::SeekType_Relative: + newPos = mReadPosition + Offset; + break; + case IOStream::SeekType_End: + newPos = mBytesInBuffer + Offset; + break; + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + break; + } + + // Make sure it doesn't go over + if(newPos > mBytesInBuffer) + { + newPos = mBytesInBuffer; + } + // or under + if(newPos < 0) + { + newPos = 0; + } + + // Set the new read position + mReadPosition = newPos; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::StreamDataLeft() +// Purpose: As interface +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool CollectInBufferStream::StreamDataLeft() +{ + return mInWritePhase?(false):(mReadPosition < mBytesInBuffer); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::StreamClosed() +// Purpose: As interface +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool CollectInBufferStream::StreamClosed() +{ + return !mInWritePhase; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::SetForReading() +// Purpose: Switch to read phase, after all data written +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::SetForReading() +{ + if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Move to read phase + mInWritePhase = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetBuffer() +// Purpose: Returns the buffer +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void *CollectInBufferStream::GetBuffer() const +{ + return mBuffer.GetPtr(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetSize() +// Purpose: Returns the buffer size +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +int CollectInBufferStream::GetSize() const +{ + return mBytesInBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Reset() +// Purpose: Reset the stream, so it is empty and ready to be written to. +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Reset() +{ + mInWritePhase = true; + mBytesInBuffer = 0; + mReadPosition = 0; +} + diff --git a/lib/common/CollectInBufferStream.h b/lib/common/CollectInBufferStream.h new file mode 100644 index 00000000..d73af8db --- /dev/null +++ b/lib/common/CollectInBufferStream.h @@ -0,0 +1,60 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CollectInBufferStream.h +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#ifndef COLLECTINBUFFERSTREAM__H +#define COLLECTINBUFFERSTREAM__H + +#include "IOStream.h" +#include "Guards.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CollectInBufferStream +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +class CollectInBufferStream : public IOStream +{ +public: + CollectInBufferStream(); + ~CollectInBufferStream(); +private: + // No copying + CollectInBufferStream(const CollectInBufferStream &); + CollectInBufferStream(const IOStream &); +public: + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + void SetForReading(); + + void Reset(); + + void *GetBuffer() const; + int GetSize() const; + bool IsSetForReading() const {return !mInWritePhase;} + +private: + MemoryBlockGuard<char*> mBuffer; + int mBufferSize; + int mBytesInBuffer; + int mReadPosition; + bool mInWritePhase; +}; + +#endif // COLLECTINBUFFERSTREAM__H + diff --git a/lib/common/CommonException.h b/lib/common/CommonException.h new file mode 100644 index 00000000..a0eb3bf5 --- /dev/null +++ b/lib/common/CommonException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CommonException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef COMMONEXCEPTION__H +#define COMMONEXCEPTION__H + +// Compatibility header with old non-autogen exception scheme +#include "autogen_CommonException.h" + +#endif // COMMONEXCEPTION__H + diff --git a/lib/common/CommonException.txt b/lib/common/CommonException.txt new file mode 100644 index 00000000..b2819886 --- /dev/null +++ b/lib/common/CommonException.txt @@ -0,0 +1,47 @@ + +# NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. + + +EXCEPTION Common 1 + +Internal 0 +AssertFailed 1 +OSFileOpenError 2 Can't open a file -- attempted to load a non-existant config file or bad file referenced within? +OSFileCloseError 3 +FileAlreadyClosed 4 +BadArguments 5 +ConfigNoKey 6 +ConfigNoSubConfig 7 +GetLineNoHandle 8 +OSFileError 9 Error accessing a file. Check permissions. +GetLineEOF 10 +ConfigBadIntValue 11 +GetLineTooLarge 12 Protects against very large lines using up lots of memory. +NotSupported 13 +OSFileReadError 14 +OSFileWriteError 15 +FileClosed 16 +IOStreamBadSeekType 17 +CantWriteToPartialReadStream 18 +CollectInBufferStreamNotInCorrectPhase 19 +NamedLockAlreadyLockingSomething 20 +NamedLockNotHeld 21 +StreamableMemBlockIncompleteRead 22 +MemBlockStreamNotSupported 23 +StreamDoesntHaveRequiredProperty 24 +CannotWriteToReadGatherStream 25 +ReadGatherStreamAddingBadBlock 26 +CouldNotLookUpUsername 27 +CouldNotRestoreProcessUser 28 +CouldNotChangeProcessUser 29 +RegexNotSupportedOnThisPlatform 30 Your platform does not have built in regular expression libraries. +BadRegularExpression 31 +CouldNotCreateKQueue 32 +KEventErrorAdd 33 +KEventErrorWait 34 +KEventErrorRemove 35 +KQueueNotSupportedOnThisPlatform 36 +IOStreamGetLineNotEnoughDataToIgnore 37 Bad value passed to IOStreamGetLine::IgnoreBufferedData() +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. diff --git a/lib/common/Configuration.cpp b/lib/common/Configuration.cpp new file mode 100644 index 00000000..f49f3c6e --- /dev/null +++ b/lib/common/Configuration.cpp @@ -0,0 +1,920 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Configuration.cpp +// Purpose: Reading configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include <sstream> + +#include "Configuration.h" +#include "CommonException.h" +#include "Guards.h" +#include "FdGetLine.h" + +#include "MemLeakFindOn.h" + +#include <cstring> + +// utility whitespace function +inline bool iw(int c) +{ + return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded +} + +// boolean values +static const char *sValueBooleanStrings[] = {"yes", "true", "no", "false", 0}; +static const bool sValueBooleanValue[] = {true, true, false, false}; + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + void *testFunction +) +: mName(name), + mHasDefaultValue(false), + mFlags(flags), + mTestFunction(testFunction) +{ } + +// to allow passing NULL for default ListenAddresses + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + NoDefaultValue_t t, + void *testFunction +) +: mName(name), + mHasDefaultValue(false), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + std::string defaultValue, + void *testFunction +) +: mName(name), + mDefaultValue(defaultValue), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + const char *defaultValue, + void *testFunction +) +: mName(name), + mDefaultValue(defaultValue), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + int defaultValue, + void *testFunction +) +: mName(name), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ + ASSERT(flags & ConfigTest_IsInt); + std::ostringstream val; + val << defaultValue; + mDefaultValue = val.str(); +} + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + bool defaultValue, + void *testFunction +) +: mName(name), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ + ASSERT(flags & ConfigTest_IsBool); + mDefaultValue = defaultValue ? "yes" : "no"; +} + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + const ConfigurationVerifyKey& rToCopy +) +: mName(rToCopy.mName), + mDefaultValue(rToCopy.mDefaultValue), + mHasDefaultValue(rToCopy.mHasDefaultValue), + mFlags(rToCopy.mFlags), + mTestFunction(rToCopy.mTestFunction) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Configuration(const std::string &) +// Purpose: Constructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::Configuration(const std::string &rName) + : mName(rName) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Configuration(const Configuration &) +// Purpose: Copy constructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::Configuration(const Configuration &rToCopy) + : mName(rToCopy.mName), + mKeys(rToCopy.mKeys), + mSubConfigurations(rToCopy.mSubConfigurations) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::~Configuration() +// Purpose: Destructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::~Configuration() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::LoadAndVerify(const std::string &, const ConfigurationVerify *, std::string &) +// Purpose: Loads a configuration file from disc, checks it. Returns NULL if it was faulting, in which +// case they'll be an error message. +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +std::auto_ptr<Configuration> Configuration::LoadAndVerify( + const std::string& rFilename, + const ConfigurationVerify *pVerify, + std::string &rErrorMsg) +{ + // Just to make sure + rErrorMsg.erase(); + + // Open the file + FileHandleGuard<O_RDONLY> file(rFilename); + + // GetLine object + FdGetLine getline(file); + + // Object to create + std::auto_ptr<Configuration> apConfig( + new Configuration(std::string("<root>"))); + + try + { + // Load + LoadInto(*apConfig, getline, rErrorMsg, true); + + if(!rErrorMsg.empty()) + { + // An error occured, return now + BOX_ERROR("Error in Configuration::LoadInto: " << + rErrorMsg); + return std::auto_ptr<Configuration>(0); + } + + // Verify? + if(pVerify) + { + if(!apConfig->Verify(*pVerify, std::string(), rErrorMsg)) + { + BOX_ERROR("Error verifying configuration: " << + rErrorMsg); + return std::auto_ptr<Configuration>(0); + } + } + } + catch(...) + { + // Clean up + throw; + } + + // Success. Return result. + return apConfig; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: LoadInto(Configuration &, FdGetLine &, std::string &, bool) +// Purpose: Private. Load configuration information from the file into the config object. +// Returns 'abort' flag, if error, will be appended to rErrorMsg. +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool Configuration::LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel) +{ + bool startBlockExpected = false; + std::string blockName; + + //TRACE1("BLOCK: |%s|\n", rConfig.mName.c_str()); + + while(!rGetLine.IsEOF()) + { + std::string line(rGetLine.GetLine(true)); /* preprocess out whitespace and comments */ + + if(line.empty()) + { + // Ignore blank lines + continue; + } + + // Line an open block string? + if(line == "{") + { + if(startBlockExpected) + { + // New config object + Configuration subConfig(blockName); + + // Continue processing into this block + if(!LoadInto(subConfig, rGetLine, rErrorMsg, false)) + { + // Abort error + return false; + } + + startBlockExpected = false; + + // Store... + rConfig.AddSubConfig(blockName, subConfig); + } + else + { + rErrorMsg += "Unexpected start block in " + + rConfig.mName + "\n"; + } + } + else + { + // Close block? + if(line == "}") + { + if(RootLevel) + { + // error -- root level doesn't have a close + rErrorMsg += "Root level has close block -- forgot to terminate subblock?\n"; + // but otherwise ignore + } + else + { + //TRACE0("ENDBLOCK\n"); + return true; // All very good and nice + } + } + // Either a key, or a sub block beginning + else + { + // Can't be a start block + if(startBlockExpected) + { + rErrorMsg += "Block " + blockName + " wasn't started correctly (no '{' on line of it's own)\n"; + startBlockExpected = false; + } + + // Has the line got an = in it? + unsigned int equals = 0; + for(; equals < line.size(); ++equals) + { + if(line[equals] == '=') + { + // found! + break; + } + } + if(equals < line.size()) + { + // Make key value pair + unsigned int keyend = equals; + while(keyend > 0 && iw(line[keyend-1])) + { + keyend--; + } + unsigned int valuestart = equals+1; + while(valuestart < line.size() && iw(line[valuestart])) + { + valuestart++; + } + if(keyend > 0 && valuestart <= line.size()) + { + std::string key(line.substr(0, keyend)); + std::string value(line.substr(valuestart)); + rConfig.AddKeyValue(key, value); + } + else + { + rErrorMsg += "Invalid configuration key: " + line + "\n"; + } + } + else + { + // Start of sub block + blockName = line; + startBlockExpected = true; + } + } + } + } + + // End of file? + if(!RootLevel && rGetLine.IsEOF()) + { + // Error if EOF and this isn't the root level + rErrorMsg += "File ended without terminating all subblocks\n"; + } + + return true; +} + +void Configuration::AddKeyValue(const std::string& rKey, + const std::string& rValue) +{ + // Check for duplicate values + if(mKeys.find(rKey) != mKeys.end()) + { + // Multi-values allowed here, but checked later on + mKeys[rKey] += MultiValueSeparator; + mKeys[rKey] += rValue; + } + else + { + // Store + mKeys[rKey] = rValue; + } +} + +void Configuration::AddSubConfig(const std::string& rName, + const Configuration& rSubConfig) +{ + mSubConfigurations.push_back( + std::pair<std::string, Configuration>(rName, rSubConfig)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::KeyExists(const std::string&) +// Purpose: Checks to see if a key exists +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +bool Configuration::KeyExists(const std::string& rKeyName) const +{ + return mKeys.find(rKeyName) != mKeys.end(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValue(const std::string&) +// Purpose: Returns the value of a configuration variable +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +const std::string &Configuration::GetKeyValue(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + BOX_ERROR("Missing configuration key: " << rKeyName); + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + return i->second; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueInt(const std::string& rKeyName) +// Purpose: Gets a key value as an integer +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +int Configuration::GetKeyValueInt(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + long value = ::strtol((i->second).c_str(), NULL, + 0 /* C style handling */); + if(value == LONG_MAX || value == LONG_MIN) + { + THROW_EXCEPTION(CommonException, ConfigBadIntValue) + } + return (int)value; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueUint32(const std::string& rKeyName) +// Purpose: Gets a key value as a 32-bit unsigned integer +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +uint32_t Configuration::GetKeyValueUint32(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + errno = 0; + long value = ::strtoul((i->second).c_str(), NULL, + 0 /* C style handling */); + if(errno != 0) + { + THROW_EXCEPTION(CommonException, ConfigBadIntValue) + } + return (int)value; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueBool(const std::string&) +// Purpose: Gets a key value as a boolean +// Created: 17/2/04 +// +// -------------------------------------------------------------------------- +bool Configuration::GetKeyValueBool(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + bool value = false; + + // Anything this is called for should have been verified as having a correct + // string in the verification section. However, this does default to false + // if it isn't in the string table. + + for(int l = 0; sValueBooleanStrings[l] != 0; ++l) + { + if(::strcasecmp((i->second).c_str(), sValueBooleanStrings[l]) == 0) + { + // Found. + value = sValueBooleanValue[l]; + break; + } + } + + return value; + } + +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyNames() +// Purpose: Returns list of key names +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::vector<std::string> Configuration::GetKeyNames() const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.begin()); + + std::vector<std::string> r; + + for(; i != mKeys.end(); ++i) + { + r.push_back(i->first); + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::SubConfigurationExists(const +// std::string&) +// Purpose: Checks to see if a sub configuration exists +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +bool Configuration::SubConfigurationExists(const std::string& rSubName) const +{ + // Attempt to find it... + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + for(; i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return true; + } + } + + // didn't find it. + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfiguration(const +// std::string&) +// Purpose: Gets a sub configuration +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +const Configuration &Configuration::GetSubConfiguration(const std::string& + rSubName) const +{ + // Attempt to find it... + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + for(; i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return i->second; + } + } + + THROW_EXCEPTION(CommonException, ConfigNoSubConfig) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfiguration(const +// std::string&) +// Purpose: Gets a sub configuration for editing +// Created: 2008/08/12 +// +// -------------------------------------------------------------------------- +Configuration &Configuration::GetSubConfigurationEditable(const std::string& + rSubName) +{ + // Attempt to find it... + + for(SubConfigListType::iterator + i = mSubConfigurations.begin(); + i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return i->second; + } + } + + THROW_EXCEPTION(CommonException, ConfigNoSubConfig) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfigurationNames() +// Purpose: Return list of sub configuration names +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::vector<std::string> Configuration::GetSubConfigurationNames() const +{ + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + std::vector<std::string> r; + + for(; i != mSubConfigurations.end(); ++i) + { + r.push_back(i->first); + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Verify(const ConfigurationVerify &, const std::string &, std::string &) +// Purpose: Checks that the configuration is valid according to the +// supplied verifier +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool Configuration::Verify(const ConfigurationVerify &rVerify, + const std::string &rLevel, std::string &rErrorMsg) +{ + bool ok = true; + + // First... check the keys + if(rVerify.mpKeys != 0) + { + const ConfigurationVerifyKey *pvkey = rVerify.mpKeys; + + bool todo = true; + do + { + // Can the key be found? + if(KeyExists(pvkey->Name())) + { + // Get value + const std::string &rval = GetKeyValue(pvkey->Name()); + const char *val = rval.c_str(); + + // Check it's a number? + if((pvkey->Flags() & ConfigTest_IsInt) == ConfigTest_IsInt) + { + // Test it... + char *end; + long r = ::strtol(val, &end, 0); + if(r == LONG_MIN || r == LONG_MAX || end != (val + rval.size())) + { + // not a good value + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid integer.\n"; + } + } + + // Check it's a number? + if(pvkey->Flags() & ConfigTest_IsUint32) + { + // Test it... + char *end; + errno = 0; + uint32_t r = ::strtoul(val, &end, 0); + if(errno != 0 || end != (val + rval.size())) + { + // not a good value + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid unsigned 32-bit integer.\n"; + } + } + + // Check it's a bool? + if((pvkey->Flags() & ConfigTest_IsBool) == ConfigTest_IsBool) + { + // See if it's one of the allowed strings. + bool found = false; + for(int l = 0; sValueBooleanStrings[l] != 0; ++l) + { + if(::strcasecmp(val, sValueBooleanStrings[l]) == 0) + { + // Found. + found = true; + break; + } + } + + // Error if it's not one of them. + if(!found) + { + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid boolean value.\n"; + } + } + + // Check for multi valued statments where they're not allowed + if((pvkey->Flags() & ConfigTest_MultiValueAllowed) == 0) + { + // Check to see if this key is a multi-value -- it shouldn't be + if(rval.find(MultiValueSeparator) != rval.npos) + { + ok = false; + rErrorMsg += rLevel + mName +"." + pvkey->Name() + " (key) multi value not allowed (duplicated key?).\n"; + } + } + } + else + { + // Is it required to exist? + if((pvkey->Flags() & ConfigTest_Exists) == ConfigTest_Exists) + { + // Should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is missing.\n"; + } + else if(pvkey->HasDefaultValue()) + { + mKeys[pvkey->Name()] = + pvkey->DefaultValue(); + } + } + + if((pvkey->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + // No more! + todo = false; + } + + // next + pvkey++; + + } while(todo); + + // Check for additional keys + for(std::map<std::string, std::string>::const_iterator i = mKeys.begin(); + i != mKeys.end(); ++i) + { + // Is the name in the list? + const ConfigurationVerifyKey *scan = rVerify.mpKeys; + bool found = false; + while(scan) + { + if(scan->Name() == i->first) + { + found = true; + break; + } + + // Next? + if((scan->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + if(!found) + { + // Shouldn't exist, but does. + ok = false; + rErrorMsg += rLevel + mName + "." + i->first + " (key) is not a known key. Check spelling and placement.\n"; + } + } + } + + // Then the sub configurations + if(rVerify.mpSubConfigurations) + { + // Find the wildcard entry, if it exists, and check that required subconfigs are there + const ConfigurationVerify *wildcardverify = 0; + + const ConfigurationVerify *scan = rVerify.mpSubConfigurations; + while(scan) + { + if(scan->mName.length() > 0 && scan->mName[0] == '*') + { + wildcardverify = scan; + } + + // Required? + if((scan->Tests & ConfigTest_Exists) == ConfigTest_Exists) + { + if(scan->mName.length() > 0 && + scan->mName[0] == '*') + { + // Check something exists + if(mSubConfigurations.size() < 1) + { + // A sub config should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + ".* (block) is missing (a block must be present).\n"; + } + } + else + { + // Check real thing exists + if(!SubConfigurationExists(scan->mName)) + { + // Should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + "." + scan->mName + " (block) is missing.\n"; + } + } + } + + // Next? + if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + // Go through the sub configurations, one by one + for(SubConfigListType::iterator + i = mSubConfigurations.begin(); + i != mSubConfigurations.end(); ++i) + { + // Can this be found? + const ConfigurationVerify *subverify = 0; + + const ConfigurationVerify *scan = rVerify.mpSubConfigurations; + const char *name = i->first.c_str(); + ASSERT(name); + while(scan) + { + if(scan->mName == name) + { + // found it! + subverify = scan; + } + + // Next? + if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + // Use wildcard? + if(subverify == 0) + { + subverify = wildcardverify; + } + + // Verify + if(subverify) + { + // override const-ness here... + if(!i->second.Verify(*subverify, mName + '.', + rErrorMsg)) + { + ok = false; + } + } + } + } + + return ok; +} + + diff --git a/lib/common/Configuration.h b/lib/common/Configuration.h new file mode 100644 index 00000000..4828b315 --- /dev/null +++ b/lib/common/Configuration.h @@ -0,0 +1,147 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Configuration +// Purpose: Reading configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#ifndef CONFIGURATION__H +#define CONFIGURATION__H + +#include <map> +#include <list> +#include <vector> +#include <string> +#include <memory> + +// For defining tests +enum +{ + ConfigTest_LastEntry = 1, + ConfigTest_Exists = 2, + ConfigTest_IsInt = 4, + ConfigTest_IsUint32 = 8, + ConfigTest_MultiValueAllowed = 16, + ConfigTest_IsBool = 32 +}; + +class ConfigurationVerifyKey +{ +public: + typedef enum + { + NoDefaultValue = 1 + } NoDefaultValue_t; + + ConfigurationVerifyKey(std::string name, int flags, + void *testFunction = NULL); + // to allow passing ConfigurationVerifyKey::NoDefaultValue + // for default ListenAddresses + ConfigurationVerifyKey(std::string name, int flags, + NoDefaultValue_t t, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + std::string defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + const char* defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + int defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + bool defaultValue, void *testFunction = NULL); + const std::string& Name() const { return mName; } + const std::string& DefaultValue() const { return mDefaultValue; } + const bool HasDefaultValue() const { return mHasDefaultValue; } + const int Flags() const { return mFlags; } + const void* TestFunction() const { return mTestFunction; } + ConfigurationVerifyKey(const ConfigurationVerifyKey& rToCopy); + +private: + ConfigurationVerifyKey& operator=(const ConfigurationVerifyKey& + noAssign); + + std::string mName; // "*" for all other keys (not implemented yet) + std::string mDefaultValue; // default for when it's not present + bool mHasDefaultValue; + int mFlags; + void *mTestFunction; // set to zero for now, will implement later +}; + +class ConfigurationVerify +{ +public: + std::string mName; // "*" for all other sub config names + const ConfigurationVerify *mpSubConfigurations; + const ConfigurationVerifyKey *mpKeys; + int Tests; + void *TestFunction; // set to zero for now, will implement later +}; + +class FdGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Configuration +// Purpose: Loading, checking, and representing configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +class Configuration +{ +public: + Configuration(const std::string &rName); + Configuration(const Configuration &rToCopy); + ~Configuration(); + + enum + { + // The character to separate multi-values + MultiValueSeparator = '\x01' + }; + + static std::auto_ptr<Configuration> LoadAndVerify( + const std::string& rFilename, + const ConfigurationVerify *pVerify, + std::string &rErrorMsg); + + static std::auto_ptr<Configuration> Load( + const std::string& rFilename, + std::string &rErrorMsg) + { return LoadAndVerify(rFilename, 0, rErrorMsg); } + + bool KeyExists(const std::string& rKeyName) const; + const std::string &GetKeyValue(const std::string& rKeyName) const; + int GetKeyValueInt(const std::string& rKeyName) const; + uint32_t GetKeyValueUint32(const std::string& rKeyName) const; + bool GetKeyValueBool(const std::string& rKeyName) const; + std::vector<std::string> GetKeyNames() const; + + bool SubConfigurationExists(const std::string& rSubName) const; + const Configuration &GetSubConfiguration(const std::string& rSubName) const; + Configuration &GetSubConfigurationEditable(const std::string& rSubName); + std::vector<std::string> GetSubConfigurationNames() const; + + void AddKeyValue(const std::string& rKey, const std::string& rValue); + void AddSubConfig(const std::string& rName, const Configuration& rSubConfig); + + bool Verify(const ConfigurationVerify &rVerify, std::string &rErrorMsg) + { + return Verify(rVerify, std::string(), rErrorMsg); + } + +private: + std::string mName; + // Order of keys not preserved + std::map<std::string, std::string> mKeys; + // Order of sub blocks preserved + typedef std::list<std::pair<std::string, Configuration> > SubConfigListType; + SubConfigListType mSubConfigurations; + + static bool LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel); + bool Verify(const ConfigurationVerify &rVerify, const std::string &rLevel, + std::string &rErrorMsg); +}; + +#endif // CONFIGURATION__H + diff --git a/lib/common/Conversion.h b/lib/common/Conversion.h new file mode 100644 index 00000000..cba5bb08 --- /dev/null +++ b/lib/common/Conversion.h @@ -0,0 +1,98 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Conversion.h +// Purpose: Convert between various types +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef CONVERSION__H +#define CONVERSION__H + +#include <string> + +namespace BoxConvert +{ + // -------------------------------------------------------------------------- + // + // Function + // Name: BoxConvert::Convert<to_type, from_type>(to_type &, from_type) + // Purpose: Convert from types to types + // Created: 9/4/04 + // + // -------------------------------------------------------------------------- + template<typename to_type, typename from_type> + inline to_type Convert(from_type From) + { + // Default conversion, simply use C++ conversion + return From; + } + + // Specialise for string -> integer + int32_t _ConvertStringToInt(const char *pString, int Size); + template<> + inline int32_t Convert<int32_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 32); + } + template<> + inline int16_t Convert<int16_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 16); + } + template<> + inline int8_t Convert<int8_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 8); + } + template<> + inline int32_t Convert<int32_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 32); + } + template<> + inline int16_t Convert<int16_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 16); + } + template<> + inline int8_t Convert<int8_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 8); + } + + // Specialise for integer -> string + void _ConvertIntToString(std::string &rTo, int32_t From); + template<> + inline std::string Convert<std::string, int32_t>(int32_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + template<> + inline std::string Convert<std::string, int16_t>(int16_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + template<> + inline std::string Convert<std::string, int8_t>(int8_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + + // Specialise for bool -> string + template<> + inline std::string Convert<std::string, bool>(bool From) + { + return std::string(From?"true":"false"); + } +}; + +#endif // CONVERSION__H + diff --git a/lib/common/ConversionException.txt b/lib/common/ConversionException.txt new file mode 100644 index 00000000..91b5fa9a --- /dev/null +++ b/lib/common/ConversionException.txt @@ -0,0 +1,8 @@ + +EXCEPTION Conversion 12 + +Internal 0 +CannotConvertEmptyStringToInt 1 +BadStringRepresentationOfInt 2 +IntOverflowInConvertFromString 3 +BadIntSize 4 diff --git a/lib/common/ConversionString.cpp b/lib/common/ConversionString.cpp new file mode 100644 index 00000000..2d0a8d58 --- /dev/null +++ b/lib/common/ConversionString.cpp @@ -0,0 +1,129 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ConversionString.cpp +// Purpose: Conversions to and from strings +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> + +#include "Conversion.h" +#include "autogen_ConversionException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxConvert::_ConvertStringToInt(const char *, int) +// Purpose: Convert from string to integer, with range checking. +// Always does signed -- no point in unsigned as C++ type checking +// isn't up to handling it properly. +// If a null pointer is passed in, then returns 0. +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +int32_t BoxConvert::_ConvertStringToInt(const char *pString, int Size) +{ + // Handle null strings gracefully. + if(pString == 0) + { + return 0; + } + + // Check for initial validity + if(*pString == '\0') + { + THROW_EXCEPTION(ConversionException, CannotConvertEmptyStringToInt) + } + + // Convert. + char *numEnd = 0; + errno = 0; // Some platforms don't reset it. + long r = ::strtol(pString, &numEnd, 0); + + // Check that all the characters were used + if(*numEnd != '\0') + { + THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) + } + + // Error check + if(r == 0 && errno == EINVAL) + { + THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) + } + + // Range check from strtol + if((r == LONG_MIN || r == LONG_MAX) && errno == ERANGE) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + + // Check range for size of integer + switch(Size) + { + case 32: + { + // No extra checking needed if long is an int32 + if(sizeof(long) > sizeof(int32_t)) + { + if(r <= (0 - 0x7fffffffL) || r > 0x7fffffffL) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + } + break; + } + + case 16: + { + if(r <= (0 - 0x7fff) || r > 0x7fff) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + break; + } + + case 8: + { + if(r <= (0 - 0x7f) || r > 0x7f) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + break; + } + + default: + { + THROW_EXCEPTION(ConversionException, BadIntSize) + break; + } + } + + // Return number + return r; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxConvert::_ConvertIntToString(std::string &, int32_t) +// Purpose: Convert signed interger to a string +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +void BoxConvert::_ConvertIntToString(std::string &rTo, int32_t From) +{ + char text[64]; // size more than enough + ::sprintf(text, "%d", (int)From); + rTo = text; +} + diff --git a/lib/common/DebugAssertFailed.cpp b/lib/common/DebugAssertFailed.cpp new file mode 100644 index 00000000..e498d641 --- /dev/null +++ b/lib/common/DebugAssertFailed.cpp @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: AssertFailed.cpp +// Purpose: Assert failure code +// Created: 2003/09/04 +// +// -------------------------------------------------------------------------- + +#ifndef BOX_RELEASE_BUILD + +#include "Box.h" + +#include <stdio.h> + +#ifdef WIN32 + #include "emu.h" +#else + #include <syslog.h> +#endif + +#include "MemLeakFindOn.h" + +bool AssertFailuresToSyslog = false; + +void BoxDebugAssertFailed(const char *cond, const char *file, int line) +{ + printf("ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); + if(AssertFailuresToSyslog) + { + ::syslog(LOG_ERR, "ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); + } +} + + +#endif // BOX_RELEASE_BUILD + diff --git a/lib/common/DebugMemLeakFinder.cpp b/lib/common/DebugMemLeakFinder.cpp new file mode 100644 index 00000000..72891cd1 --- /dev/null +++ b/lib/common/DebugMemLeakFinder.cpp @@ -0,0 +1,552 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFinder.cpp +// Purpose: Memory leak finder +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + + +#ifndef BOX_RELEASE_BUILD + +#include "Box.h" + +#undef malloc +#undef realloc +#undef free + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <map> +#include <stdio.h> +#include <string.h> +#include <set> +#include <cstdlib> // for std::atexit + +#include "MemLeakFinder.h" + +static bool memleakfinder_initialised = false; +bool memleakfinder_global_enable = false; + +typedef struct +{ + size_t size; + const char *file; + int line; +} MallocBlockInfo; + +typedef struct +{ + size_t size; + const char *file; + int line; + bool array; +} ObjectInfo; + +namespace +{ + static std::map<void *, MallocBlockInfo> sMallocBlocks; + static std::map<void *, ObjectInfo> sObjectBlocks; + static bool sTrackingDataDestroyed = false; + + static class DestructionWatchdog + { + public: + ~DestructionWatchdog() + { + sTrackingDataDestroyed = true; + } + } + sWatchdog; + + static bool sTrackMallocInSection = false; + static std::set<void *> sSectionMallocBlocks; + static bool sTrackObjectsInSection = false; + static std::map<void *, ObjectInfo> sSectionObjectBlocks; + + static std::set<void *> sNotLeaks; + + void *sNotLeaksPre[1024]; + size_t sNotLeaksPreNum = 0; +} + +void memleakfinder_init() +{ + ASSERT(!memleakfinder_initialised); + + { + // allocates a permanent buffer on Solaris. + // not a leak? + std::ostringstream oss; + } + + memleakfinder_initialised = true; +} + +MemLeakSuppressionGuard::MemLeakSuppressionGuard() +{ + ASSERT(memleakfinder_global_enable); + memleakfinder_global_enable = false; +} + +MemLeakSuppressionGuard::~MemLeakSuppressionGuard() +{ + ASSERT(!memleakfinder_global_enable); + memleakfinder_global_enable = true; +} + +// these functions may well allocate memory, which we don't want to track. +static int sInternalAllocDepth = 0; + +class InternalAllocGuard +{ + public: + InternalAllocGuard () { sInternalAllocDepth++; } + ~InternalAllocGuard() { sInternalAllocDepth--; } +}; + +void memleakfinder_malloc_add_block(void *b, size_t size, const char *file, int line) +{ + InternalAllocGuard guard; + + if(b != 0) + { + MallocBlockInfo i; + i.size = size; + i.file = file; + i.line = line; + sMallocBlocks[b] = i; + + if(sTrackMallocInSection) + { + sSectionMallocBlocks.insert(b); + } + } +} + +void *memleakfinder_malloc(size_t size, const char *file, int line) +{ + InternalAllocGuard guard; + + void *b = std::malloc(size); + if(!memleakfinder_global_enable) return b; + if(!memleakfinder_initialised) return b; + + memleakfinder_malloc_add_block(b, size, file, line); + + //TRACE4("malloc(), %d, %s, %d, %08x\n", size, file, line, b); + return b; +} + +void *memleakfinder_realloc(void *ptr, size_t size) +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable || !memleakfinder_initialised) + { + return std::realloc(ptr, size); + } + + // Check it's been allocated + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + if(ptr && i == sMallocBlocks.end()) + { + BOX_WARNING("Block " << ptr << " realloc()ated, but not " + "in list. Error? Or allocated in startup static " + "objects?"); + } + + void *b = std::realloc(ptr, size); + + if(ptr && i!=sMallocBlocks.end()) + { + // Worked? + if(b != 0) + { + // Update map + MallocBlockInfo inf = i->second; + inf.size = size; + sMallocBlocks.erase(i); + sMallocBlocks[b] = inf; + + if(sTrackMallocInSection) + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + sSectionMallocBlocks.insert(b); + } + } + } + else + { + memleakfinder_malloc_add_block(b, size, "FOUND-IN-REALLOC", 0); + } + + //TRACE3("realloc(), %d, %08x->%08x\n", size, ptr, b); + return b; +} + +void memleakfinder_free(void *ptr) +{ + InternalAllocGuard guard; + + if(memleakfinder_global_enable && memleakfinder_initialised) + { + // Check it's been allocated + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + if(i != sMallocBlocks.end()) + { + sMallocBlocks.erase(i); + } + else + { + BOX_WARNING("Block " << ptr << " freed, but not " + "known. Error? Or allocated in startup " + "static allocation?"); + } + + if(sTrackMallocInSection) + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + } + } + + //TRACE1("free(), %08x\n", ptr); + std::free(ptr); +} + + +void memleakfinder_notaleak_insert_pre() +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + + for(size_t l = 0; l < sNotLeaksPreNum; l++) + { + sNotLeaks.insert(sNotLeaksPre[l]); + } + + sNotLeaksPreNum = 0; +} + +bool is_leak(void *ptr) +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + memleakfinder_notaleak_insert_pre(); + return sNotLeaks.find(ptr) == sNotLeaks.end(); +} + +void memleakfinder_notaleak(void *ptr) +{ + InternalAllocGuard guard; + + ASSERT(!sTrackingDataDestroyed); + + memleakfinder_notaleak_insert_pre(); + if(memleakfinder_global_enable && memleakfinder_initialised) + { + sNotLeaks.insert(ptr); + } + else + { + if ( sNotLeaksPreNum < + sizeof(sNotLeaksPre)/sizeof(*sNotLeaksPre) ) + sNotLeaksPre[sNotLeaksPreNum++] = ptr; + } +/* { + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + if(i != sMallocBlocks.end()) sMallocBlocks.erase(i); + } + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + } + { + std::map<void *, ObjectInfo>::iterator i(sObjectBlocks.find(ptr)); + if(i != sObjectBlocks.end()) sObjectBlocks.erase(i); + }*/ +} + + + +// start monitoring a section of code +void memleakfinder_startsectionmonitor() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + sTrackMallocInSection = true; + sSectionMallocBlocks.clear(); + sTrackObjectsInSection = true; + sSectionObjectBlocks.clear(); +} + +// trace all blocks allocated and still allocated since memleakfinder_startsectionmonitor() called +void memleakfinder_traceblocksinsection() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + std::set<void *>::iterator s(sSectionMallocBlocks.begin()); + for(; s != sSectionMallocBlocks.end(); ++s) + { + std::map<void *, MallocBlockInfo>::const_iterator i(sMallocBlocks.find(*s)); + if(i == sMallocBlocks.end()) + { + BOX_WARNING("Logical error in section block finding"); + } + else + { + BOX_TRACE("Block " << i->first << " size " << + i->second.size << " allocated at " << + i->second.file << ":" << i->second.line); + } + } + for(std::map<void *, ObjectInfo>::const_iterator i(sSectionObjectBlocks.begin()); i != sSectionObjectBlocks.end(); ++i) + { + BOX_TRACE("Object" << (i->second.array?" []":"") << " " << + i->first << " size " << i->second.size << + " allocated at " << i->second.file << + ":" << i->second.line); + } +} + +int memleakfinder_numleaks() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + int n = 0; + + for(std::map<void *, MallocBlockInfo>::const_iterator i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) + { + if(is_leak(i->first)) ++n; + } + + for(std::map<void *, ObjectInfo>::const_iterator i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) + { + const ObjectInfo& rInfo = i->second; + if(is_leak(i->first)) ++n; + } + + return n; +} + +void memleakfinder_reportleaks_file(FILE *file) +{ + InternalAllocGuard guard; + + ASSERT(!sTrackingDataDestroyed); + + for(std::map<void *, MallocBlockInfo>::const_iterator + i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) + { + if(is_leak(i->first)) + { + ::fprintf(file, "Block %p size %d allocated at " + "%s:%d\n", i->first, i->second.size, + i->second.file, i->second.line); + } + } + + for(std::map<void *, ObjectInfo>::const_iterator + i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) + { + if(is_leak(i->first)) + { + ::fprintf(file, "Object%s %p size %d allocated at " + "%s:%d\n", i->second.array?" []":"", + i->first, i->second.size, i->second.file, + i->second.line); + } + } +} + +void memleakfinder_reportleaks() +{ + InternalAllocGuard guard; + + // report to stdout + memleakfinder_reportleaks_file(stdout); +} + +void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext) +{ + InternalAllocGuard guard; + + FILE *file = ::fopen(filename, "a"); + if(file != 0) + { + if(memleakfinder_numleaks() > 0) + { +#ifdef HAVE_GETPID + fprintf(file, "MEMORY LEAKS FROM PROCESS %d (%s)\n", getpid(), markertext); +#else + fprintf(file, "MEMORY LEAKS (%s)\n", markertext); +#endif + memleakfinder_reportleaks_file(file); + } + + ::fclose(file); + } + else + { + BOX_WARNING("Couldn't open memory leak results file " << + filename << " for appending"); + } +} + +static char atexit_filename[512]; +static char atexit_markertext[512]; +static bool atexit_registered = false; + +extern "C" void memleakfinder_atexit() +{ + memleakfinder_reportleaks_appendfile(atexit_filename, atexit_markertext); +} + +void memleakfinder_setup_exit_report(const char *filename, const char *markertext) +{ + ::strncpy(atexit_filename, filename, sizeof(atexit_filename)-1); + ::strncpy(atexit_markertext, markertext, sizeof(atexit_markertext)-1); + atexit_filename[sizeof(atexit_filename)-1] = 0; + atexit_markertext[sizeof(atexit_markertext)-1] = 0; + if(!atexit_registered) + { + std::atexit(memleakfinder_atexit); + atexit_registered = true; + } +} + + + + +void add_object_block(void *block, size_t size, const char *file, int line, bool array) +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + ASSERT(!sTrackingDataDestroyed); + + if(block != 0) + { + ObjectInfo i; + i.size = size; + i.file = file; + i.line = line; + i.array = array; + sObjectBlocks[block] = i; + + if(sTrackObjectsInSection) + { + sSectionObjectBlocks[block] = i; + } + } +} + +void remove_object_block(void *block) +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + if(sTrackingDataDestroyed) return; + + std::map<void *, ObjectInfo>::iterator i(sObjectBlocks.find(block)); + if(i != sObjectBlocks.end()) + { + sObjectBlocks.erase(i); + } + + if(sTrackObjectsInSection) + { + std::map<void *, ObjectInfo>::iterator i(sSectionObjectBlocks.find(block)); + if(i != sSectionObjectBlocks.end()) + { + sSectionObjectBlocks.erase(i); + } + } + + // If it's not in the list, just ignore it, as lots of stuff goes this way... +} + +static void *internal_new(size_t size, const char *file, int line) +{ + void *r; + + { + InternalAllocGuard guard; + r = std::malloc(size); + } + + if (sInternalAllocDepth == 0) + { + InternalAllocGuard guard; + add_object_block(r, size, file, line, false); + //TRACE4("new(), %d, %s, %d, %08x\n", size, file, line, r); + } + + return r; +} + +void *operator new(size_t size, const char *file, int line) +{ + return internal_new(size, file, line); +} + +void *operator new[](size_t size, const char *file, int line) +{ + return internal_new(size, file, line); +} + +// where there is no doctor... need to override standard new() too +// http://www.relisoft.com/book/tech/9new.html +// disabled because it causes hangs on FC2 in futex() in test/common +// while reading files. reason unknown. +/* +void *operator new(size_t size) +{ + return internal_new(size, "standard libraries", 0); +} +*/ + +void *operator new[](size_t size) +{ + return internal_new(size, "standard libraries", 0); +} + +void internal_delete(void *ptr) +{ + InternalAllocGuard guard; + + std::free(ptr); + remove_object_block(ptr); + //TRACE1("delete[]() called, %08x\n", ptr); +} + +void operator delete[](void *ptr) throw () +{ + internal_delete(ptr); +} + +void operator delete(void *ptr) throw () +{ + internal_delete(ptr); +} + +#endif // BOX_RELEASE_BUILD diff --git a/lib/common/DebugPrintf.cpp b/lib/common/DebugPrintf.cpp new file mode 100644 index 00000000..1335d473 --- /dev/null +++ b/lib/common/DebugPrintf.cpp @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: DebugPrintf.cpp +// Purpose: Implementation of a printf function, to avoid a stdio.h include in Box.h +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- + +#ifndef BOX_RELEASE_BUILD + +#include "Box.h" + +#include <stdio.h> +#include <stdarg.h> + +#ifdef WIN32 + #include "emu.h" +#else + #include <syslog.h> +#endif + +#include "MemLeakFindOn.h" + +// Use this apparently superflous printf function to avoid having to +// include stdio.h in every file in the project. + +int BoxDebug_printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int r = vprintf(format, ap); + va_end(ap); + return r; +} + + +bool BoxDebugTraceOn = true; +bool BoxDebugTraceToStdout = true; +bool BoxDebugTraceToSyslog = false; + +int BoxDebugTrace(const char *format, ...) +{ + char text[512]; + int r = 0; + if(BoxDebugTraceOn || BoxDebugTraceToSyslog) + { + va_list ap; + va_start(ap, format); + r = vsnprintf(text, sizeof(text), format, ap); + va_end(ap); + } + + // Send to stdout if trace is on and std out is enabled + if(BoxDebugTraceOn && BoxDebugTraceToStdout) + { + printf("%s", text); + } + + // But tracing to syslog is independent of tracing being on or not + if(BoxDebugTraceToSyslog) + { +#ifdef WIN32 + // Remove trailing '\n', if it's there + if(r > 0 && text[r-1] == '\n') + { + text[r-1] = '\0'; +#else + if(r > 0 && text[r] == '\n') + { + text[r] = '\0'; +#endif + --r; + } + // Log it + ::syslog(LOG_INFO, "TRACE: %s", text); + } + + return r; +} + + +#endif // BOX_RELEASE_BUILD diff --git a/lib/common/EndStructPackForWire.h b/lib/common/EndStructPackForWire.h new file mode 100644 index 00000000..82637f33 --- /dev/null +++ b/lib/common/EndStructPackForWire.h @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: EndStructPackForWire.h +// Purpose: End structure packing for wire +// Created: 25/11/03 +// +// -------------------------------------------------------------------------- + +// No header guard -- this is intentional + +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS + +#pragma pack() + +#else + + logical error -- check BoxPlatform.h and including file + +#endif + + + diff --git a/lib/common/EventWatchFilesystemObject.cpp b/lib/common/EventWatchFilesystemObject.cpp new file mode 100644 index 00000000..43533fc8 --- /dev/null +++ b/lib/common/EventWatchFilesystemObject.cpp @@ -0,0 +1,112 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: EventWatchFilesystemObject.cpp +// Purpose: WaitForEvent compatible object for watching directories +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <fcntl.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "EventWatchFilesystemObject.h" +#include "autogen_CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: EventWatchFilesystemObject::EventWatchFilesystemObject +// (const char *) +// Purpose: Constructor -- opens the file object +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +EventWatchFilesystemObject::EventWatchFilesystemObject(const char *Filename) +#ifdef HAVE_KQUEUE + : mDescriptor(::open(Filename, O_RDONLY /*O_EVTONLY*/, 0)) +#endif +{ +#ifdef HAVE_KQUEUE + if(mDescriptor == -1) + { + BOX_LOG_SYS_ERROR("EventWatchFilesystemObject: " + "Failed to open file '" << Filename << "'"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } +#else + THROW_EXCEPTION(CommonException, KQueueNotSupportedOnThisPlatform) +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: EventWatchFilesystemObject::~EventWatchFilesystemObject() +// Purpose: Destructor +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +EventWatchFilesystemObject::~EventWatchFilesystemObject() +{ + if(mDescriptor != -1) + { + ::close(mDescriptor); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: EventWatchFilesystemObject::EventWatchFilesystemObject +// (const EventWatchFilesystemObject &) +// Purpose: Copy constructor +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +EventWatchFilesystemObject::EventWatchFilesystemObject( + const EventWatchFilesystemObject &rToCopy) + : mDescriptor(::dup(rToCopy.mDescriptor)) +{ + if(mDescriptor == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } +} + + +#ifdef HAVE_KQUEUE +// -------------------------------------------------------------------------- +// +// Function +// Name: EventWatchFilesystemObject::FillInKEvent(struct kevent &, int) +// Purpose: For WaitForEvent +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +void EventWatchFilesystemObject::FillInKEvent(struct kevent &rEvent, + int Flags) const +{ + EV_SET(&rEvent, mDescriptor, EVFILT_VNODE, EV_CLEAR, + NOTE_DELETE | NOTE_WRITE, 0, (void*)this); +} +#else +void EventWatchFilesystemObject::FillInPoll(int &fd, short &events, + int Flags) const +{ + THROW_EXCEPTION(CommonException, KQueueNotSupportedOnThisPlatform) +} +#endif + diff --git a/lib/common/EventWatchFilesystemObject.h b/lib/common/EventWatchFilesystemObject.h new file mode 100644 index 00000000..f9175a49 --- /dev/null +++ b/lib/common/EventWatchFilesystemObject.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: EventWatchFilesystemObject.h +// Purpose: WaitForEvent compatible object for watching directories +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef EVENTWATCHFILESYSTEMOBJECT__H +#define EVENTWATCHFILESYSTEMOBJECT__H + +#ifdef HAVE_KQUEUE + #include <sys/event.h> +#endif + + +// -------------------------------------------------------------------------- +// +// Class +// Name: EventWatchFilesystemObject +// Purpose: WaitForEvent compatible object for watching files and directories +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +class EventWatchFilesystemObject +{ +public: + EventWatchFilesystemObject(const char *Filename); + ~EventWatchFilesystemObject(); + EventWatchFilesystemObject(const EventWatchFilesystemObject &rToCopy); +private: + // Assignment not allowed + EventWatchFilesystemObject &operator=(const EventWatchFilesystemObject &); +public: + +#ifdef HAVE_KQUEUE + void FillInKEvent(struct kevent &rEvent, int Flags = 0) const; +#else + void FillInPoll(int &fd, short &events, int Flags = 0) const; +#endif + +private: + int mDescriptor; +}; + +#endif // EventWatchFilesystemObject__H + diff --git a/lib/common/ExcludeList.cpp b/lib/common/ExcludeList.cpp new file mode 100644 index 00000000..edbf1a6a --- /dev/null +++ b/lib/common/ExcludeList.cpp @@ -0,0 +1,481 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ExcludeList.cpp +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_REGEX_SUPPORT + #ifdef HAVE_PCREPOSIX_H + #include <pcreposix.h> + #else + #include <regex.h> + #endif + #define EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED +#endif + +#include "ExcludeList.h" +#include "Utils.h" +#include "Configuration.h" +#include "Archive.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::ExcludeList() +// Purpose: Constructor. Generates an exclude list which will allow everything +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList::ExcludeList() + : mpAlwaysInclude(0) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::~ExcludeList() +// Purpose: Destructor +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList::~ExcludeList() +{ +#ifdef HAVE_REGEX_SUPPORT + // free regex memory + while(mRegex.size() > 0) + { + regex_t *pregex = mRegex.back(); + mRegex.pop_back(); + // Free regex storage, and the structure itself + ::regfree(pregex); + delete pregex; + } +#endif + + // Clean up exceptions list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } +} + +#ifdef WIN32 +std::string ExcludeList::ReplaceSlashesDefinite(const std::string& input) const +{ + std::string output = input; + + for (std::string::size_type pos = output.find("/"); + pos != std::string::npos; + pos = output.find("/")) + { + output.replace(pos, 1, DIRECTORY_SEPARATOR); + } + + for (std::string::iterator i = output.begin(); i != output.end(); i++) + { + *i = tolower(*i); + } + + return output; +} + +std::string ExcludeList::ReplaceSlashesRegex(const std::string& input) const +{ + std::string output = input; + + for (std::string::size_type pos = output.find("/"); + pos != std::string::npos; + pos = output.find("/")) + { + output.replace(pos, 1, "\\" DIRECTORY_SEPARATOR); + } + + for (std::string::iterator i = output.begin(); i != output.end(); i++) + { + *i = tolower(*i); + } + + return output; +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::AddDefiniteEntries(const std::string &) +// Purpose: Adds a number of definite entries to the exclude list -- ones which +// will be excluded if and only if the test string matches exactly. +// Uses the Configuration classes' multi-value conventions, with +// multiple entires in one string separated by Configuration::MultiValueSeparator +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::AddDefiniteEntries(const std::string &rEntries) +{ + // Split strings up + std::vector<std::string> ens; + SplitString(rEntries, Configuration::MultiValueSeparator, ens); + + // Add to set of excluded strings + for(std::vector<std::string>::const_iterator i(ens.begin()); i != ens.end(); ++i) + { + if(i->size() > 0) + { + std::string entry = *i; + + // Convert any forward slashes in the string + // to backslashes + + #ifdef WIN32 + entry = ReplaceSlashesDefinite(entry); + #endif + + if (entry.size() > 0 && entry[entry.size() - 1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + BOX_WARNING("Exclude entry ends in path " + "separator, will never match: " + << entry); + } + + mDefinite.insert(entry); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::AddRegexEntries(const std::string &) +// Purpose: Adds a number of regular expression entries to the exclude list -- +// if the test expression matches any of these regex, it will be excluded. +// Uses the Configuration classes' multi-value conventions, with +// multiple entires in one string separated by Configuration::MultiValueSeparator +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::AddRegexEntries(const std::string &rEntries) +{ +#ifdef HAVE_REGEX_SUPPORT + + // Split strings up + std::vector<std::string> ens; + SplitString(rEntries, Configuration::MultiValueSeparator, ens); + + // Create and add new regular expressions + for(std::vector<std::string>::const_iterator i(ens.begin()); i != ens.end(); ++i) + { + if(i->size() > 0) + { + // Allocate memory + regex_t *pregex = new regex_t; + + try + { + std::string entry = *i; + + // Convert any forward slashes in the string + // to appropriately escaped backslashes + + #ifdef WIN32 + entry = ReplaceSlashesRegex(entry); + #endif + + // Compile + int errcode = ::regcomp(pregex, entry.c_str(), + REG_EXTENDED | REG_NOSUB); + + if (errcode != 0) + { + char buf[1024]; + regerror(errcode, pregex, buf, sizeof(buf)); + BOX_ERROR("Invalid regular expression: " << + entry << ": " << buf); + THROW_EXCEPTION(CommonException, BadRegularExpression) + } + + // Store in list of regular expressions + mRegex.push_back(pregex); + // Store in list of regular expression string for Serialize + mRegexStr.push_back(entry.c_str()); + } + catch(...) + { + delete pregex; + throw; + } + } + } + +#else + THROW_EXCEPTION(CommonException, RegexNotSupportedOnThisPlatform) +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::IsExcluded(const std::string &) +// Purpose: Returns true if the entry should be excluded +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +bool ExcludeList::IsExcluded(const std::string &rTest) const +{ + std::string test = rTest; + + #ifdef WIN32 + test = ReplaceSlashesDefinite(test); + #endif + + // Check against the always include list + if(mpAlwaysInclude != 0) + { + if(mpAlwaysInclude->IsExcluded(test)) + { + // Because the "always include" list says it's 'excluded' + // this means it should actually be included. + return false; + } + } + + // Is it in the set of definite entries? + if(mDefinite.find(test) != mDefinite.end()) + { + return true; + } + + // Check against regular expressions +#ifdef HAVE_REGEX_SUPPORT + for(std::vector<regex_t *>::const_iterator i(mRegex.begin()); i != mRegex.end(); ++i) + { + // Test against this expression + if(regexec(*i, test.c_str(), 0, 0 /* no match information required */, 0 /* no flags */) == 0) + { + // match happened + return true; + } + // In all other cases, including an error, just continue to the next expression + } +#endif + + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::SetAlwaysIncludeList(ExcludeList *) +// Purpose: Takes ownership of the list, deletes any pre-existing list. +// NULL is acceptable to delete the list. +// The AlwaysInclude list is a list of exceptions to the exclusions. +// Created: 19/2/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::SetAlwaysIncludeList(ExcludeList *pAlwaysInclude) +{ + // Delete old list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } + + // Store the pointer + mpAlwaysInclude = pAlwaysInclude; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::Deserialize(Archive & rArchive) +// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void ExcludeList::Deserialize(Archive & rArchive) +{ + // + // + // + mDefinite.clear(); + +#ifdef HAVE_REGEX_SUPPORT + // free regex memory + while(mRegex.size() > 0) + { + regex_t *pregex = mRegex.back(); + mRegex.pop_back(); + // Free regex storage, and the structure itself + ::regfree(pregex); + delete pregex; + } + + mRegexStr.clear(); +#endif + + // Clean up exceptions list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } + + // + // + // + int64_t iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + // load each one + std::string strItem; + rArchive.Read(strItem); + mDefinite.insert(strItem); + } + } + + // + // + // +#ifdef HAVE_REGEX_SUPPORT + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + std::string strItem; + rArchive.Read(strItem); + + // Allocate memory + regex_t* pregex = new regex_t; + + try + { + // Compile + if(::regcomp(pregex, strItem.c_str(), + REG_EXTENDED | REG_NOSUB) != 0) + { + THROW_EXCEPTION(CommonException, + BadRegularExpression) + } + + // Store in list of regular expressions + mRegex.push_back(pregex); + + // Store in list of regular expression strings + // for Serialize + mRegexStr.push_back(strItem); + } + catch(...) + { + delete pregex; + throw; + } + } + } +#endif // HAVE_REGEX_SUPPORT + + // + // + // + int64_t aMagicMarker = 0; + rArchive.Read(aMagicMarker); + + if (aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) + { + // NOOP + } + else if (aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) + { + mpAlwaysInclude = new ExcludeList; + if (!mpAlwaysInclude) + { + throw std::bad_alloc(); + } + + mpAlwaysInclude->Deserialize(rArchive); + } + else + { + // there is something going on here + THROW_EXCEPTION(CommonException, Internal) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::Serialize(Archive & rArchive) +// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void ExcludeList::Serialize(Archive & rArchive) const +{ + // + // + // + int64_t iCount = mDefinite.size(); + rArchive.Write(iCount); + + for (std::set<std::string>::const_iterator i = mDefinite.begin(); + i != mDefinite.end(); i++) + { + rArchive.Write(*i); + } + + // + // + // +#ifdef HAVE_REGEX_SUPPORT + // don't even try to save compiled regular expressions, + // use string copies instead. + ASSERT(mRegex.size() == mRegexStr.size()); + + iCount = mRegexStr.size(); + rArchive.Write(iCount); + + for (std::vector<std::string>::const_iterator i = mRegexStr.begin(); + i != mRegexStr.end(); i++) + { + rArchive.Write(*i); + } +#endif // HAVE_REGEX_SUPPORT + + // + // + // + if (!mpAlwaysInclude) + { + 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); + + mpAlwaysInclude->Serialize(rArchive); + } +} diff --git a/lib/common/ExcludeList.h b/lib/common/ExcludeList.h new file mode 100644 index 00000000..3c41bd11 --- /dev/null +++ b/lib/common/ExcludeList.h @@ -0,0 +1,76 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ExcludeList.h +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef EXCLUDELIST__H +#define EXCLUDELIST__H + +#include <string> +#include <set> +#include <vector> + +// avoid including regex.h in lots of places +#ifndef EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED + typedef int regex_t; +#endif + +class Archive; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ExcludeList +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +class ExcludeList +{ +public: + ExcludeList(); + ~ExcludeList(); + + void Deserialize(Archive & rArchive); + void Serialize(Archive & rArchive) const; + + void AddDefiniteEntries(const std::string &rEntries); + void AddRegexEntries(const std::string &rEntries); + + // Add exceptions to the exclusions (takes ownership) + void SetAlwaysIncludeList(ExcludeList *pAlwaysInclude); + + // Test function + bool IsExcluded(const std::string &rTest) const; + + // Mainly for tests + unsigned int SizeOfDefiniteList() const {return mDefinite.size();} + unsigned int SizeOfRegexList() const +#ifdef HAVE_REGEX_SUPPORT + {return mRegex.size();} +#else + {return 0;} +#endif + +private: + std::set<std::string> mDefinite; +#ifdef HAVE_REGEX_SUPPORT + std::vector<regex_t *> mRegex; + std::vector<std::string> mRegexStr; // save original regular expression string-based source for Serialize +#endif + +#ifdef WIN32 + std::string ReplaceSlashesDefinite(const std::string& input) const; + std::string ReplaceSlashesRegex (const std::string& input) const; +#endif + + // For exceptions to the excludes + ExcludeList *mpAlwaysInclude; +}; + +#endif // EXCLUDELIST__H + diff --git a/lib/common/FdGetLine.cpp b/lib/common/FdGetLine.cpp new file mode 100644 index 00000000..9b53288b --- /dev/null +++ b/lib/common/FdGetLine.cpp @@ -0,0 +1,228 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FdGetLine.cpp +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "FdGetLine.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: FdGetLine::FdGetLine(int) +// Purpose: Constructor, taking file descriptor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +FdGetLine::FdGetLine(int fd) + : mFileHandle(fd), + mLineNumber(0), + mBufferBegin(0), + mBytesInBuffer(0), + mPendingEOF(false), + mEOF(false) +{ + if(mFileHandle < 0) {THROW_EXCEPTION(CommonException, BadArguments)} + //printf("FdGetLine buffer size = %d\n", sizeof(mBuffer)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::~FdGetLine() +// Purpose: Destructor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +FdGetLine::~FdGetLine() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::GetLine(bool) +// Purpose: Returns a file from the file. If Preprocess is true, leading +// and trailing whitespace is removed, and comments (after #) +// are deleted. +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::string FdGetLine::GetLine(bool Preprocess) +{ + if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} + + // EOF? + if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)} + + std::string r; + + 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 + r += c; + } + + // Implicit line ending at EOF + if(mBufferBegin >= mBytesInBuffer && mPendingEOF) + { + foundLineEnd = true; + } + } + + // Check size + if(r.size() > FDGETLINE_MAX_LINE_SIZE) + { + THROW_EXCEPTION(CommonException, GetLineTooLarge) + } + + // Read more in? + if(!foundLineEnd && mBufferBegin >= mBytesInBuffer && !mPendingEOF) + { +#ifdef WIN32 + int bytes; + + if (mFileHandle == _fileno(stdin)) + { + bytes = console_read(mBuffer, sizeof(mBuffer)); + } + else + { + bytes = ::read(mFileHandle, mBuffer, + sizeof(mBuffer)); + } +#else // !WIN32 + int bytes = ::read(mFileHandle, mBuffer, sizeof(mBuffer)); +#endif // WIN32 + + // Error? + if(bytes == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + + // Adjust buffer info + mBytesInBuffer = bytes; + mBufferBegin = 0; + + // EOF / closed? + if(bytes == 0) + { + mPendingEOF = true; + } + } + + // 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) + { + return r; + } + else + { + // Check for comment char, but char before must be whitespace + int end = 0; + int size = r.size(); + while(end < size) + { + if(r[end] == '#' && (end == 0 || (iw(r[end-1])))) + { + break; + } + end++; + } + + // Remove whitespace + int begin = 0; + while(begin < size && iw(r[begin])) + { + begin++; + } + if(!iw(r[end])) end--; + while(end > begin && iw(r[end])) + { + end--; + } + + // Return a sub string + return r.substr(begin, end - begin + 1); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::DetachFile() +// Purpose: Detaches the file handle, setting the file pointer correctly. +// Probably not good for sockets... +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +void FdGetLine::DetachFile() +{ + if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} + + // Adjust file pointer + int bytesOver = mBufferBegin - mBufferBegin; + ASSERT(bytesOver >= 0); + if(bytesOver > 0) + { + if(::lseek(mFileHandle, 0 - bytesOver, SEEK_CUR) == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } + + // Unset file pointer + mFileHandle = -1; +} + + diff --git a/lib/common/FdGetLine.h b/lib/common/FdGetLine.h new file mode 100644 index 00000000..df43c3c9 --- /dev/null +++ b/lib/common/FdGetLine.h @@ -0,0 +1,65 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FdGetLine.h +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#ifndef FDGETLINE__H +#define FDGETLINE__H + +#include <string> + +#ifdef BOX_RELEASE_BUILD + #define FDGETLINE_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 FDGETLINE_BUFFER_SIZE 5 +#else + #define FDGETLINE_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 FDGETLINE_MAX_LINE_SIZE (1024*256) + +// -------------------------------------------------------------------------- +// +// Class +// Name: FdGetLine +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +class FdGetLine +{ +public: + FdGetLine(int fd); + ~FdGetLine(); +private: + FdGetLine(const FdGetLine &rToCopy); + +public: + std::string GetLine(bool Preprocess = false); + bool IsEOF() {return mEOF;} + int GetLineNumber() {return mLineNumber;} + + // Call to detach, setting file pointer correctly to last bit read. + // Only works for lseek-able file descriptors. + void DetachFile(); + +private: + char mBuffer[FDGETLINE_BUFFER_SIZE]; + int mFileHandle; + int mLineNumber; + int mBufferBegin; + int mBytesInBuffer; + bool mPendingEOF; + bool mEOF; +}; + +#endif // FDGETLINE__H + diff --git a/lib/common/FileModificationTime.cpp b/lib/common/FileModificationTime.cpp new file mode 100644 index 00000000..1109b15f --- /dev/null +++ b/lib/common/FileModificationTime.cpp @@ -0,0 +1,64 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileModificationTime.cpp +// Purpose: Function for getting file modification time. +// Created: 2010/02/15 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/stat.h> + +#include "BoxTime.h" +#include "FileModificationTime.h" + +#include "MemLeakFindOn.h" + +box_time_t FileModificationTime(EMU_STRUCT_STAT &st) +{ +#ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC + box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); +#else + box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#endif + + return datamodified; +} + +box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st) +{ + box_time_t statusmodified = +#ifdef HAVE_STRUCT_STAT_ST_MTIMESPEC + (((int64_t)st.st_ctimespec.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + (((int64_t)st.st_ctim.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctim.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIMENSEC + (((int64_t)st.st_ctimensec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); +#else // no nanoseconds anywhere + (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); +#endif + + return statusmodified; +} + +box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st) +{ +#ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC + box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); + box_time_t statusmodified = ((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL); +#else + box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); + box_time_t statusmodified = (((int64_t)st.st_ctimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#endif + + return (datamodified > statusmodified)?datamodified:statusmodified; +} + diff --git a/lib/common/FileModificationTime.h b/lib/common/FileModificationTime.h new file mode 100644 index 00000000..e6e6c172 --- /dev/null +++ b/lib/common/FileModificationTime.h @@ -0,0 +1,22 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileModificationTime.h +// Purpose: Function for getting file modification time. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef FILEMODIFICATIONTIME__H +#define FILEMODIFICATIONTIME__H + +#include <sys/stat.h> + +#include "BoxTime.h" + +box_time_t FileModificationTime(EMU_STRUCT_STAT &st); +box_time_t FileAttrModificationTime(EMU_STRUCT_STAT &st); +box_time_t FileModificationTimeMaxModAndAttr(EMU_STRUCT_STAT &st); + +#endif // FILEMODIFICATIONTIME__H + diff --git a/lib/common/FileStream.cpp b/lib/common/FileStream.cpp new file mode 100644 index 00000000..5be8237c --- /dev/null +++ b/lib/common/FileStream.cpp @@ -0,0 +1,447 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileStream.cpp +// Purpose: IOStream interface to files +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "FileStream.h" +#include "CommonException.h" +#include "Logging.h" + +#include <errno.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const char *, int, int) +// Purpose: Constructor, opens file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const std::string& rFilename, int flags, int mode) +#ifdef WIN32 + : mOSFileHandle(::openfile(rFilename.c_str(), flags, mode)), +#else + : mOSFileHandle(::open(rFilename.c_str(), flags, mode)), +#endif + mIsEOF(false), + mFileName(rFilename) +{ + AfterOpen(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const char *, int, int) +// Purpose: Alternative constructor, takes a const char *, +// avoids const strings being interpreted as handles! +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const char *pFilename, int flags, int mode) +#ifdef WIN32 + : mOSFileHandle(::openfile(pFilename, flags, mode)), +#else + : mOSFileHandle(::open(pFilename, flags, mode)), +#endif + mIsEOF(false), + mFileName(pFilename) +{ + AfterOpen(); +} + +void FileStream::AfterOpen() +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + + #ifdef WIN32 + BOX_LOG_WIN_WARNING_NUMBER("Failed to open file: " << + mFileName, winerrno); + #else + BOX_LOG_SYS_WARNING("Failed to open file: " << + mFileName); + #endif + + if(errno == EACCES) + { + THROW_EXCEPTION(CommonException, AccessDenied) + } + else + { + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(tOSFileHandle) +// Purpose: Constructor, using existing file descriptor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(tOSFileHandle FileDescriptor) + : mOSFileHandle(FileDescriptor), + mIsEOF(false), + mFileName("HANDLE") +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + BOX_ERROR("FileStream: called with invalid file handle"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } +} + +#if 0 +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const FileStream &) +// Purpose: Copy constructor, creates a duplicate of the file handle +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const FileStream &rToCopy) + : mOSFileHandle(::dup(rToCopy.mOSFileHandle)), + mIsEOF(rToCopy.mIsEOF) +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + BOX_ERROR("FileStream: copying unopened file"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } +} +#endif // 0 + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::~FileStream() +// Purpose: Destructor, closes file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::~FileStream() +{ + if(mOSFileHandle != INVALID_FILE) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int FileStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + int r; + DWORD numBytesRead = 0; + BOOL valid = ReadFile( + this->mOSFileHandle, + pBuffer, + NBytes, + &numBytesRead, + NULL + ); + + if(valid) + { + r = numBytesRead; + } + else if(GetLastError() == ERROR_BROKEN_PIPE) + { + r = 0; + } + else + { + BOX_LOG_WIN_ERROR("Failed to read from file: " << mFileName); + r = -1; + } +#else + int r = ::read(mOSFileHandle, pBuffer, NBytes); + if(r == -1) + { + BOX_LOG_SYS_ERROR("Failed to read from file: " << mFileName); + } +#endif + + if(r == -1) + { + THROW_EXCEPTION(CommonException, OSFileReadError) + } + + if(r == 0) + { + mIsEOF = true; + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +IOStream::pos_type FileStream::BytesLeftToRead() +{ + EMU_STRUCT_STAT st; + if(EMU_FSTAT(mOSFileHandle, &st) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + + return st.st_size - GetPosition(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Write(void *, int) +// Purpose: Writes bytes to the file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Write(const void *pBuffer, int NBytes) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + DWORD numBytesWritten = 0; + BOOL res = WriteFile( + this->mOSFileHandle, + pBuffer, + NBytes, + &numBytesWritten, + NULL + ); + + if ((res == 0) || (numBytesWritten != (DWORD)NBytes)) + { + // DWORD err = GetLastError(); + THROW_EXCEPTION(CommonException, OSFileWriteError) + } +#else + if(::write(mOSFileHandle, pBuffer, NBytes) != NBytes) + { + BOX_LOG_SYS_ERROR("Failed to write to file: " << mFileName); + THROW_EXCEPTION(CommonException, OSFileWriteError) + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type FileStream::GetPosition() const +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + LARGE_INTEGER conv; + + conv.HighPart = 0; + conv.LowPart = 0; + + conv.LowPart = SetFilePointer(this->mOSFileHandle, 0, &conv.HighPart, FILE_CURRENT); + + return (IOStream::pos_type)conv.QuadPart; +#else // ! WIN32 + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + + return (IOStream::pos_type)p; +#endif // WIN32 +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + LARGE_INTEGER conv; + + conv.QuadPart = Offset; + DWORD retVal = SetFilePointer(this->mOSFileHandle, conv.LowPart, &conv.HighPart, ConvertSeekTypeToOSWhence(SeekType)); + + if(retVal == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + THROW_EXCEPTION(CommonException, OSFileError) + } +#else // ! WIN32 + if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } +#endif // WIN32 + + // Not end of file any more! + mIsEOF = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Close() +// Purpose: Closes the underlying file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Close() +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileAlreadyClosed) + } + +#ifdef WIN32 + if(::CloseHandle(mOSFileHandle) == 0) +#else + if(::close(mOSFileHandle) != 0) +#endif + { + THROW_EXCEPTION(CommonException, OSFileCloseError) + } + + mOSFileHandle = INVALID_FILE; + mIsEOF = true; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool FileStream::StreamDataLeft() +{ + return !mIsEOF; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool FileStream::StreamClosed() +{ + return mIsEOF; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::CompareWith(IOStream&, int) +// Purpose: Compare bytes in this file with other stream's data +// Created: 2009/01/03 +// +// -------------------------------------------------------------------------- +bool FileStream::CompareWith(IOStream& rOther, int Timeout) +{ + // Size + IOStream::pos_type mySize = BytesLeftToRead(); + IOStream::pos_type otherSize = 0; + + // Test the contents + char buf1[2048]; + char buf2[2048]; + while(StreamDataLeft() && rOther.StreamDataLeft()) + { + int readSize = rOther.Read(buf1, sizeof(buf1), Timeout); + otherSize += readSize; + + if(Read(buf2, readSize) != readSize || + ::memcmp(buf1, buf2, readSize) != 0) + { + return false; + } + } + + // Check read all the data from the server and file -- can't be + // equal if local and remote aren't the same length. Can't use + // StreamDataLeft() test on local file, because if it's the same + // size, it won't know it's EOF yet. + + if(rOther.StreamDataLeft() || otherSize != mySize) + { + return false; + } + + return true; +} diff --git a/lib/common/FileStream.h b/lib/common/FileStream.h new file mode 100644 index 00000000..9101a968 --- /dev/null +++ b/lib/common/FileStream.h @@ -0,0 +1,66 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileStream.h +// Purpose: FileStream interface to files +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef FILESTREAM__H +#define FILESTREAM__H + +#include "IOStream.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +class FileStream : public IOStream +{ +public: + FileStream(const std::string& rFilename, + int flags = (O_RDONLY | O_BINARY), + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + + // Ensure that const char * name doesn't end up as a handle + // on Windows! + + FileStream(const char *pFilename, + int flags = (O_RDONLY | O_BINARY), + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + + FileStream(tOSFileHandle FileDescriptor); + + virtual ~FileStream(); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + bool CompareWith(IOStream& rOther, int Timeout = IOStream::TimeOutInfinite); + +private: + tOSFileHandle mOSFileHandle; + bool mIsEOF; + FileStream(const FileStream &rToCopy) { /* do not call */ } + void AfterOpen(); + + // for debugging.. + std::string mFileName; +}; + + +#endif // FILESTREAM__H + + diff --git a/lib/common/Guards.h b/lib/common/Guards.h new file mode 100644 index 00000000..cd2e4628 --- /dev/null +++ b/lib/common/Guards.h @@ -0,0 +1,121 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Guards.h +// Purpose: Classes which ensure things are closed/deleted properly when +// going out of scope. Easy exception proof code, etc +// Created: 2003/07/12 +// +// -------------------------------------------------------------------------- + +#ifndef GUARDS__H +#define GUARDS__H + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include <new> + +#include "CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +template <int flags = O_RDONLY | O_BINARY, int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)> +class FileHandleGuard +{ +public: + FileHandleGuard(const std::string& rFilename) + : mOSFileHandle(::open(rFilename.c_str(), flags, mode)) + { + if(mOSFileHandle < 0) + { + BOX_LOG_SYS_ERROR("FileHandleGuard: failed to open " + "file '" << rFilename << "'"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + } + + ~FileHandleGuard() + { + if(mOSFileHandle >= 0) + { + Close(); + } + } + + void Close() + { + if(mOSFileHandle < 0) + { + THROW_EXCEPTION(CommonException, FileAlreadyClosed) + } + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(CommonException, OSFileCloseError) + } + mOSFileHandle = -1; + } + + operator int() const + { + return mOSFileHandle; + } + +private: + int mOSFileHandle; +}; + +template<typename type> +class MemoryBlockGuard +{ +public: + MemoryBlockGuard(int BlockSize) + : mpBlock(::malloc(BlockSize)) + { + if(mpBlock == 0) + { + throw std::bad_alloc(); + } + } + + ~MemoryBlockGuard() + { + free(mpBlock); + } + + operator type() const + { + return (type)mpBlock; + } + + type GetPtr() const + { + return (type)mpBlock; + } + + void Resize(int NewSize) + { + void *ptrn = ::realloc(mpBlock, NewSize); + if(ptrn == 0) + { + throw std::bad_alloc(); + } + mpBlock = ptrn; + } + +private: + void *mpBlock; +}; + +#include "MemLeakFindOff.h" + +#endif // GUARDS__H + diff --git a/lib/common/IOStream.cpp b/lib/common/IOStream.cpp new file mode 100644 index 00000000..fc9d0bc3 --- /dev/null +++ b/lib/common/IOStream.cpp @@ -0,0 +1,251 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStream.cpp +// Purpose: I/O Stream abstraction +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "IOStream.h" +#include "CommonException.h" +#include "Guards.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::IOStream() +// Purpose: Constructor +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +IOStream::IOStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::~IOStream() +// Purpose: Destructor +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +IOStream::~IOStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Close() +// Purpose: Close the stream +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void IOStream::Close() +{ + // Do nothing by default -- let the destructor clear everything up. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Seek(int, int) +// Purpose: Seek in stream (if supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void IOStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + THROW_EXCEPTION(CommonException, NotSupported) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::GetPosition() +// Purpose: Returns current position in stream (if supported) +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type IOStream::GetPosition() const +{ + THROW_EXCEPTION(CommonException, NotSupported) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::ConvertSeekTypeToOSWhence(int) +// Purpose: Return an whence arg for lseek given a IOStream seek type +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +int IOStream::ConvertSeekTypeToOSWhence(int SeekType) +{ + // Should be nicely optimised out as values are choosen in header file to match OS values. + int ostype = SEEK_SET; + switch(SeekType) + { +#ifdef WIN32 + case SeekType_Absolute: + ostype = FILE_BEGIN; + break; + case SeekType_Relative: + ostype = FILE_CURRENT; + break; + case SeekType_End: + ostype = FILE_END; + break; +#else // ! WIN32 + case SeekType_Absolute: + ostype = SEEK_SET; + break; + case SeekType_Relative: + ostype = SEEK_CUR; + break; + case SeekType_End: + ostype = SEEK_END; + break; +#endif // WIN32 + + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + } + + return ostype; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::ReadFullBuffer(void *, int, int) +// Purpose: Reads bytes into buffer, returning whether or not it managed to +// get all the bytes required. Exception and abort use of stream +// if this returns false. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool IOStream::ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout) +{ + int bytesToGo = NBytes; + char *buffer = (char*)pBuffer; + if(pNBytesRead) (*pNBytesRead) = 0; + + while(bytesToGo > 0) + { + int bytesRead = Read(buffer, bytesToGo, Timeout); + if(bytesRead == 0) + { + // Timeout or something + return false; + } + // Increment things + bytesToGo -= bytesRead; + buffer += bytesRead; + if(pNBytesRead) (*pNBytesRead) += bytesRead; + } + + // Got everything + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::WriteAllBuffered() +// Purpose: Ensures that any data which has been buffered is written to the stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void IOStream::WriteAllBuffered() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::BytesLeftToRead() +// Purpose: Numbers of bytes left to read in the stream, or +// IOStream::SizeOfStreamUnknown if this isn't known. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type IOStream::BytesLeftToRead() +{ + return IOStream::SizeOfStreamUnknown; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::CopyStreamTo(IOStream &, int Timeout) +// Purpose: Copies the entire stream to another stream (reading from this, +// writing to rCopyTo). Returns whether the copy completed (ie +// StreamDataLeft() returns false) +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool IOStream::CopyStreamTo(IOStream &rCopyTo, int Timeout, int BufferSize) +{ + // Make sure there's something to do before allocating that buffer + if(!StreamDataLeft()) + { + return true; // complete, even though nothing happened + } + + // Buffer + MemoryBlockGuard<char*> buffer(BufferSize); + + // Get copying! + while(StreamDataLeft()) + { + // Read some data + int bytes = Read(buffer, BufferSize, Timeout); + if(bytes == 0 && StreamDataLeft()) + { + return false; // incomplete, timed out + } + + // Write some data + if(bytes != 0) + { + rCopyTo.Write(buffer, bytes); + } + } + + return true; // completed +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Flush(int Timeout) +// Purpose: Read and discard all remaining data in stream. +// Useful for protocol streams which must be flushed +// to avoid breaking the protocol. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- +void IOStream::Flush(int Timeout) +{ + char buffer[4096]; + + while(StreamDataLeft()) + { + Read(buffer, sizeof(buffer), Timeout); + } +} + +void IOStream::Write(const char *pBuffer) +{ + Write(pBuffer, strlen(pBuffer)); +} diff --git a/lib/common/IOStream.h b/lib/common/IOStream.h new file mode 100644 index 00000000..0b1cedd3 --- /dev/null +++ b/lib/common/IOStream.h @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStream.h +// Purpose: I/O Stream abstraction +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef IOSTREAM__H +#define IOSTREAM__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: IOStream +// Purpose: Abstract interface to streams of data +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class IOStream +{ +public: + IOStream(); + virtual ~IOStream(); + +private: + IOStream(const IOStream &rToCopy); /* forbidden */ + IOStream& operator=(const IOStream &rToCopy); /* forbidden */ + +public: + enum + { + TimeOutInfinite = -1, + SizeOfStreamUnknown = -1 + }; + + enum + { + SeekType_Absolute = 0, + SeekType_Relative = 1, + SeekType_End = 2 + }; + + // Timeout in milliseconds + // Read may return 0 -- does not mean end of stream. + typedef int64_t pos_type; + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) = 0; + virtual pos_type BytesLeftToRead(); // may return IOStream::SizeOfStreamUnknown (and will for most stream types) + virtual void Write(const void *pBuffer, int NBytes) = 0; + virtual void Write(const char *pBuffer); + virtual void WriteAllBuffered(); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual void Close(); + + // Has all data that can be read been read? + virtual bool StreamDataLeft() = 0; + // Has the stream been closed (writing not possible) + virtual bool StreamClosed() = 0; + + // Utility functions + bool ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout = IOStream::TimeOutInfinite); + bool CopyStreamTo(IOStream &rCopyTo, int Timeout = IOStream::TimeOutInfinite, int BufferSize = 1024); + void Flush(int Timeout = IOStream::TimeOutInfinite); + + static int ConvertSeekTypeToOSWhence(int SeekType); +}; + + +#endif // IOSTREAM__H + + diff --git a/lib/common/IOStreamGetLine.cpp b/lib/common/IOStreamGetLine.cpp new file mode 100644 index 00000000..27a77c29 --- /dev/null +++ b/lib/common/IOStreamGetLine.cpp @@ -0,0 +1,227 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStreamGetLine.cpp +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "IOStreamGetLine.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: IOStreamGetLine::IOStreamGetLine(int) +// Purpose: Constructor, taking file descriptor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +IOStreamGetLine::IOStreamGetLine(IOStream &Stream) + : mrStream(Stream), + mLineNumber(0), + mBufferBegin(0), + mBytesInBuffer(0), + mPendingEOF(false), + mEOF(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::~IOStreamGetLine() +// Purpose: Destructor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +IOStreamGetLine::~IOStreamGetLine() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::GetLine(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: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool IOStreamGetLine::GetLine(std::string &rOutput, bool Preprocess, int Timeout) +{ + // EOF? + if(mEOF) {THROW_EXCEPTION(CommonException, GetLineEOF)} + + // Initialise string to stored into + std::string r(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 + r += c; + } + + // Implicit line ending at EOF + if(mBufferBegin >= mBytesInBuffer && mPendingEOF) + { + foundLineEnd = true; + } + } + + // Check size + if(r.size() > IOSTREAMGETLINE_MAX_LINE_SIZE) + { + THROW_EXCEPTION(CommonException, GetLineTooLarge) + } + + // Read more in? + if(!foundLineEnd && mBufferBegin >= mBytesInBuffer && !mPendingEOF) + { + int bytes = mrStream.Read(mBuffer, sizeof(mBuffer), Timeout); + + // Adjust buffer info + mBytesInBuffer = bytes; + mBufferBegin = 0; + + // EOF / closed? + if(!mrStream.StreamDataLeft()) + { + mPendingEOF = true; + } + + // No data returned? + if(bytes == 0 && mrStream.StreamDataLeft()) + { + // store string away + mPendingString = r; + // 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) + { + rOutput = r; + return true; + } + else + { + // Check for comment char, but char before must be whitespace + int end = 0; + int size = r.size(); + while(end < size) + { + if(r[end] == '#' && (end == 0 || (iw(r[end-1])))) + { + break; + } + end++; + } + + // Remove whitespace + int begin = 0; + while(begin < size && iw(r[begin])) + { + begin++; + } + if(!iw(r[end])) end--; + while(end > begin && iw(r[end])) + { + end--; + } + + // Return a sub string + rOutput = r.substr(begin, end - begin + 1); + return true; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::DetachFile() +// Purpose: Detaches the file handle, setting the file pointer correctly. +// Probably not good for sockets... +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +void IOStreamGetLine::DetachFile() +{ + // Adjust file pointer + int bytesOver = mBytesInBuffer - mBufferBegin; + ASSERT(bytesOver >= 0); + if(bytesOver > 0) + { + mrStream.Seek(0 - bytesOver, IOStream::SeekType_Relative); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::IgnoreBufferedData(int) +// Purpose: Ignore buffered bytes (effectively removing them from the +// beginning of the buffered data.) +// Cannot remove more bytes than are currently in the buffer. +// Be careful when this is used! +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void IOStreamGetLine::IgnoreBufferedData(int BytesToIgnore) +{ + int bytesInBuffer = mBytesInBuffer - mBufferBegin; + if(BytesToIgnore < 0 || BytesToIgnore > bytesInBuffer) + { + THROW_EXCEPTION(CommonException, IOStreamGetLineNotEnoughDataToIgnore) + } + mBufferBegin += BytesToIgnore; +} + + + diff --git a/lib/common/IOStreamGetLine.h b/lib/common/IOStreamGetLine.h new file mode 100644 index 00000000..9a5d1818 --- /dev/null +++ b/lib/common/IOStreamGetLine.h @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStreamGetLine.h +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#ifndef IOSTREAMGETLINE__H +#define IOSTREAMGETLINE__H + +#include <string> + +#include "IOStream.h" + +#ifdef BOX_RELEASE_BUILD + #define IOSTREAMGETLINE_BUFFER_SIZE 1024 +#else + #define IOSTREAMGETLINE_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 IOSTREAMGETLINE_MAX_LINE_SIZE (1024*256) + +// -------------------------------------------------------------------------- +// +// Class +// Name: IOStreamGetLine +// Purpose: Line based stream reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +class IOStreamGetLine +{ +public: + IOStreamGetLine(IOStream &Stream); + ~IOStreamGetLine(); +private: + IOStreamGetLine(const IOStreamGetLine &rToCopy); + +public: + bool GetLine(std::string &rOutput, bool Preprocess = false, int Timeout = IOStream::TimeOutInfinite); + bool IsEOF() {return mEOF;} + int GetLineNumber() {return mLineNumber;} + + // Call to detach, setting file pointer correctly to last bit read. + // Only works for lseek-able file descriptors. + void DetachFile(); + + // 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;} + +private: + char mBuffer[IOSTREAMGETLINE_BUFFER_SIZE]; + IOStream &mrStream; + int mLineNumber; + int mBufferBegin; + int mBytesInBuffer; + bool mPendingEOF; + bool mEOF; + std::string mPendingString; +}; + +#endif // IOSTREAMGETLINE__H + diff --git a/lib/common/InvisibleTempFileStream.cpp b/lib/common/InvisibleTempFileStream.cpp new file mode 100644 index 00000000..abfcb5f6 --- /dev/null +++ b/lib/common/InvisibleTempFileStream.cpp @@ -0,0 +1,39 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: InvisibleTempFileStream.cpp +// Purpose: IOStream interface to temporary files that +// delete themselves +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "InvisibleTempFileStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: InvisibleTempFileStream::InvisibleTempFileStream +// (const char *, int, int) +// Purpose: Constructor, opens invisible file +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- +InvisibleTempFileStream::InvisibleTempFileStream(const char *Filename, int flags, int mode) +#ifdef WIN32 + : FileStream(Filename, flags | O_TEMPORARY, mode) +#else + : FileStream(Filename, flags, mode) +#endif +{ + #ifndef WIN32 + if(unlink(Filename) != 0) + { + MEMLEAKFINDER_NOT_A_LEAK(this); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + #endif +} diff --git a/lib/common/InvisibleTempFileStream.h b/lib/common/InvisibleTempFileStream.h new file mode 100644 index 00000000..a77d05e2 --- /dev/null +++ b/lib/common/InvisibleTempFileStream.h @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: InvisibleTempFileStream.h +// Purpose: FileStream interface to temporary files that +// delete themselves +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- + +#ifndef INVISIBLETEMPFILESTREAM__H +#define INVISIBLETEMPFILESTREAM__H + +#include "FileStream.h" + +class InvisibleTempFileStream : public FileStream +{ +public: + InvisibleTempFileStream(const char *Filename, +#ifdef WIN32 + int flags = (O_RDONLY | O_BINARY), +#else + int flags = O_RDONLY, +#endif + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + +private: + InvisibleTempFileStream(const InvisibleTempFileStream &rToCopy) + : FileStream(INVALID_FILE) + { /* do not call */ } +}; + +#endif // INVISIBLETEMPFILESTREAM__H + + diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp new file mode 100644 index 00000000..296443ea --- /dev/null +++ b/lib/common/Logging.cpp @@ -0,0 +1,518 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Logging.cpp +// Purpose: Generic logging core routines implementation +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <time.h> +#include <string.h> // for stderror + +// c.f. http://bugs.debian.org/512510 +#include <cstdio> + +#ifdef HAVE_SYSLOG_H + #include <syslog.h> +#endif +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <cstring> +#include <iomanip> + +#include "BoxTime.h" +#include "Logging.h" + +bool Logging::sLogToSyslog = false; +bool Logging::sLogToConsole = false; +bool Logging::sContextSet = false; + +bool HideExceptionMessageGuard::sHiddenState = false; + +std::vector<Logger*> Logging::sLoggers; +std::string Logging::sContext; +Console* Logging::spConsole = NULL; +Syslog* Logging::spSyslog = NULL; +Log::Level Logging::sGlobalLevel = Log::EVERYTHING; +Logging Logging::sGlobalLogging; //automatic initialisation +std::string Logging::sProgramName; + +Logging::Logging() +{ + ASSERT(!spConsole); + ASSERT(!spSyslog); + spConsole = new Console(); + spSyslog = new Syslog(); + sLogToConsole = true; + sLogToSyslog = true; +} + +Logging::~Logging() +{ + sLogToConsole = false; + sLogToSyslog = false; + delete spConsole; + delete spSyslog; + spConsole = NULL; + spSyslog = NULL; +} + +void Logging::ToSyslog(bool enabled) +{ + if (!sLogToSyslog && enabled) + { + Add(spSyslog); + } + + if (sLogToSyslog && !enabled) + { + Remove(spSyslog); + } + + sLogToSyslog = enabled; +} + +void Logging::ToConsole(bool enabled) +{ + if (!sLogToConsole && enabled) + { + Add(spConsole); + } + + if (sLogToConsole && !enabled) + { + Remove(spConsole); + } + + sLogToConsole = enabled; +} + +void Logging::FilterConsole(Log::Level level) +{ + spConsole->Filter(level); +} + +void Logging::FilterSyslog(Log::Level level) +{ + spSyslog->Filter(level); +} + +void Logging::Add(Logger* pNewLogger) +{ + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + if (*i == pNewLogger) + { + return; + } + } + + sLoggers.insert(sLoggers.begin(), pNewLogger); +} + +void Logging::Remove(Logger* pOldLogger) +{ + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + if (*i == pOldLogger) + { + sLoggers.erase(i); + return; + } + } +} + +void Logging::Log(Log::Level level, const std::string& rFile, + int line, const std::string& rMessage) +{ + if (level > sGlobalLevel) + { + return; + } + + std::string newMessage; + + if (sContextSet) + { + newMessage += "[" + sContext + "] "; + } + + newMessage += rMessage; + + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + bool result = (*i)->Log(level, rFile, line, newMessage); + if (!result) + { + return; + } + } +} + +void Logging::LogToSyslog(Log::Level level, const std::string& rFile, + int line, const std::string& rMessage) +{ + if (!sLogToSyslog) + { + return; + } + + if (level > sGlobalLevel) + { + return; + } + + std::string newMessage; + + if (sContextSet) + { + newMessage += "[" + sContext + "] "; + } + + newMessage += rMessage; + + spSyslog->Log(level, rFile, line, newMessage); +} + +void Logging::SetContext(std::string context) +{ + sContext = context; + sContextSet = true; +} + +Log::Level Logging::GetNamedLevel(const std::string& rName) +{ + if (rName == "nothing") { return Log::NOTHING; } + else if (rName == "fatal") { return Log::FATAL; } + else if (rName == "error") { return Log::ERROR; } + else if (rName == "warning") { return Log::WARNING; } + else if (rName == "notice") { return Log::NOTICE; } + else if (rName == "info") { return Log::INFO; } + else if (rName == "trace") { return Log::TRACE; } + else if (rName == "everything") { return Log::EVERYTHING; } + else + { + BOX_ERROR("Unknown verbosity level: " << rName); + return Log::INVALID; + } +} + +void Logging::ClearContext() +{ + sContextSet = false; +} + +void Logging::SetProgramName(const std::string& rProgramName) +{ + sProgramName = rProgramName; + + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + (*i)->SetProgramName(rProgramName); + } +} + +void Logging::SetFacility(int facility) +{ + spSyslog->SetFacility(facility); +} + +Logger::Logger() +: mCurrentLevel(Log::EVERYTHING) +{ + Logging::Add(this); +} + +Logger::Logger(Log::Level Level) +: mCurrentLevel(Level) +{ + Logging::Add(this); +} + +Logger::~Logger() +{ + Logging::Remove(this); +} + +bool Console::sShowTime = false; +bool Console::sShowTimeMicros = false; +bool Console::sShowTag = false; +bool Console::sShowPID = false; +std::string Console::sTag; + +void Console::SetProgramName(const std::string& rProgramName) +{ + sTag = rProgramName; +} + +void Console::SetShowTag(bool enabled) +{ + sShowTag = enabled; +} + +void Console::SetShowTime(bool enabled) +{ + sShowTime = enabled; +} + +void Console::SetShowTimeMicros(bool enabled) +{ + sShowTimeMicros = enabled; +} + +void Console::SetShowPID(bool enabled) +{ + sShowPID = enabled; +} + +bool Console::Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage) +{ + if (level > GetLevel()) + { + return true; + } + + FILE* target = stdout; + + if (level <= Log::WARNING) + { + target = stderr; + } + + std::ostringstream buf; + + if (sShowTime) + { + buf << FormatTime(GetCurrentBoxTime(), false, sShowTimeMicros); + buf << " "; + } + + if (sShowTag) + { + if (sShowPID) + { + buf << "[" << sTag << " " << getpid() << "] "; + } + else + { + buf << "[" << sTag << "] "; + } + } + else if (sShowPID) + { + buf << "[" << getpid() << "] "; + } + + if (level <= Log::FATAL) + { + buf << "FATAL: "; + } + else if (level <= Log::ERROR) + { + buf << "ERROR: "; + } + else if (level <= Log::WARNING) + { + buf << "WARNING: "; + } + else if (level <= Log::NOTICE) + { + buf << "NOTICE: "; + } + else if (level <= Log::INFO) + { + buf << "INFO: "; + } + else if (level <= Log::TRACE) + { + buf << "TRACE: "; + } + + buf << rMessage; + + #ifdef WIN32 + std::string output = buf.str(); + ConvertUtf8ToConsole(output.c_str(), output); + fprintf(target, "%s\n", output.c_str()); + #else + fprintf(target, "%s\n", buf.str().c_str()); + #endif + + return true; +} + +bool Syslog::Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage) +{ + if (level > GetLevel()) + { + return true; + } + + int syslogLevel = LOG_ERR; + + switch(level) + { + case Log::NOTHING: /* fall through */ + case Log::INVALID: /* fall through */ + case Log::FATAL: syslogLevel = LOG_CRIT; break; + case Log::ERROR: syslogLevel = LOG_ERR; break; + case Log::WARNING: syslogLevel = LOG_WARNING; break; + case Log::NOTICE: syslogLevel = LOG_NOTICE; break; + case Log::INFO: syslogLevel = LOG_INFO; break; + case Log::TRACE: /* fall through */ + case Log::EVERYTHING: syslogLevel = LOG_DEBUG; break; + } + + std::string msg; + + if (level <= Log::FATAL) + { + msg = "FATAL: "; + } + else if (level <= Log::ERROR) + { + msg = "ERROR: "; + } + else if (level <= Log::WARNING) + { + msg = "WARNING: "; + } + else if (level <= Log::NOTICE) + { + msg = "NOTICE: "; + } + + msg += rMessage; + + syslog(syslogLevel, "%s", msg.c_str()); + + return true; +} + +Syslog::Syslog() : mFacility(LOG_LOCAL6) +{ + ::openlog("Box Backup", LOG_PID, mFacility); +} + +Syslog::~Syslog() +{ + ::closelog(); +} + +void Syslog::SetProgramName(const std::string& rProgramName) +{ + mName = rProgramName; + ::closelog(); + ::openlog(mName.c_str(), LOG_PID, mFacility); +} + +void Syslog::SetFacility(int facility) +{ + mFacility = facility; + ::closelog(); + ::openlog(mName.c_str(), LOG_PID, mFacility); +} + +int Syslog::GetNamedFacility(const std::string& rFacility) +{ + #define CASE_RETURN(x) if (rFacility == #x) { return LOG_ ## x; } + CASE_RETURN(LOCAL0) + CASE_RETURN(LOCAL1) + CASE_RETURN(LOCAL2) + CASE_RETURN(LOCAL3) + CASE_RETURN(LOCAL4) + CASE_RETURN(LOCAL5) + CASE_RETURN(LOCAL6) + CASE_RETURN(DAEMON) + #undef CASE_RETURN + + BOX_ERROR("Unknown log facility '" << rFacility << "', " + "using default LOCAL6"); + return LOG_LOCAL6; +} + +bool FileLogger::Log(Log::Level Level, const std::string& rFile, + int line, std::string& rMessage) +{ + if (Level > GetLevel()) + { + return true; + } + + /* avoid infinite loop if this throws an exception */ + Logging::Remove(this); + + std::ostringstream buf; + buf << FormatTime(GetCurrentBoxTime(), true, false); + buf << " "; + + if (Level <= Log::FATAL) + { + buf << "[FATAL] "; + } + else if (Level <= Log::ERROR) + { + buf << "[ERROR] "; + } + else if (Level <= Log::WARNING) + { + buf << "[WARNING] "; + } + else if (Level <= Log::NOTICE) + { + buf << "[NOTICE] "; + } + else if (Level <= Log::INFO) + { + buf << "[INFO] "; + } + else if (Level <= Log::TRACE) + { + buf << "[TRACE] "; + } + + buf << rMessage << "\n"; + std::string output = buf.str(); + + #ifdef WIN32 + ConvertUtf8ToConsole(output.c_str(), output); + #endif + + mLogFile.Write(output.c_str(), output.length()); + + Logging::Add(this); + return true; +} + +std::string PrintEscapedBinaryData(const std::string& rInput) +{ + std::ostringstream output; + + for (size_t i = 0; i < rInput.length(); i++) + { + if (isprint(rInput[i])) + { + output << rInput[i]; + } + else + { + output << "\\x" << std::hex << std::setw(2) << + std::setfill('0') << (int) rInput[i] << + std::dec; + } + } + + return output.str(); +} diff --git a/lib/common/Logging.h b/lib/common/Logging.h new file mode 100644 index 00000000..15400711 --- /dev/null +++ b/lib/common/Logging.h @@ -0,0 +1,346 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Logging.h +// Purpose: Generic logging core routines declarations and macros +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +#ifndef LOGGING__H +#define LOGGING__H + +#include <cerrno> +#include <cstring> +#include <iomanip> +#include <sstream> +#include <vector> + +#include "FileStream.h" + +#define BOX_LOG(level, stuff) \ +{ \ + std::ostringstream _box_log_line; \ + _box_log_line << stuff; \ + Logging::Log(level, __FILE__, __LINE__, _box_log_line.str()); \ +} + +#define BOX_SYSLOG(level, stuff) \ +{ \ + std::ostringstream _box_log_line; \ + _box_log_line << stuff; \ + Logging::LogToSyslog(level, __FILE__, __LINE__, _box_log_line.str()); \ +} + +#define BOX_FATAL(stuff) BOX_LOG(Log::FATAL, stuff) +#define BOX_ERROR(stuff) BOX_LOG(Log::ERROR, stuff) +#define BOX_WARNING(stuff) BOX_LOG(Log::WARNING, stuff) +#define BOX_NOTICE(stuff) BOX_LOG(Log::NOTICE, stuff) +#define BOX_INFO(stuff) BOX_LOG(Log::INFO, stuff) +#define BOX_TRACE(stuff) \ + if (Logging::IsEnabled(Log::TRACE)) \ + { BOX_LOG(Log::TRACE, stuff) } + +#define BOX_SYS_ERROR(stuff) \ + stuff << ": " << std::strerror(errno) << " (" << errno << ")" + +#define BOX_LOG_SYS_WARNING(stuff) \ + BOX_WARNING(BOX_SYS_ERROR(stuff)) +#define BOX_LOG_SYS_ERROR(stuff) \ + BOX_ERROR(BOX_SYS_ERROR(stuff)) +#define BOX_LOG_SYS_FATAL(stuff) \ + BOX_FATAL(BOX_SYS_ERROR(stuff)) + +#define LOG_AND_THROW_ERROR(message, filename, exception, subtype) \ + BOX_LOG_SYS_ERROR(message << ": " << filename); \ + THROW_EXCEPTION_MESSAGE(exception, subtype, \ + BOX_SYS_ERROR(message << ": " << filename)); + +inline std::string GetNativeErrorMessage() +{ +#ifdef WIN32 + return GetErrorMessage(GetLastError()); +#else + std::ostringstream _box_log_line; + _box_log_line << std::strerror(errno) << " (" << errno << ")"; + return _box_log_line.str(); +#endif +} + +#ifdef WIN32 + #define BOX_LOG_WIN_ERROR(stuff) \ + BOX_ERROR(stuff << ": " << GetErrorMessage(GetLastError())) + #define BOX_LOG_WIN_WARNING(stuff) \ + BOX_WARNING(stuff << ": " << GetErrorMessage(GetLastError())) + #define BOX_LOG_WIN_ERROR_NUMBER(stuff, number) \ + BOX_ERROR(stuff << ": " << GetErrorMessage(number)) + #define BOX_LOG_WIN_WARNING_NUMBER(stuff, number) \ + 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) +#else + #define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_SYS_ERROR(stuff) + #define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_SYS_WARNING(stuff) +#endif + +#define BOX_LOG_SOCKET_ERROR(_type, _name, _port, stuff) \ + BOX_LOG_NATIVE_ERROR(stuff << " (type " << _type << ", name " << \ + _name << ", port " << _port << ")") + +#define BOX_FORMAT_HEX32(number) \ + std::hex << \ + std::showbase << \ + std::internal << \ + std::setw(10) << \ + std::setfill('0') << \ + (number) << \ + std::dec + +#define BOX_FORMAT_ACCOUNT(accno) \ + BOX_FORMAT_HEX32(accno) + +#define BOX_FORMAT_OBJECTID(objectid) \ + std::hex << \ + std::showbase << \ + (objectid) << \ + std::dec + +#define BOX_FORMAT_TIMESPEC(timespec) \ + timespec.tv_sec << \ + std::setw(6) << \ + timespec.tv_usec + +#undef ERROR + +namespace Log +{ + enum Level + { + NOTHING = 1, + FATAL, + ERROR, + WARNING, + NOTICE, + INFO, + TRACE, + EVERYTHING, + INVALID = -1 + }; +} + +// -------------------------------------------------------------------------- +// +// Class +// Name: Logger +// Purpose: Abstract base class for log targets +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Logger +{ + private: + Log::Level mCurrentLevel; + + public: + Logger(); + Logger(Log::Level level); + virtual ~Logger(); + + virtual bool Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage) = 0; + + void Filter(Log::Level level) + { + mCurrentLevel = level; + } + + virtual const char* GetType() = 0; + Log::Level GetLevel() { return mCurrentLevel; } + + virtual void SetProgramName(const std::string& rProgramName) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Console +// Purpose: Console logging target +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Console : public Logger +{ + private: + static bool sShowTag; + static bool sShowTime; + static bool sShowTimeMicros; + static bool sShowPID; + static std::string sTag; + + public: + virtual bool Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage); + virtual const char* GetType() { return "Console"; } + virtual void SetProgramName(const std::string& rProgramName); + + static void SetShowTag(bool enabled); + static void SetShowTime(bool enabled); + static void SetShowTimeMicros(bool enabled); + static void SetShowPID(bool enabled); +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Syslog +// Purpose: Syslog (or Windows Event Viewer) logging target +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Syslog : public Logger +{ + private: + std::string mName; + int mFacility; + + public: + Syslog(); + virtual ~Syslog(); + + virtual bool Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage); + virtual const char* GetType() { return "Syslog"; } + virtual void SetProgramName(const std::string& rProgramName); + virtual void SetFacility(int facility); + static int GetNamedFacility(const std::string& rFacility); +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Logging +// Purpose: Static logging helper, keeps track of enabled loggers +// and distributes log messages to them. +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Logging +{ + private: + static std::vector<Logger*> sLoggers; + static bool sLogToSyslog, sLogToConsole; + static std::string sContext; + static bool sContextSet; + static Console* spConsole; + static Syslog* spSyslog; + static Log::Level sGlobalLevel; + static Logging sGlobalLogging; + static std::string sProgramName; + + public: + Logging (); + ~Logging(); + static void ToSyslog (bool enabled); + static void ToConsole (bool enabled); + static void FilterSyslog (Log::Level level); + static void FilterConsole (Log::Level level); + static void Add (Logger* pNewLogger); + static void Remove (Logger* pOldLogger); + static void Log(Log::Level level, const std::string& rFile, + int line, const std::string& rMessage); + static void LogToSyslog(Log::Level level, const std::string& rFile, + int line, const std::string& rMessage); + static void SetContext(std::string context); + static void ClearContext(); + static void SetGlobalLevel(Log::Level level) { sGlobalLevel = level; } + static Log::Level GetGlobalLevel() { return sGlobalLevel; } + static Log::Level GetNamedLevel(const std::string& rName); + static bool IsEnabled(Log::Level level) + { + return (int)sGlobalLevel >= (int)level; + } + static void SetProgramName(const std::string& rProgramName); + static std::string GetProgramName() { return sProgramName; } + static void SetFacility(int facility); + + class Guard + { + private: + Log::Level mOldLevel; + + public: + Guard(Log::Level newLevel) + { + mOldLevel = Logging::GetGlobalLevel(); + Logging::SetGlobalLevel(newLevel); + } + ~Guard() + { + Logging::SetGlobalLevel(mOldLevel); + } + }; + + class Tagger + { + private: + std::string mOldTag; + + public: + Tagger(const std::string& rTempTag) + { + mOldTag = Logging::GetProgramName(); + Logging::SetProgramName(mOldTag + " " + rTempTag); + } + ~Tagger() + { + Logging::SetProgramName(mOldTag); + } + }; +}; + +class FileLogger : public Logger +{ + private: + FileStream mLogFile; + FileLogger(const FileLogger& forbidden) + : mLogFile("") { /* do not call */ } + + public: + FileLogger(const std::string& rFileName, Log::Level Level) + : Logger(Level), + mLogFile(rFileName, O_WRONLY | O_CREAT | O_APPEND) + { } + + virtual bool Log(Log::Level Level, const std::string& rFile, + int Line, std::string& rMessage); + + virtual const char* GetType() { return "FileLogger"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +class HideExceptionMessageGuard +{ + public: + HideExceptionMessageGuard() + { + mOldHiddenState = sHiddenState; + sHiddenState = true; + } + ~HideExceptionMessageGuard() + { + sHiddenState = mOldHiddenState; + } + static bool ExceptionsHidden() { return sHiddenState; } + + private: + static bool sHiddenState; + bool mOldHiddenState; +}; + +std::string PrintEscapedBinaryData(const std::string& rInput); + +#endif // LOGGING__H diff --git a/lib/common/MainHelper.h b/lib/common/MainHelper.h new file mode 100644 index 00000000..d91bc2f9 --- /dev/null +++ b/lib/common/MainHelper.h @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MainHelper.h +// Purpose: Helper stuff for main() programs +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#ifndef MAINHELPER__H +#define MAINHELPER__H + +#include <stdio.h> + +#include "BoxException.h" + +#define MAINHELPER_START \ + if(argc == 2 && ::strcmp(argv[1], "--version") == 0) \ + { printf(BOX_VERSION "\n"); return 0; } \ + MEMLEAKFINDER_INIT \ + MEMLEAKFINDER_START \ + try { +#define MAINHELPER_END \ + } catch(BoxException &e) { \ + printf("Exception: %s (%d/%d)\n", e.what(), e.GetType(), e.GetSubType()); \ + return 1; \ + } catch(std::exception &e) { \ + printf("Exception: %s\n", e.what()); \ + return 1; \ + } catch(...) { \ + printf("Exception: <UNKNOWN>\n"); \ + return 1; } + +#ifdef BOX_MEMORY_LEAK_TESTING + #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) \ + memleakfinder_setup_exit_report(file, marker); +#else + #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) +#endif // BOX_MEMORY_LEAK_TESTING + + +#endif // MAINHELPER__H + diff --git a/lib/common/Makefile.extra b/lib/common/Makefile.extra new file mode 100644 index 00000000..cc3f3a7a --- /dev/null +++ b/lib/common/Makefile.extra @@ -0,0 +1,11 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CommonException.h autogen_CommonException.cpp: $(MAKEEXCEPTION) CommonException.txt + $(_PERL) $(MAKEEXCEPTION) CommonException.txt + +# AUTOGEN SEEDING +autogen_ConversionException.h autogen_ConversionException.cpp: $(MAKEEXCEPTION) ConversionException.txt + $(_PERL) $(MAKEEXCEPTION) ConversionException.txt + diff --git a/lib/common/MemBlockStream.cpp b/lib/common/MemBlockStream.cpp new file mode 100644 index 00000000..538a7ef8 --- /dev/null +++ b/lib/common/MemBlockStream.cpp @@ -0,0 +1,235 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemBlockStream.cpp +// Purpose: Stream out data from any memory block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "MemBlockStream.h" +#include "CommonException.h" +#include "StreamableMemBlock.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream() +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const void *pBuffer, int Size) + : mpBuffer((char*)pBuffer), + mBytesInBuffer(Size), + mReadPosition(0) +{ + ASSERT(pBuffer != 0); + ASSERT(Size >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const StreamableMemBlock &rBlock) + : mpBuffer((char*)rBlock.GetBuffer()), + mBytesInBuffer(rBlock.GetSize()), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const CollectInBufferStream &rBuffer) + : mpBuffer((char*)rBuffer.GetBuffer()), + mBytesInBuffer(rBuffer.GetSize()), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const MemBlockStream &) +// Purpose: Copy constructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const MemBlockStream &rToCopy) + : mpBuffer(rToCopy.mpBuffer), + mBytesInBuffer(rToCopy.mBytesInBuffer), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::~MemBlockStream() +// Purpose: Destructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::~MemBlockStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Read(void *, int, int) +// Purpose: As interface. But only works in read phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +int MemBlockStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Adjust to number of bytes left + if(NBytes > (mBytesInBuffer - mReadPosition)) + { + NBytes = (mBytesInBuffer - mReadPosition); + } + ASSERT(NBytes >= 0); + if(NBytes <= 0) return 0; // careful now + + // Copy in the requested number of bytes and adjust the read pointer + ::memcpy(pBuffer, mpBuffer + mReadPosition, NBytes); + mReadPosition += NBytes; + + return NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::BytesLeftToRead() +// Purpose: As interface. But only works in read phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type MemBlockStream::BytesLeftToRead() +{ + return (mBytesInBuffer - mReadPosition); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Write(void *, int) +// Purpose: As interface. But only works in write phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void MemBlockStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, MemBlockStreamNotSupported) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::GetPosition() +// Purpose: In write phase, returns the number of bytes written, in read +// phase, the number of bytes to go +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type MemBlockStream::GetPosition() const +{ + return mReadPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Seek(pos_type, int) +// Purpose: As interface. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void MemBlockStream::Seek(pos_type Offset, int SeekType) +{ + int newPos = 0; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newPos = Offset; + break; + case IOStream::SeekType_Relative: + newPos = mReadPosition + Offset; + break; + case IOStream::SeekType_End: + newPos = mBytesInBuffer + Offset; + break; + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + break; + } + + // Make sure it doesn't go over + if(newPos > mBytesInBuffer) + { + newPos = mBytesInBuffer; + } + // or under + if(newPos < 0) + { + newPos = 0; + } + + // Set the new read position + mReadPosition = newPos; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::StreamDataLeft() +// Purpose: As interface +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +bool MemBlockStream::StreamDataLeft() +{ + return mReadPosition < mBytesInBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::StreamClosed() +// Purpose: As interface +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +bool MemBlockStream::StreamClosed() +{ + return true; +} + diff --git a/lib/common/MemBlockStream.h b/lib/common/MemBlockStream.h new file mode 100644 index 00000000..f78ff8e6 --- /dev/null +++ b/lib/common/MemBlockStream.h @@ -0,0 +1,52 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemBlockStream.h +// Purpose: Stream out data from any memory block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#ifndef MEMBLOCKSTREAM__H +#define MEMBLOCKSTREAM__H + +#include "IOStream.h" + +class StreamableMemBlock; +class CollectInBufferStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: MemBlockStream +// Purpose: Stream out data from any memory block -- be careful the lifetime +// of the block is greater than the lifetime of this stream. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +class MemBlockStream : public IOStream +{ +public: + MemBlockStream(const void *pBuffer, int Size); + MemBlockStream(const StreamableMemBlock &rBlock); + MemBlockStream(const CollectInBufferStream &rBuffer); + MemBlockStream(const MemBlockStream &rToCopy); + ~MemBlockStream(); +public: + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + const char *mpBuffer; + int mBytesInBuffer; + int mReadPosition; +}; + +#endif // MEMBLOCKSTREAM__H + diff --git a/lib/common/MemLeakFindOff.h b/lib/common/MemLeakFindOff.h new file mode 100644 index 00000000..1cc98bac --- /dev/null +++ b/lib/common/MemLeakFindOff.h @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFindOff.h +// Purpose: Switch memory leak finding off +// Created: 13/1/04 +// +// -------------------------------------------------------------------------- + +// no header guard + +#ifdef BOX_MEMORY_LEAK_TESTING + +#undef new + +#ifndef MEMLEAKFINDER_FULL_MALLOC_MONITORING + #ifdef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #undef malloc + #undef realloc + #undef free + #undef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #endif +#endif + +#undef MEMLEAKFINDER_ENABLED + +#endif diff --git a/lib/common/MemLeakFindOn.h b/lib/common/MemLeakFindOn.h new file mode 100644 index 00000000..c20fe25a --- /dev/null +++ b/lib/common/MemLeakFindOn.h @@ -0,0 +1,25 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFindOn.h +// Purpose: Switch memory leak finding on +// Created: 13/1/04 +// +// -------------------------------------------------------------------------- + +// no header guard + +#ifdef BOX_MEMORY_LEAK_TESTING + +#define new DEBUG_NEW + +#ifndef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__) + #define realloc memleakfinder_realloc + #define free memleakfinder_free + #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED +#endif + +#define MEMLEAKFINDER_ENABLED + +#endif diff --git a/lib/common/MemLeakFinder.h b/lib/common/MemLeakFinder.h new file mode 100644 index 00000000..ca207bd5 --- /dev/null +++ b/lib/common/MemLeakFinder.h @@ -0,0 +1,63 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFinder.h +// Purpose: Memory leak finder +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef MEMLEAKFINDER__H +#define MEMLEAKFINDER__H + +#ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING + // include stdlib now, to avoid problems with having the macros defined already + #include <cstdlib> +#endif + +// global enable flag +extern bool memleakfinder_global_enable; + +class MemLeakSuppressionGuard +{ + public: + MemLeakSuppressionGuard(); + ~MemLeakSuppressionGuard(); +}; + +extern "C" +{ + void *memleakfinder_malloc(size_t size, const char *file, int line); + void *memleakfinder_realloc(void *ptr, size_t size); + void memleakfinder_free(void *ptr); +} + +void memleakfinder_init(); + +int memleakfinder_numleaks(); + +void memleakfinder_reportleaks(); + +void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext); + +void memleakfinder_setup_exit_report(const char *filename, const char *markertext); + +void memleakfinder_startsectionmonitor(); + +void memleakfinder_traceblocksinsection(); + +void memleakfinder_notaleak(void *ptr); + +void *operator new (size_t size, const char *file, int line); +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__) + #define realloc memleakfinder_realloc + #define free memleakfinder_free + #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED +#endif + +#endif // MEMLEAKFINDER__H + diff --git a/lib/common/NamedLock.cpp b/lib/common/NamedLock.cpp new file mode 100644 index 00000000..f96f80b5 --- /dev/null +++ b/lib/common/NamedLock.cpp @@ -0,0 +1,170 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: NamedLock.cpp +// Purpose: A global named lock, implemented as a lock file in +// file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <fcntl.h> +#include <errno.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#ifdef HAVE_FLOCK + #include <sys/file.h> +#endif + +#include "NamedLock.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::NamedLock() +// Purpose: Constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +NamedLock::NamedLock() + : mFileDescriptor(-1) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::~NamedLock() +// Purpose: Destructor (automatically unlocks if locked) +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +NamedLock::~NamedLock() +{ + if(mFileDescriptor != -1) + { + ReleaseLock(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::TryAndGetLock(const char *, int) +// Purpose: Tries to get a lock on the name in the file system. +// IMPORTANT NOTE: If a file exists with this name, it +// will be deleted. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +bool NamedLock::TryAndGetLock(const std::string& rFilename, int mode) +{ + // Check + if(mFileDescriptor != -1) + { + THROW_EXCEPTION(CommonException, NamedLockAlreadyLockingSomething) + } + + // See if the lock can be got +#if HAVE_DECL_O_EXLOCK + int fd = ::open(rFilename.c_str(), + O_WRONLY | O_NONBLOCK | O_CREAT | O_TRUNC | O_EXLOCK, mode); + if(fd != -1) + { + // Got a lock, lovely + mFileDescriptor = fd; + return true; + } + + // Failed. Why? + if(errno != EWOULDBLOCK) + { + // Not the expected error + THROW_EXCEPTION(CommonException, OSFileError) + } + + return false; +#else + int fd = ::open(rFilename.c_str(), O_WRONLY | O_CREAT | O_TRUNC, mode); + if(fd == -1) + { + BOX_WARNING("Failed to open lockfile: " << rFilename); + THROW_EXCEPTION(CommonException, OSFileError) + } + +#ifdef HAVE_FLOCK + if(::flock(fd, LOCK_EX | LOCK_NB) != 0) + { + ::close(fd); + if(errno == EWOULDBLOCK) + { + return false; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } +#elif HAVE_DECL_F_SETLK + struct flock desc; + desc.l_type = F_WRLCK; + desc.l_whence = SEEK_SET; + desc.l_start = 0; + desc.l_len = 0; + if(::fcntl(fd, F_SETLK, &desc) != 0) + { + ::close(fd); + if(errno == EAGAIN) + { + return false; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } +#endif + + // Success + mFileDescriptor = fd; + + return true; +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::ReleaseLock() +// Purpose: Release the lock. Exceptions if the lock is not held +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void NamedLock::ReleaseLock() +{ + // Got a lock? + if(mFileDescriptor == -1) + { + THROW_EXCEPTION(CommonException, NamedLockNotHeld) + } + + // Close the file + if(::close(mFileDescriptor) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + // Mark as unlocked + mFileDescriptor = -1; +} + + + + diff --git a/lib/common/NamedLock.h b/lib/common/NamedLock.h new file mode 100644 index 00000000..534115db --- /dev/null +++ b/lib/common/NamedLock.h @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: NamedLock.h +// Purpose: A global named lock, implemented as a lock file in file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef NAMEDLOCK__H +#define NAMEDLOCK__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: NamedLock +// Purpose: A global named lock, implemented as a lock file in file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +class NamedLock +{ +public: + NamedLock(); + ~NamedLock(); +private: + // No copying allowed + NamedLock(const NamedLock &); + +public: + bool TryAndGetLock(const std::string& rFilename, int mode = 0755); + bool GotLock() {return mFileDescriptor != -1;} + void ReleaseLock(); + + +private: + int mFileDescriptor; +}; + +#endif // NAMEDLOCK__H + diff --git a/lib/common/PartialReadStream.cpp b/lib/common/PartialReadStream.cpp new file mode 100644 index 00000000..f2f79715 --- /dev/null +++ b/lib/common/PartialReadStream.cpp @@ -0,0 +1,138 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PartialReadStream.h +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "PartialReadStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::PartialReadStream(IOStream &, +// pos_type) +// Purpose: Constructor, taking another stream and the number of +// bytes to be read from it. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +PartialReadStream::PartialReadStream(IOStream &rSource, + pos_type BytesToRead) + : mrSource(rSource), + mBytesLeft(BytesToRead) +{ + ASSERT(BytesToRead > 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::~PartialReadStream() +// Purpose: Destructor. Won't absorb any unread bytes. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +PartialReadStream::~PartialReadStream() +{ + // Warn in debug mode + if(mBytesLeft != 0) + { + BOX_TRACE("PartialReadStream destroyed with " << mBytesLeft << + " bytes remaining"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +int PartialReadStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Finished? + if(mBytesLeft <= 0) + { + return 0; + } + + // Asking for more than is allowed? + if(NBytes > mBytesLeft) + { + // Adjust downwards + NBytes = mBytesLeft; + } + + // Route the request to the source + int read = mrSource.Read(pBuffer, NBytes, Timeout); + ASSERT(read <= mBytesLeft); + + // Adjust the count + mBytesLeft -= read; + + // Return the number read + return read; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::BytesLeftToRead() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type PartialReadStream::BytesLeftToRead() +{ + return mBytesLeft; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::Write(const void *, int) +// Purpose: As interface. But will exception. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void PartialReadStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, CantWriteToPartialReadStream) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::StreamDataLeft() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool PartialReadStream::StreamDataLeft() +{ + return mBytesLeft != 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::StreamClosed() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool PartialReadStream::StreamClosed() +{ + // always closed + return true; +} + diff --git a/lib/common/PartialReadStream.h b/lib/common/PartialReadStream.h new file mode 100644 index 00000000..1b46b0bd --- /dev/null +++ b/lib/common/PartialReadStream.h @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PartialReadStream.h +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#ifndef PARTIALREADSTREAM__H +#define PARTIALREADSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: PartialReadStream +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +class PartialReadStream : public IOStream +{ +public: + PartialReadStream(IOStream &rSource, pos_type BytesToRead); + ~PartialReadStream(); +private: + // no copying allowed + PartialReadStream(const IOStream &); + PartialReadStream(const PartialReadStream &); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + IOStream &mrSource; + pos_type mBytesLeft; +}; + +#endif // PARTIALREADSTREAM__H + diff --git a/lib/common/PathUtils.cpp b/lib/common/PathUtils.cpp new file mode 100644 index 00000000..924d47d2 --- /dev/null +++ b/lib/common/PathUtils.cpp @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PathUtils.cpp +// Purpose: Platform-independent path manipulation +// Created: 2007/01/17 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeFullPath(const std::string& rDir, const std::string& rFile) +// Purpose: Combine directory and file name +// Created: 2006/08/10 +// +// -------------------------------------------------------------------------- +std::string MakeFullPath(const std::string& rDir, const std::string& rEntry) +{ + std::string result(rDir); + + if (result.size() > 0 && + result[result.size()-1] != DIRECTORY_SEPARATOR_ASCHAR) + { + result += DIRECTORY_SEPARATOR; + } + + result += rEntry; + + return result; +} diff --git a/lib/common/PathUtils.h b/lib/common/PathUtils.h new file mode 100644 index 00000000..1cf2e507 --- /dev/null +++ b/lib/common/PathUtils.h @@ -0,0 +1,26 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PathUtils.h +// Purpose: Platform-independent path manipulation +// Created: 2007/01/17 +// +// -------------------------------------------------------------------------- + +#ifndef PATHUTILS_H +#define PATHUTILS_H + +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeFullPath(const std::string& rDir, const std::string& rFile) +// Purpose: Combine directory and file name +// Created: 2006/08/10 +// +// -------------------------------------------------------------------------- + +std::string MakeFullPath(const std::string& rDir, const std::string& rEntry); + +#endif // !PATHUTILS_H diff --git a/lib/common/ReadGatherStream.cpp b/lib/common/ReadGatherStream.cpp new file mode 100644 index 00000000..f50e6664 --- /dev/null +++ b/lib/common/ReadGatherStream.cpp @@ -0,0 +1,263 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadGatherStream.cpp +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "ReadGatherStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::ReadGatherStream(bool) +// Purpose: Constructor. Args says whether or not all the component streams will be deleted when this +// object is deleted. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +ReadGatherStream::ReadGatherStream(bool DeleteComponentStreamsOnDestruction) + : mDeleteComponentStreamsOnDestruction(DeleteComponentStreamsOnDestruction), + mCurrentPosition(0), + mTotalSize(0), + mCurrentBlock(0), + mPositionInCurrentBlock(0), + mSeekDoneForCurrent(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::~ReadGatherStream() +// Purpose: Destructor. Will delete all the stream objects, if required. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +ReadGatherStream::~ReadGatherStream() +{ + // Delete compoenent streams? + if(mDeleteComponentStreamsOnDestruction) + { + for(unsigned int l = 0; l < mComponents.size(); ++l) + { + delete mComponents[l]; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::AddComponent(IOStream *) +// Purpose: Add a component to this stream, returning the index +// of this component in the internal list. Use this +// with AddBlock() +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +int ReadGatherStream::AddComponent(IOStream *pStream) +{ + ASSERT(pStream != 0); + + // Just add the component to the list, returning it's index. + int index = mComponents.size(); + mComponents.push_back(pStream); + return index; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::AddBlock(int, pos_type, bool, pos_type) +// Purpose: Add a block to the list of blocks being gathered into one stream. +// Length is length of block to read from this component, Seek == true +// if a seek is required, and if true, SeekTo is the position (absolute) +// in the stream to be seeked to when this block is required. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +void ReadGatherStream::AddBlock(int Component, pos_type Length, bool Seek, pos_type SeekTo) +{ + // Check block + if(Component < 0 || Component >= (int)mComponents.size() || Length < 0 || SeekTo < 0) + { + THROW_EXCEPTION(CommonException, ReadGatherStreamAddingBadBlock); + } + + // Add to list + Block b; + b.mLength = Length; + b.mSeekTo = SeekTo; + b.mComponent = Component; + b.mSeek = Seek; + + mBlocks.push_back(b); + + // And update the total size + mTotalSize += Length; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +int ReadGatherStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + int bytesToRead = NBytes; + uint8_t *buffer = (uint8_t*)pBuffer; + + while(bytesToRead > 0) + { + // Done? + if(mCurrentBlock >= mBlocks.size()) + { + // Stop now, as have finished the last block + return NBytes - bytesToRead; + } + + // Seek? + if(mPositionInCurrentBlock == 0 && mBlocks[mCurrentBlock].mSeek && !mSeekDoneForCurrent) + { + // Do seeks in this manner so that seeks are done regardless of whether the block + // has length > 0, and it will only be done once, and at as late a stage as possible. + + mComponents[mBlocks[mCurrentBlock].mComponent]->Seek(mBlocks[mCurrentBlock].mSeekTo, IOStream::SeekType_Absolute); + + mSeekDoneForCurrent = true; + } + + // Anything in the current block? + if(mPositionInCurrentBlock < mBlocks[mCurrentBlock].mLength) + { + // Read! + pos_type s = mBlocks[mCurrentBlock].mLength - mPositionInCurrentBlock; + if(s > bytesToRead) s = bytesToRead; + + pos_type r = mComponents[mBlocks[mCurrentBlock].mComponent]->Read(buffer, s, Timeout); + + // update variables + mPositionInCurrentBlock += r; + buffer += r; + bytesToRead -= r; + mCurrentPosition += r; + + if(r != s) + { + // Stream returned less than requested. To avoid blocking when not necessary, + // return now. + return NBytes - bytesToRead; + } + } + else + { + // Move to next block + ++mCurrentBlock; + mPositionInCurrentBlock = 0; + mSeekDoneForCurrent = false; + } + } + + return NBytes - bytesToRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::GetPosition() +// Purpose: As interface +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadGatherStream::GetPosition() const +{ + return mCurrentPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::BytesLeftToRead() +// Purpose: As interface +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadGatherStream::BytesLeftToRead() +{ + return mTotalSize - mCurrentPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::Write(const void *, int) +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +void ReadGatherStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, CannotWriteToReadGatherStream); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::StreamDataLeft() +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +bool ReadGatherStream::StreamDataLeft() +{ + if(mCurrentBlock >= mBlocks.size()) + { + // Done all the blocks + return false; + } + + if(mCurrentBlock == (mBlocks.size() - 1) + && mPositionInCurrentBlock >= mBlocks[mCurrentBlock].mLength) + { + // Are on the last block, and have got all the data from it. + return false; + } + + // Otherwise, there's more data to be read + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::StreamClosed() +// Purpose: As interface. But the stream is always closed. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +bool ReadGatherStream::StreamClosed() +{ + return true; +} + + diff --git a/lib/common/ReadGatherStream.h b/lib/common/ReadGatherStream.h new file mode 100644 index 00000000..613ede3e --- /dev/null +++ b/lib/common/ReadGatherStream.h @@ -0,0 +1,67 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadGatherStream.h +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef READGATHERSTREAM_H +#define READGATHERSTREAM_H + +#include "IOStream.h" + +#include <vector> + +// -------------------------------------------------------------------------- +// +// Class +// Name: ReadGatherStream +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +class ReadGatherStream : public IOStream +{ +public: + ReadGatherStream(bool DeleteComponentStreamsOnDestruction); + ~ReadGatherStream(); +private: + ReadGatherStream(const ReadGatherStream &); + ReadGatherStream &operator=(const ReadGatherStream &); +public: + + int AddComponent(IOStream *pStream); + void AddBlock(int Component, pos_type Length, bool Seek = false, pos_type SeekTo = 0); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + virtual pos_type GetPosition() const; + +private: + bool mDeleteComponentStreamsOnDestruction; + std::vector<IOStream *> mComponents; + + typedef struct + { + pos_type mLength; + pos_type mSeekTo; + int mComponent; + bool mSeek; + } Block; + + std::vector<Block> mBlocks; + + pos_type mCurrentPosition; + pos_type mTotalSize; + unsigned int mCurrentBlock; + pos_type mPositionInCurrentBlock; + bool mSeekDoneForCurrent; +}; + + +#endif // READGATHERSTREAM_H diff --git a/lib/common/ReadLoggingStream.cpp b/lib/common/ReadLoggingStream.cpp new file mode 100644 index 00000000..54c99c95 --- /dev/null +++ b/lib/common/ReadLoggingStream.cpp @@ -0,0 +1,203 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadLoggingStream.cpp +// Purpose: Buffering wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "ReadLoggingStream.h" +#include "CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::ReadLoggingStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +ReadLoggingStream::ReadLoggingStream(IOStream& rSource, Logger& rLogger) +: mrSource(rSource), + mOffset(0), + mLength(mrSource.BytesLeftToRead()), + mTotalRead(0), + mStartTime(GetCurrentBoxTime()), + mrLogger(rLogger) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int ReadLoggingStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + int numBytesRead = mrSource.Read(pBuffer, NBytes, Timeout); + + if (numBytesRead > 0) + { + mTotalRead += numBytesRead; + mOffset += numBytesRead; + } + + if (mLength == 0) + { + mrLogger.Log(numBytesRead, mOffset); + } + else if (mTotalRead == 0) + { + mrLogger.Log(numBytesRead, mOffset, mLength); + } + else + { + box_time_t timeNow = GetCurrentBoxTime(); + box_time_t elapsed = timeNow - mStartTime; + box_time_t finish = (elapsed * mLength) / mTotalRead; + // box_time_t remain = finish - elapsed; + mrLogger.Log(numBytesRead, mOffset, mLength, elapsed, finish); + } + + return numBytesRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadLoggingStream::BytesLeftToRead() +{ + return mLength - mOffset; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadLoggingStream::GetPosition() const +{ + return mOffset; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + mrSource.Seek(Offset, SeekType); + + switch (SeekType) + { + case SeekType_Absolute: + { + // just go there + mOffset = Offset; + } + break; + + case SeekType_Relative: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to subtract that amount from the seek + // to seek forward that much less, putting the + // real pointer in the right place. + mOffset += Offset; + } + break; + + case SeekType_End: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to add that amount to the seek + // to seek backwards that much more, putting the + // real pointer in the right place. + mOffset = mLength - Offset; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ReadLoggingStream::StreamDataLeft() +{ + return mrSource.StreamDataLeft(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ReadLoggingStream::StreamClosed() +{ + return mrSource.StreamClosed(); +} + diff --git a/lib/common/ReadLoggingStream.h b/lib/common/ReadLoggingStream.h new file mode 100644 index 00000000..b23b542c --- /dev/null +++ b/lib/common/ReadLoggingStream.h @@ -0,0 +1,58 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadLoggingStream.h +// Purpose: Wrapper around IOStreams that logs read progress +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#ifndef READLOGGINGSTREAM__H +#define READLOGGINGSTREAM__H + +#include "IOStream.h" +#include "BoxTime.h" + +class ReadLoggingStream : public IOStream +{ +public: + class Logger + { + public: + virtual ~Logger() { } + virtual void Log(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, + box_time_t finish) = 0; + virtual void Log(int64_t readSize, int64_t offset, + int64_t length) = 0; + virtual void Log(int64_t readSize, int64_t offset) = 0; + }; + +private: + IOStream& mrSource; + IOStream::pos_type mOffset, mLength, mTotalRead; + box_time_t mStartTime; + Logger& mrLogger; + +public: + ReadLoggingStream(IOStream& rSource, Logger& rLogger); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + ReadLoggingStream(const ReadLoggingStream &rToCopy) + : mrSource(rToCopy.mrSource), mrLogger(rToCopy.mrLogger) + { /* do not call */ } +}; + +#endif // READLOGGINGSTREAM__H + + diff --git a/lib/common/SelfFlushingStream.h b/lib/common/SelfFlushingStream.h new file mode 100644 index 00000000..36e9a4d3 --- /dev/null +++ b/lib/common/SelfFlushingStream.h @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SelfFlushingStream.h +// Purpose: A stream wrapper that always flushes the underlying +// stream, to ensure protocol safety. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef SELFFLUSHINGSTREAM__H +#define SELFFLUSHINGSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: SelfFlushingStream +// Purpose: A stream wrapper that always flushes the underlying +// stream, to ensure protocol safety. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- +class SelfFlushingStream : public IOStream +{ +public: + SelfFlushingStream(IOStream &rSource) + : mrSource(rSource) { } + + SelfFlushingStream(const SelfFlushingStream &rToCopy) + : mrSource(rToCopy.mrSource) { } + + ~SelfFlushingStream() + { + Flush(); + } + +private: + // no copying from IOStream allowed + SelfFlushingStream(const IOStream& rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) + { + return mrSource.Read(pBuffer, NBytes, Timeout); + } + virtual pos_type BytesLeftToRead() + { + return mrSource.BytesLeftToRead(); + } + virtual void Write(const void *pBuffer, int NBytes) + { + mrSource.Write(pBuffer, NBytes); + } + virtual bool StreamDataLeft() + { + return mrSource.StreamDataLeft(); + } + virtual bool StreamClosed() + { + return mrSource.StreamClosed(); + } + +private: + IOStream &mrSource; +}; + +#endif // SELFFLUSHINGSTREAM__H + diff --git a/lib/common/StreamableMemBlock.cpp b/lib/common/StreamableMemBlock.cpp new file mode 100644 index 00000000..cf431022 --- /dev/null +++ b/lib/common/StreamableMemBlock.cpp @@ -0,0 +1,364 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StreamableMemBlock.cpp +// Purpose: Memory blocks which can be loaded and saved from streams +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <new> +#include <cstdlib> +#include <string.h> + +#include "StreamableMemBlock.h" +#include "IOStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock() +// Purpose: Constructor, making empty block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock() + : mpBuffer(0), + mSize(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(void *, int) +// Purpose: Create block, copying data from another bit of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(void *pBuffer, int Size) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(Size); + ::memcpy(mpBuffer, pBuffer, Size); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(int) +// Purpose: Create block, initialising it to all zeros +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(int Size) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(Size); + ::memset(mpBuffer, 0, Size); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &) +// Purpose: Copy constructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &rToCopy) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(rToCopy.mSize); + ::memcpy(mpBuffer, rToCopy.mpBuffer, mSize); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(void *, int) +// Purpose: Set the contents of the block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(void *pBuffer, int Size) +{ + FreeBlock(); + AllocateBlock(Size); + ::memcpy(mpBuffer, pBuffer, Size); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(IOStream &) +// Purpose: Set from stream. Stream must support BytesLeftToRead() +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(IOStream &rStream, int Timeout) +{ + // Get size + IOStream::pos_type size = rStream.BytesLeftToRead(); + if(size == IOStream::SizeOfStreamUnknown) + { + THROW_EXCEPTION(CommonException, StreamDoesntHaveRequiredProperty) + } + + // Allocate a new block (this way to be exception safe) + char *pblock = (char*)malloc(size); + if(pblock == 0) + { + throw std::bad_alloc(); + } + + try + { + // Read in + if(!rStream.ReadFullBuffer(pblock, size, 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + // Free the block ready for replacement + FreeBlock(); + } + catch(...) + { + ::free(pblock); + throw; + } + + // store... + ASSERT(mpBuffer == 0); + mpBuffer = pblock; + mSize = size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(const StreamableMemBlock &) +// Purpose: Set from other block. +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(const StreamableMemBlock &rBlock) +{ + Set(rBlock.mpBuffer, rBlock.mSize); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::~StreamableMemBlock() +// Purpose: Destructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::~StreamableMemBlock() +{ + FreeBlock(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::FreeBlock() +// Purpose: Protected. Frees block of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::FreeBlock() +{ + if(mpBuffer != 0) + { + ::free(mpBuffer); + } + mpBuffer = 0; + mSize = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::AllocateBlock(int) +// Purpose: Protected. Allocate the block of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::AllocateBlock(int Size) +{ + ASSERT(mpBuffer == 0); + if(Size > 0) + { + mpBuffer = ::malloc(Size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } + } + mSize = Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::ResizeBlock(int) +// Purpose: Protected. Resizes the allocated block. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::ResizeBlock(int Size) +{ + ASSERT(Size > 0); + if(Size > 0) + { + void *pnewBuffer = ::realloc(mpBuffer, Size); + if(pnewBuffer == 0) + { + throw std::bad_alloc(); + } + mpBuffer = pnewBuffer; + } + mSize = Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::ReadFromStream(IOStream &, int) +// Purpose: Read the block in from a stream +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::ReadFromStream(IOStream &rStream, int Timeout) +{ + // Get the size of the block + int32_t size_s; + if(!rStream.ReadFullBuffer(&size_s, sizeof(size_s), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + int size = ntohl(size_s); + + + // Allocate a new block (this way to be exception safe) + char *pblock = (char*)malloc(size); + if(pblock == 0) + { + throw std::bad_alloc(); + } + + try + { + // Read in + if(!rStream.ReadFullBuffer(pblock, size, 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + // Free the block ready for replacement + FreeBlock(); + } + catch(...) + { + ::free(pblock); + throw; + } + + // store... + ASSERT(mpBuffer == 0); + mpBuffer = pblock; + mSize = size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::WriteToStream(IOStream &) +// Purpose: Write the block to a stream +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::WriteToStream(IOStream &rStream) const +{ + int32_t sizenbo = htonl(mSize); + // Size + rStream.Write(&sizenbo, sizeof(sizenbo)); + // Buffer + if(mSize > 0) + { + rStream.Write(mpBuffer, mSize); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::WriteEmptyBlockToStream(IOStream &) +// Purpose: Writes an empty block to a stream. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::WriteEmptyBlockToStream(IOStream &rStream) +{ + int32_t sizenbo = htonl(0); + rStream.Write(&sizenbo, sizeof(sizenbo)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::GetBuffer() +// Purpose: Get pointer to buffer +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void *StreamableMemBlock::GetBuffer() const +{ + if(mSize == 0) + { + // Return something which isn't a null pointer + static const int validptr = 0; + return (void*)&validptr; + } + + // return the buffer + ASSERT(mpBuffer != 0); + return mpBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::operator==(const StreamableMemBlock &) +// Purpose: Test for equality of memory blocks +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +bool StreamableMemBlock::operator==(const StreamableMemBlock &rCompare) const +{ + if(mSize != rCompare.mSize) return false; + if(mSize == 0 && rCompare.mSize == 0) return true; // without memory comparison! + return ::memcmp(mpBuffer, rCompare.mpBuffer, mSize) == 0; +} + + diff --git a/lib/common/StreamableMemBlock.h b/lib/common/StreamableMemBlock.h new file mode 100644 index 00000000..250c0aea --- /dev/null +++ b/lib/common/StreamableMemBlock.h @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StreamableMemBlock.h +// Purpose: Memory blocks which can be loaded and saved from streams +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#ifndef STREAMABLEMEMBLOCK__H +#define STREAMABLEMEMBLOCK__H + +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: StreamableMemBlock +// Purpose: Memory blocks which can be loaded and saved from streams +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +class StreamableMemBlock +{ +public: + StreamableMemBlock(); + StreamableMemBlock(int Size); + StreamableMemBlock(void *pBuffer, int Size); + StreamableMemBlock(const StreamableMemBlock &rToCopy); + ~StreamableMemBlock(); + + void Set(const StreamableMemBlock &rBlock); + void Set(void *pBuffer, int Size); + void Set(IOStream &rStream, int Timeout); + StreamableMemBlock &operator=(const StreamableMemBlock &rBlock) + { + Set(rBlock); + return *this; + } + + void ReadFromStream(IOStream &rStream, int Timeout); + void WriteToStream(IOStream &rStream) const; + + static void WriteEmptyBlockToStream(IOStream &rStream); + + void *GetBuffer() const; + + // Size of block + int GetSize() const {return mSize;} + + // Buffer empty? + bool IsEmpty() const {return mSize == 0;} + + // Clear the contents of the block + void Clear() {FreeBlock();} + + bool operator==(const StreamableMemBlock &rCompare) const; + + void ResizeBlock(int Size); + +protected: // be careful with these! + void AllocateBlock(int Size); + void FreeBlock(); + +private: + void *mpBuffer; + int mSize; +}; + +#endif // STREAMABLEMEMBLOCK__H + diff --git a/lib/common/TemporaryDirectory.h b/lib/common/TemporaryDirectory.h new file mode 100644 index 00000000..9d52ecd9 --- /dev/null +++ b/lib/common/TemporaryDirectory.h @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: TemporaryDirectory.h +// Purpose: Location of temporary directory +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- + +#ifndef TEMPORARYDIRECTORY__H +#define TEMPORARYDIRECTORY__H + +#include <string> + +#ifdef WIN32 + #include <windows.h> +#endif + +// Prefix name with Box to avoid clashing with OS API names +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 +} + +#endif // TEMPORARYDIRECTORY__H diff --git a/lib/common/Test.cpp b/lib/common/Test.cpp new file mode 100644 index 00000000..56638058 --- /dev/null +++ b/lib/common/Test.cpp @@ -0,0 +1,486 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Test.cpp +// Purpose: Useful stuff for tests +// Created: 2008/04/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "Test.h" + +bool TestFileExists(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0; +} + +bool TestFileNotEmpty(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0 && + st.st_size > 0; +} + +bool TestDirExists(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == S_IFDIR; +} + +// -1 if doesn't exist +int TestGetFileSize(const char *Filename) +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(Filename, &st) == 0) + { + return st.st_size; + } + return -1; +} + +std::string ConvertPaths(const std::string& rOriginal) +{ +#ifdef WIN32 + // convert UNIX paths to native + + std::string converted; + for (size_t i = 0; i < rOriginal.size(); i++) + { + if (rOriginal[i] == '/') + { + converted += '\\'; + } + else + { + converted += rOriginal[i]; + } + } + return converted; + +#else // !WIN32 + return rOriginal; +#endif +} + +int RunCommand(const std::string& rCommandLine) +{ + return ::system(ConvertPaths(rCommandLine).c_str()); +} + +#ifdef WIN32 +#include <windows.h> +#endif + +bool ServerIsAlive(int pid) +{ + #ifdef WIN32 + + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, + false, pid); + if (hProcess == NULL) + { + if (GetLastError() != ERROR_INVALID_PARAMETER) + { + BOX_ERROR("Failed to open process " << pid << + ": " << + GetErrorMessage(GetLastError())); + } + return false; + } + + DWORD exitCode; + BOOL result = GetExitCodeProcess(hProcess, &exitCode); + CloseHandle(hProcess); + + if (result == 0) + { + BOX_ERROR("Failed to get exit code for process " << + pid << ": " << + GetErrorMessage(GetLastError())) + return false; + } + + if (exitCode == STILL_ACTIVE) + { + return true; + } + + return false; + + #else // !WIN32 + + if(pid == 0) return false; + return ::kill(pid, 0) != -1; + + #endif // WIN32 +} + +int ReadPidFile(const char *pidFile) +{ + if(!TestFileNotEmpty(pidFile)) + { + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file " + "(perhaps one was already running?)"); + return -1; + } + + int pid = -1; + + FILE *f = fopen(pidFile, "r"); + if(f == NULL || fscanf(f, "%d", &pid) != 1) + { + TEST_FAIL_WITH_MESSAGE("Couldn't read PID file"); + return -1; + } + fclose(f); + + return pid; +} + +int LaunchServer(const std::string& rCommandLine, const char *pidFile) +{ + ::fprintf(stdout, "Starting server: %s\n", rCommandLine.c_str()); + +#ifdef WIN32 + + PROCESS_INFORMATION procInfo; + + STARTUPINFO startInfo; + startInfo.cb = sizeof(startInfo); + startInfo.lpReserved = NULL; + startInfo.lpDesktop = NULL; + startInfo.lpTitle = NULL; + startInfo.dwFlags = 0; + startInfo.cbReserved2 = 0; + startInfo.lpReserved2 = NULL; + + std::string cmd = ConvertPaths(rCommandLine); + CHAR* tempCmd = strdup(cmd.c_str()); + + DWORD result = CreateProcess + ( + NULL, // lpApplicationName, naughty! + tempCmd, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + false, // bInheritHandles + 0, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &startInfo, // lpStartupInfo + &procInfo // lpProcessInformation + ); + + free(tempCmd); + + if (result == 0) + { + DWORD err = GetLastError(); + printf("Launch failed: %s: error %d\n", rCommandLine.c_str(), + (int)err); + TEST_FAIL_WITH_MESSAGE("Couldn't start server"); + return -1; + } + + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + + return WaitForServerStartup(pidFile, (int)procInfo.dwProcessId); + +#else // !WIN32 + + if(RunCommand(rCommandLine) != 0) + { + TEST_FAIL_WITH_MESSAGE("Couldn't start server"); + return -1; + } + + return WaitForServerStartup(pidFile, 0); + +#endif // WIN32 +} + +int WaitForServerStartup(const char *pidFile, int pidIfKnown) +{ + #ifdef WIN32 + if (pidFile == NULL) + { + return pidIfKnown; + } + #else + // on other platforms there is no other way to get + // the PID, so a NULL pidFile doesn't make sense. + ASSERT(pidFile != NULL); + #endif + + // time for it to start up + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_TRACE("Waiting for server to start"); + } + else + { + ::fprintf(stdout, "Waiting for server to start: "); + } + + for (int i = 0; i < 15; i++) + { + if (TestFileNotEmpty(pidFile)) + { + break; + } + + if (pidIfKnown && !ServerIsAlive(pidIfKnown)) + { + break; + } + + if (Logging::GetGlobalLevel() < Log::TRACE) + { + ::fprintf(stdout, "."); + ::fflush(stdout); + } + + ::sleep(1); + } + + // on Win32 we can check whether the process is alive + // without even checking the PID file + + if (pidIfKnown && !ServerIsAlive(pidIfKnown)) + { + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_ERROR("server died!"); + } + else + { + ::fprintf(stdout, " server died!\n"); + } + + TEST_FAIL_WITH_MESSAGE("Server died!"); + return -1; + } + + if (!TestFileNotEmpty(pidFile)) + { + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_ERROR("timed out!"); + } + else + { + ::fprintf(stdout, " timed out!\n"); + } + + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file"); + return -1; + } + + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_TRACE("Server started"); + } + else + { + ::fprintf(stdout, " done.\n"); + } + + // wait a second for the pid to be written to the file + ::sleep(1); + + // read pid file + int pid = ReadPidFile(pidFile); + + // On Win32 we can check whether the PID in the pidFile matches + // the one returned by the system, which it always should. + + if (pidIfKnown && pid != pidIfKnown) + { + BOX_ERROR("Server wrote wrong pid to file (" << pidFile << + "): expected " << pidIfKnown << " but found " << + pid); + TEST_FAIL_WITH_MESSAGE("Server wrote wrong pid to file"); + return -1; + } + + return pid; +} + +void TestRemoteProcessMemLeaksFunc(const char *filename, + const char* file, int line) +{ +#ifdef BOX_MEMORY_LEAK_TESTING + // Does the file exist? + if(!TestFileExists(filename)) + { + if (failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } + ++failures; + printf("FAILURE: MemLeak report not available (file %s) " + "at %s:%d\n", filename, file, line); + } + else + { + // Is it empty? + if(TestGetFileSize(filename) > 0) + { + if (failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } + ++failures; + printf("FAILURE: Memory leaks found in other process " + "(file %s) at %s:%d\n==========\n", + filename, file, line); + FILE *f = fopen(filename, "r"); + char linebuf[512]; + while(::fgets(linebuf, sizeof(linebuf), f) != 0) + { + printf("%s", linebuf); + } + fclose(f); + printf("==========\n"); + } + + // Delete it + ::unlink(filename); + } +#endif +} + +void force_sync() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "force-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +void wait_for_sync_start() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +void wait_for_sync_end() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-end") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +void sync_and_wait() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "sync-and-wait") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +void terminate_bbackupd(int pid) +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "terminate") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + + for (int i = 0; i < 20; i++) + { + if (!ServerIsAlive(pid)) break; + fprintf(stdout, "."); + fflush(stdout); + sleep(1); + } + + TEST_THAT(!ServerIsAlive(pid)); + TestRemoteProcessMemLeaks("bbackupd.memleaks"); +} + + +// Wait a given number of seconds for something to complete +void wait_for_operation(int seconds, const char* message) +{ + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_TRACE("Waiting " << seconds << " seconds for " << message); + } + else + { + printf("Waiting for %s: ", message); + fflush(stdout); + } + + for(int l = 0; l < seconds; ++l) + { + sleep(1); + if (Logging::GetGlobalLevel() < Log::TRACE) + { + printf("."); + fflush(stdout); + } + } + + if (Logging::GetGlobalLevel() >= Log::TRACE) + { + BOX_TRACE("Finished waiting for " << message); + } + else + { + printf(" done.\n"); + fflush(stdout); + } +} + +void safe_sleep(int seconds) +{ + BOX_TRACE("sleeping for " << seconds << " seconds"); + +#ifdef WIN32 + Sleep(seconds * 1000); +#else + struct timespec ts; + memset(&ts, 0, sizeof(ts)); + ts.tv_sec = seconds; + ts.tv_nsec = 0; + 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 = (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 / 1000000000) << " secs remaining, " + "sleeping again"); + } +#endif +} + diff --git a/lib/common/Test.h b/lib/common/Test.h new file mode 100644 index 00000000..08ba4542 --- /dev/null +++ b/lib/common/Test.h @@ -0,0 +1,167 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Test.h +// Purpose: Useful stuff for tests +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#ifndef TEST__H +#define TEST__H + +#include <cstring> + +#ifdef WIN32 +#define BBACKUPCTL "..\\..\\bin\\bbackupctl\\bbackupctl.exe" +#define BBACKUPD "..\\..\\bin\\bbackupd\\bbackupd.exe" +#define BBSTORED "..\\..\\bin\\bbstored\\bbstored.exe" +#define BBACKUPQUERY "..\\..\\bin\\bbackupquery\\bbackupquery.exe" +#define BBSTOREACCOUNTS "..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts.exe" +#define TEST_RETURN(actual, expected) TEST_EQUAL(expected, actual); +#else +#define BBACKUPCTL "../../bin/bbackupctl/bbackupctl" +#define BBACKUPD "../../bin/bbackupd/bbackupd" +#define BBSTORED "../../bin/bbstored/bbstored" +#define BBACKUPQUERY "../../bin/bbackupquery/bbackupquery" +#define BBSTOREACCOUNTS "../../bin/bbstoreaccounts/bbstoreaccounts" +#define TEST_RETURN(actual, expected) TEST_EQUAL((expected << 8), actual); +#endif + +extern int failures; +extern int first_fail_line; +extern std::string first_fail_file; +extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args; + +#define TEST_FAIL_WITH_MESSAGE(msg) \ +{ \ + if (failures == 0) \ + { \ + first_fail_file = __FILE__; \ + first_fail_line = __LINE__; \ + } \ + failures++; \ + BOX_ERROR("**** TEST FAILURE: " << msg << " at " << __FILE__ << \ + ":" << __LINE__); \ +} + +#define TEST_ABORT_WITH_MESSAGE(msg) {TEST_FAIL_WITH_MESSAGE(msg); return 1;} + +#define TEST_THAT(condition) {if(!(condition)) TEST_FAIL_WITH_MESSAGE("Condition [" #condition "] failed")} +#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) +#define TEST_CHECK_THROWS(statement, excepttype, subtype) \ + { \ + bool didthrow = false; \ + HideExceptionMessageGuard hide; \ + 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 ")") \ + } \ + } + +// utility macro for comparing two strings in a line +#define TEST_EQUAL(_expected, _found) \ +{ \ + std::ostringstream _oss1; \ + _oss1 << _expected; \ + std::string _exp_str = _oss1.str(); \ + \ + std::ostringstream _oss2; \ + _oss2 << _found; \ + std::string _found_str = _oss2.str(); \ + \ + if(_exp_str != _found_str) \ + { \ + BOX_WARNING("Expected <" << _exp_str << "> but found <" << \ + _found_str << ">"); \ + \ + std::ostringstream _oss3; \ + _oss3 << #_found << " != " << #_expected; \ + \ + TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ + } \ +} + +// utility macro for comparing two strings in a line +#define TEST_EQUAL_LINE(_expected, _found, _line) \ +{ \ + std::ostringstream _oss1; \ + _oss1 << _expected; \ + std::string _exp_str = _oss1.str(); \ + \ + std::ostringstream _oss2; \ + _oss2 << _found; \ + std::string _found_str = _oss2.str(); \ + \ + if(_exp_str != _found_str) \ + { \ + std::ostringstream _ossl; \ + _ossl << _line; \ + std::string _line_str = _ossl.str(); \ + printf("Expected <%s> but found <%s> in <%s>\n", \ + _exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \ + \ + std::ostringstream _oss3; \ + _oss3 << #_found << " != " << #_expected << " in " << _line; \ + \ + TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ + } \ +} + + +// utility macro for testing a line +#define TEST_LINE(_condition, _line) \ + TEST_THAT(_condition); \ + if (!(_condition)) \ + { \ + printf("Test failed on <%s>\n", _line.c_str()); \ + } + +bool TestFileExists(const char *Filename); +bool TestDirExists(const char *Filename); + +// -1 if doesn't exist +int TestGetFileSize(const char *Filename); +std::string ConvertPaths(const std::string& rOriginal); +int RunCommand(const std::string& rCommandLine); +bool ServerIsAlive(int pid); +int ReadPidFile(const char *pidFile); +int LaunchServer(const std::string& rCommandLine, const char *pidFile); +int WaitForServerStartup(const char *pidFile, int pidIfKnown); + +#define TestRemoteProcessMemLeaks(filename) \ + TestRemoteProcessMemLeaksFunc(filename, __FILE__, __LINE__) + +void TestRemoteProcessMemLeaksFunc(const char *filename, + const char* file, int line); + +void force_sync(); +void wait_for_sync_start(); +void wait_for_sync_end(); +void sync_and_wait(); +void terminate_bbackupd(int pid); + +// Wait a given number of seconds for something to complete +void wait_for_operation(int seconds, const char* message); +void safe_sleep(int seconds); + +#endif // TEST__H diff --git a/lib/common/Timer.cpp b/lib/common/Timer.cpp new file mode 100644 index 00000000..137ad45f --- /dev/null +++ b/lib/common/Timer.cpp @@ -0,0 +1,626 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Timer.cpp +// Purpose: Generic timers which execute arbitrary code when +// they expire. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- + +#ifdef WIN32 + #define _WIN32_WINNT 0x0500 +#endif + +#include "Box.h" + +#include <signal.h> +#include <cstring> + +#include "Timer.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +std::vector<Timer*>* Timers::spTimers = NULL; +bool Timers::sRescheduleNeeded = false; + +#define TIMER_ID "timer " << mName << " (" << this << ") " +#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ") " + +typedef void (*sighandler_t)(int); + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Init() +// Purpose: Initialise timers, prepare signal handler +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Init() +{ + ASSERT(!spTimers); + + #if defined WIN32 && ! defined PLATFORM_CYGWIN + // no init needed + #else + struct sigaction newact, oldact; + newact.sa_handler = Timers::SignalHandler; + newact.sa_flags = SA_RESTART; + sigemptyset(&newact.sa_mask); + if (::sigaction(SIGALRM, &newact, &oldact) != 0) + { + BOX_ERROR("Failed to install signal handler"); + THROW_EXCEPTION(CommonException, Internal); + } + ASSERT(oldact.sa_handler == 0); + #endif // WIN32 && !PLATFORM_CYGWIN + + spTimers = new std::vector<Timer*>; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Cleanup() +// Purpose: Clean up timers, stop signal handler +// Created: 6/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Cleanup() +{ + ASSERT(spTimers); + if (!spTimers) + { + BOX_ERROR("Tried to clean up timers when not initialised!"); + return; + } + + #if defined WIN32 && ! defined PLATFORM_CYGWIN + // no cleanup needed + #else + struct itimerval timeout; + memset(&timeout, 0, sizeof(timeout)); + + int result = ::setitimer(ITIMER_REAL, &timeout, NULL); + ASSERT(result == 0); + + struct sigaction newact, oldact; + newact.sa_handler = SIG_DFL; + newact.sa_flags = SA_RESTART; + sigemptyset(&(newact.sa_mask)); + if (::sigaction(SIGALRM, &newact, &oldact) != 0) + { + BOX_ERROR("Failed to remove signal handler"); + THROW_EXCEPTION(CommonException, Internal); + } + ASSERT(oldact.sa_handler == Timers::SignalHandler); + #endif // WIN32 && !PLATFORM_CYGWIN + + spTimers->clear(); + delete spTimers; + spTimers = NULL; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Add(Timer&) +// Purpose: Add a new timer to the set, and reschedule next wakeup +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Add(Timer& rTimer) +{ + ASSERT(spTimers); + ASSERT(&rTimer); + spTimers->push_back(&rTimer); + Reschedule(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Remove(Timer&) +// Purpose: Removes the timer from the set (preventing it from +// being called) and reschedule next wakeup +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Remove(Timer& rTimer) +{ + ASSERT(spTimers); + ASSERT(&rTimer); + + bool restart = true; + while (restart) + { + restart = false; + + for (std::vector<Timer*>::iterator i = spTimers->begin(); + i != spTimers->end(); i++) + { + if (&rTimer == *i) + { + spTimers->erase(i); + restart = true; + break; + } + } + } + + Reschedule(); +} + +void Timers::RequestReschedule() +{ + sRescheduleNeeded = true; +} + +void Timers::RescheduleIfNeeded() +{ + if (sRescheduleNeeded) + { + Reschedule(); + } +} + +#define FORMAT_MICROSECONDS(t) \ + (int)(t / 1000000) << "." << \ + (int)(t % 1000000) << " seconds" + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Reschedule() +// Purpose: Recalculate when the next wakeup is due +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Reschedule() +{ + ASSERT(spTimers); + if (spTimers == NULL) + { + THROW_EXCEPTION(CommonException, Internal) + } + + #ifndef WIN32 + struct sigaction oldact; + if (::sigaction(SIGALRM, NULL, &oldact) != 0) + { + BOX_ERROR("Failed to check signal handler"); + THROW_EXCEPTION(CommonException, Internal) + } + + ASSERT(oldact.sa_handler == Timers::SignalHandler); + + if (oldact.sa_handler != Timers::SignalHandler) + { + BOX_ERROR("Signal handler was " << + (void *)oldact.sa_handler << + ", expected " << + (void *)Timers::SignalHandler); + THROW_EXCEPTION(CommonException, Internal) + } + #endif + + // Clear the reschedule-needed flag to false before we start. + // If a timer event occurs while we are scheduling, then we + // may or may not need to reschedule again, but this way + // we will do it anyway. + sRescheduleNeeded = false; + +#ifdef WIN32 + // win32 timers need no management +#else + box_time_t timeNow = GetCurrentBoxTime(); + + // scan for, trigger and remove expired timers. Removal requires + // us to restart the scan each time, due to std::vector semantics. + bool restart = true; + while (restart) + { + restart = false; + + for (std::vector<Timer*>::iterator i = spTimers->begin(); + i != spTimers->end(); i++) + { + Timer& rTimer = **i; + int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; + + if (timeToExpiry <= 0) + { + /* + BOX_TRACE("timer " << *i << " has expired, " + "triggering it"); + */ + BOX_TRACE(TIMER_ID_OF(**i) "has expired, " + "triggering " << + FORMAT_MICROSECONDS(-timeToExpiry) << + " late"); + rTimer.OnExpire(); + spTimers->erase(i); + restart = true; + break; + } + else + { + /* + BOX_TRACE("timer " << *i << " has not " + "expired, triggering in " << + FORMAT_MICROSECONDS(timeToExpiry) << + " seconds"); + */ + } + } + } + + // Now the only remaining timers should all be in the future. + // Scan to find the next one to fire (earliest deadline). + + int64_t timeToNextEvent = 0; + std::string nameOfNextEvent; + + for (std::vector<Timer*>::iterator i = spTimers->begin(); + i != spTimers->end(); i++) + { + Timer& rTimer = **i; + int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; + + ASSERT(timeToExpiry > 0) + if (timeToExpiry <= 0) + { + timeToExpiry = 1; + } + + if (timeToNextEvent == 0 || timeToNextEvent > timeToExpiry) + { + timeToNextEvent = timeToExpiry; + nameOfNextEvent = rTimer.GetName(); + } + } + + ASSERT(timeToNextEvent >= 0); + + if (timeToNextEvent == 0) + { + BOX_TRACE("timer: no more events, going to sleep."); + } + else + { + BOX_TRACE("timer: next event: " << nameOfNextEvent << + " expires in " << FORMAT_MICROSECONDS(timeToNextEvent)); + } + + struct itimerval timeout; + memset(&timeout, 0, sizeof(timeout)); + + timeout.it_value.tv_sec = BoxTimeToSeconds(timeToNextEvent); + timeout.it_value.tv_usec = (int) + (BoxTimeToMicroSeconds(timeToNextEvent) + % MICRO_SEC_IN_SEC); + + if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) + { + BOX_ERROR("Failed to initialise system timer\n"); + THROW_EXCEPTION(CommonException, Internal) + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::SignalHandler(unused) +// Purpose: Called as signal handler. Nothing is safe in a signal +// handler, not even traversing the list of timers, so +// just request a reschedule in future, which will do +// that for us, and trigger any expired timers at that +// time. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::SignalHandler(int unused) +{ + // ASSERT(spTimers); + Timers::RequestReschedule(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(size_t timeoutSecs, +// const std::string& rName) +// Purpose: Standard timer constructor, takes a timeout in +// seconds from now, and an optional name for +// logging purposes. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::Timer(size_t timeoutSecs, const std::string& rName) +: mExpires(GetCurrentBoxTime() + SecondsToBoxTime(timeoutSecs)), + mExpired(false), + mName(rName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif +{ + #ifndef BOX_RELEASE_BUILD + if (timeoutSecs == 0) + { + BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << + " secs, will not fire"); + } + else + { + BOX_TRACE(TIMER_ID "initialised for " << timeoutSecs << + " secs, to fire at " << FormatTime(mExpires, false, true)); + } + #endif + + if (timeoutSecs == 0) + { + mExpires = 0; + } + else + { + Timers::Add(*this); + Start(timeoutSecs * MICRO_SEC_IN_SEC_LL); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Start() +// 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. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start() +{ +#ifdef WIN32 + box_time_t timeNow = GetCurrentBoxTime(); + int64_t timeToExpiry = mExpires - timeNow; + + if (timeToExpiry <= 0) + { + BOX_WARNING(TIMER_ID << "fudging expiry from -" << + FORMAT_MICROSECONDS(-timeToExpiry)) + timeToExpiry = 1; + } + + Start(timeToExpiry); +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// 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. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start(int64_t delayInMicros) +{ +#ifdef WIN32 + // only call me once! + ASSERT(mTimerHandle == INVALID_HANDLE_VALUE); + + int64_t delayInMillis = delayInMicros / 1000; + + // 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. + delayInMillis -= 20; + + // Set a system timer to call our timer routine + if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine, + (PVOID)this, delayInMillis, 0, WT_EXECUTEINTIMERTHREAD) + == FALSE) + { + BOX_ERROR(TIMER_ID "failed to create timer: " << + GetErrorMessage(GetLastError())); + mTimerHandle = INVALID_HANDLE_VALUE; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Stop() +// Purpose: This internal function deletes the associated OS +// TimerQueue timer on Windows, and on Unixes does +// nothing. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Stop() +{ +#ifdef WIN32 + if (mTimerHandle != INVALID_HANDLE_VALUE) + { + if (DeleteTimerQueueTimer(NULL, mTimerHandle, + INVALID_HANDLE_VALUE) == FALSE) + { + BOX_ERROR(TIMER_ID "failed to delete timer: " << + GetErrorMessage(GetLastError())); + } + mTimerHandle = INVALID_HANDLE_VALUE; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::~Timer() +// Purpose: Destructor for Timer objects. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::~Timer() +{ + #ifndef BOX_RELEASE_BUILD + BOX_TRACE(TIMER_ID "destroyed"); + #endif + + Timers::Remove(*this); + Stop(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(Timer& rToCopy) +// Purpose: Copy constructor for Timer objects. Creates a new +// timer that will trigger at the same time as the +// original. The original will usually be discarded. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::Timer(const Timer& rToCopy) +: mExpires(rToCopy.mExpires), + mExpired(rToCopy.mExpired), + mName(rToCopy.mName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif +{ + #ifndef BOX_RELEASE_BUILD + if (mExpired) + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "already expired, will not fire"); + } + else if (mExpires == 0) + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "no expiry, will not fire"); + } + else + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "to fire at " << + (int)(mExpires / 1000000) << "." << + (int)(mExpires % 1000000)); + } + #endif + + if (!mExpired && mExpires != 0) + { + Timers::Add(*this); + Start(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::operator=(const Timer& rToCopy) +// Purpose: Assignment operator for Timer objects. Works +// exactly the same as the copy constructor, except +// that if the receiving timer is already running, +// it is stopped first. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer& Timer::operator=(const Timer& rToCopy) +{ + #ifndef BOX_RELEASE_BUILD + if (rToCopy.mExpired) + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "already expired, will not fire"); + } + else if (rToCopy.mExpires == 0) + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "no expiry, will not fire"); + } + else + { + BOX_TRACE(TIMER_ID "initialised from timer " << &rToCopy << ", " + "to fire at " << + (int)(rToCopy.mExpires / 1000000) << "." << + (int)(rToCopy.mExpires % 1000000)); + } + #endif + + Timers::Remove(*this); + Stop(); + + mExpires = rToCopy.mExpires; + mExpired = rToCopy.mExpired; + mName = rToCopy.mName; + + if (!mExpired && mExpires != 0) + { + Timers::Add(*this); + Start(); + } + + return *this; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::OnExpire() +// Purpose: Method called by Timers::Reschedule (on Unixes) +// on next poll after timer expires, or from +// Timer::TimerRoutine (on Windows) from a separate +// thread managed by the OS. Marks the timer as +// expired for future reference. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::OnExpire() +{ + #ifndef BOX_RELEASE_BUILD + BOX_TRACE(TIMER_ID "fired"); + #endif + + mExpired = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::TimerRoutine(PVOID lpParam, +// BOOLEAN TimerOrWaitFired) +// Purpose: Static method called by the Windows OS when a +// TimerQueue timer expires. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +#ifdef WIN32 +VOID CALLBACK Timer::TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired) +{ + Timer* pTimer = (Timer*)lpParam; + pTimer->OnExpire(); + // is it safe to write to write debug output from a timer? + // e.g. to write to the Event Log? +} +#endif diff --git a/lib/common/Timer.h b/lib/common/Timer.h new file mode 100644 index 00000000..42b2e00f --- /dev/null +++ b/lib/common/Timer.h @@ -0,0 +1,89 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Timer.h +// Purpose: Generic timers which execute arbitrary code when +// they expire. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- + +#ifndef TIMER__H +#define TIMER__H + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#include <vector> + +#include "BoxTime.h" + +#include "MemLeakFindOn.h" + +class Timer; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Timers +// Purpose: Static class to manage all timers and arrange +// efficient delivery of wakeup signals +// Created: 19/3/04 +// +// -------------------------------------------------------------------------- +class Timers +{ + private: + static std::vector<Timer*>* spTimers; + static void Reschedule(); + + static bool sRescheduleNeeded; + static void SignalHandler(int iUnused); + + public: + static void Init(); + static void Cleanup(); + static void Add (Timer& rTimer); + static void Remove(Timer& rTimer); + static void RequestReschedule(); + static void RescheduleIfNeeded(); +}; + +class Timer +{ +public: + Timer(size_t timeoutSecs, const std::string& rName = ""); + virtual ~Timer(); + Timer(const Timer &); + Timer &operator=(const Timer &); + + box_time_t GetExpiryTime() { return mExpires; } + virtual void OnExpire(); + bool HasExpired() + { + Timers::RescheduleIfNeeded(); + return mExpired; + } + + const std::string& GetName() const { return mName; } + +private: + box_time_t mExpires; + bool mExpired; + std::string mName; + + void Start(); + void Start(int64_t delayInMicros); + void Stop(); + + #ifdef WIN32 + HANDLE mTimerHandle; + static VOID CALLBACK TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired); + #endif +}; + +#include "MemLeakFindOff.h" + +#endif // TIMER__H diff --git a/lib/common/UnixUser.cpp b/lib/common/UnixUser.cpp new file mode 100644 index 00000000..f81b474c --- /dev/null +++ b/lib/common/UnixUser.cpp @@ -0,0 +1,123 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: UnixUser.cpp +// Purpose: Interface for managing the UNIX user of the current process +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_PWD_H + #include <pwd.h> +#endif + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "UnixUser.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::UnixUser(const char *) +// Purpose: Constructor, initialises to info of given username +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +UnixUser::UnixUser(const char *Username) + : mUID(0), + mGID(0), + mRevertOnDestruction(false) +{ + // Get password info + struct passwd *pwd = ::getpwnam(Username); + if(pwd == 0) + { + THROW_EXCEPTION(CommonException, CouldNotLookUpUsername) + } + + // Store UID and GID + mUID = pwd->pw_uid; + mGID = pwd->pw_gid; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::UnixUser(uid_t, gid_t) +// Purpose: Construct from given UNIX user ID and group ID +// Created: 15/3/04 +// +// -------------------------------------------------------------------------- +UnixUser::UnixUser(uid_t UID, gid_t GID) + : mUID(UID), + mGID(GID), + mRevertOnDestruction(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::~UnixUser() +// Purpose: Destructor -- reverts to previous user if the change wasn't perminant +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +UnixUser::~UnixUser() +{ + if(mRevertOnDestruction) + { + // Revert to "real" user and group id of the process + if(::setegid(::getgid()) != 0 || ::seteuid(::getuid()) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotRestoreProcessUser) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::ChangeProcessUser(bool) +// Purpose: Change the process user and group ID to the user. If Temporary == true +// the process username will be changed back when the object is destructed. +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void UnixUser::ChangeProcessUser(bool Temporary) +{ + if(Temporary) + { + // Change temporarily (change effective only) + if(::setegid(mGID) != 0 || ::seteuid(mUID) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) + } + + // Mark for change on destruction + mRevertOnDestruction = true; + } + else + { + // Change permanently (change all UIDs and GIDs) + if(::setgid(mGID) != 0 || ::setuid(mUID) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) + } + } +} + + + + diff --git a/lib/common/UnixUser.h b/lib/common/UnixUser.h new file mode 100644 index 00000000..c895eb2a --- /dev/null +++ b/lib/common/UnixUser.h @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: UnixUser.h +// Purpose: Interface for managing the UNIX user of the current process +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef UNIXUSER__H +#define UNIXUSER__H + +class UnixUser +{ +public: + UnixUser(const char *Username); + UnixUser(uid_t UID, gid_t GID); + ~UnixUser(); +private: + // no copying allowed + UnixUser(const UnixUser &); + UnixUser &operator=(const UnixUser &); +public: + + void ChangeProcessUser(bool Temporary = false); + + uid_t GetUID() {return mUID;} + gid_t GetGID() {return mGID;} + +private: + uid_t mUID; + gid_t mGID; + bool mRevertOnDestruction; +}; + +#endif // UNIXUSER__H + diff --git a/lib/common/Utils.cpp b/lib/common/Utils.cpp new file mode 100644 index 00000000..6f21330d --- /dev/null +++ b/lib/common/Utils.cpp @@ -0,0 +1,315 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Utils.cpp +// Purpose: Utility function +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> + +#include <cstdlib> + +#ifdef SHOW_BACKTRACE_ON_EXCEPTION + #include <execinfo.h> + #include <stdlib.h> +#endif + +#ifdef HAVE_CXXABI_H + #include <cxxabi.h> +#endif + +#include "Utils.h" +#include "CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +std::string GetBoxBackupVersion() +{ + return BOX_VERSION; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SplitString(const std::string &, char, std::vector<std::string> &) +// Purpose: Splits a string at a given character +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SplitString(const std::string &String, char SplitOn, std::vector<std::string> &rOutput) +{ + // Split it up. + std::string::size_type b = 0; + std::string::size_type e = 0; + while(e = String.find_first_of(SplitOn, b), e != String.npos) + { + // Get this string + unsigned int len = e - b; + if(len >= 1) + { + rOutput.push_back(String.substr(b, len)); + } + b = e + 1; + } + // Last string + if(b < String.size()) + { + rOutput.push_back(String.substr(b)); + } +/*#ifndef BOX_RELEASE_BUILD + BOX_TRACE("Splitting string '" << String << " on " << (char)SplitOn); + for(unsigned int l = 0; l < rOutput.size(); ++l) + { + BOX_TRACE(l << " = '" << rOutput[l] << "'"); + } +#endif*/ +} + +#ifdef SHOW_BACKTRACE_ON_EXCEPTION +void DumpStackBacktrace() +{ + void *array[10]; + size_t size = backtrace (array, 10); + char **strings = backtrace_symbols (array, size); + + BOX_TRACE("Obtained " << size << " stack frames."); + + for(size_t i = 0; i < size; i++) + { + // Demangling code copied from + // cctbx_sources/boost_adaptbx/meta_ext.cpp, BSD license + + std::string mangled_frame = strings[i]; + std::string output_frame = strings[i]; // default + + #ifdef HAVE_CXXABI_H + int start = mangled_frame.find('('); + int end = mangled_frame.find('+', start); + std::string mangled_func = mangled_frame.substr(start + 1, + end - start - 1); + + int status; + +#include "MemLeakFindOff.h" + char* result = abi::__cxa_demangle(mangled_func.c_str(), + NULL, NULL, &status); +#include "MemLeakFindOn.h" + + if (result == NULL) + { + if (status == 0) + { + BOX_WARNING("Demangle failed but no error: " << + mangled_func); + } + else if (status == -1) + { + BOX_WARNING("Demangle failed with " + "memory allocation error: " << + mangled_func); + } + else if (status == -2) + { + // Probably non-C++ name, don't demangle + /* + BOX_WARNING("Demangle failed with " + "with invalid name: " << + mangled_func); + */ + } + else if (status == -3) + { + BOX_WARNING("Demangle failed with " + "with invalid argument: " << + mangled_func); + } + else + { + BOX_WARNING("Demangle failed with " + "with unknown error " << status << + ": " << mangled_func); + } + } + else + { + output_frame = mangled_frame.substr(0, start + 1) + + result + mangled_frame.substr(end); +#include "MemLeakFindOff.h" + std::free(result); +#include "MemLeakFindOn.h" + } + #endif // HAVE_CXXABI_H + + BOX_TRACE("Stack frame " << i << ": " << output_frame); + } + +#include "MemLeakFindOff.h" + std::free (strings); +#include "MemLeakFindOn.h" +} +#endif + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileExists(const std::string& rFilename) +// Purpose: Does a file exist? +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +bool FileExists(const std::string& rFilename, int64_t *pFileSize, + bool TreatLinksAsNotExisting) +{ + EMU_STRUCT_STAT st; + if(EMU_LSTAT(rFilename.c_str(), &st) != 0) + { + if(errno == ENOENT) + { + return false; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError); + } + } + + // is it a file? + if((st.st_mode & S_IFDIR) == 0) + { + if(TreatLinksAsNotExisting && ((st.st_mode & S_IFLNK) != 0)) + { + return false; + } + + // Yes. Tell caller the size? + if(pFileSize != 0) + { + *pFileSize = st.st_size; + } + + return true; + } + else + { + return false; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ObjectExists(const std::string& rFilename) +// Purpose: Does a object exist, and if so, is it a file or a directory? +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +int ObjectExists(const std::string& rFilename) +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(rFilename.c_str(), &st) != 0) + { + if(errno == ENOENT) + { + return ObjectExists_NoObject; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError); + } + } + + // is it a file or a dir? + return ((st.st_mode & S_IFDIR) == 0)?ObjectExists_File:ObjectExists_Dir; +} + +std::string HumanReadableSize(int64_t Bytes) +{ + double readableValue = Bytes; + std::string units = " B"; + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "kB"; + } + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "MB"; + } + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "GB"; + } + + std::ostringstream result; + result << std::fixed << std::setprecision(2) << readableValue << + " " << units; + return result.str(); +} + +std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, + bool MachineReadable) +{ + std::ostringstream result; + + + if (MachineReadable) + { + result << (Bytes >> 10) << " kB, " << + std::setprecision(0) << ((Bytes*100)/Max) << "%"; + } + else + { + // Bar graph + char bar[17]; + unsigned int b = (int)((Bytes * (sizeof(bar)-1)) / Max); + if(b > sizeof(bar)-1) {b = sizeof(bar)-1;} + for(unsigned int l = 0; l < b; l++) + { + bar[l] = '*'; + } + for(unsigned int l = b; l < sizeof(bar) - 1; l++) + { + bar[l] = ' '; + } + bar[sizeof(bar)-1] = '\0'; + + result << std::fixed << + std::setw(10) << Blocks << " blocks, " << + std::setw(10) << HumanReadableSize(Bytes) << ", " << + std::setw(3) << std::setprecision(0) << + ((Bytes*100)/Max) << "% |" << bar << "|"; + } + + return result.str(); +} + +std::string FormatUsageLineStart(const std::string& rName, + bool MachineReadable) +{ + std::ostringstream result; + + if (MachineReadable) + { + result << rName << ": "; + } + else + { + result << std::setw(20) << std::right << rName << ": "; + } + + return result.str(); +} diff --git a/lib/common/Utils.h b/lib/common/Utils.h new file mode 100644 index 00000000..8d98a520 --- /dev/null +++ b/lib/common/Utils.h @@ -0,0 +1,44 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Utils.h +// Purpose: Utility function +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef UTILS__H +#define UTILS__H + +#include <string> +#include <vector> + +#include "MemLeakFindOn.h" + +std::string GetBoxBackupVersion(); + +void SplitString(const std::string &String, char SplitOn, std::vector<std::string> &rOutput); + +#ifdef SHOW_BACKTRACE_ON_EXCEPTION + void DumpStackBacktrace(); +#endif + +bool FileExists(const std::string& rFilename, int64_t *pFileSize = 0, + bool TreatLinksAsNotExisting = false); + +enum +{ + ObjectExists_NoObject = 0, + ObjectExists_File = 1, + ObjectExists_Dir = 2 +}; +int ObjectExists(const std::string& rFilename); +std::string HumanReadableSize(int64_t Bytes); +std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, + bool MachineReadable); +std::string FormatUsageLineStart(const std::string& rName, + bool MachineReadable); + +#include "MemLeakFindOff.h" + +#endif // UTILS__H diff --git a/lib/common/WaitForEvent.cpp b/lib/common/WaitForEvent.cpp new file mode 100644 index 00000000..5646bfbf --- /dev/null +++ b/lib/common/WaitForEvent.cpp @@ -0,0 +1,197 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WaitForEvent.cpp +// Purpose: Generic waiting for events, using an efficient method (platform dependent) +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <errno.h> +#include <string.h> + +#include "WaitForEvent.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::WaitForEvent() +// Purpose: Constructor +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +#ifdef HAVE_KQUEUE +WaitForEvent::WaitForEvent(int Timeout) + : mKQueue(::kqueue()), + mpTimeout(0) +{ + if(mKQueue == -1) + { + THROW_EXCEPTION(CommonException, CouldNotCreateKQueue) + } + + // Set the choosen timeout + SetTimeout(Timeout); +} +#else +WaitForEvent::WaitForEvent(int Timeout) + : mTimeout(Timeout), + mpPollInfo(0) +{ +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::~WaitForEvent() +// Purpose: Destructor +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +WaitForEvent::~WaitForEvent() +{ +#ifdef HAVE_KQUEUE + ::close(mKQueue); + mKQueue = -1; +#else + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::SetTimeout +// Purpose: Sets the timeout for future wait calls +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +void WaitForEvent::SetTimeout(int Timeout) +{ +#ifdef HAVE_KQUEUE + // Generate timeout + if(Timeout != TimeoutInfinite) + { + mTimeout.tv_sec = Timeout / 1000; + mTimeout.tv_nsec = (Timeout % 1000) * 1000000; + } + + // Infinite or not? + mpTimeout = (Timeout != TimeoutInfinite)?(&mTimeout):(NULL); +#else + mTimeout = Timeout; +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::Wait(int) +// Purpose: Wait for an event to take place. Returns a pointer to the object +// which has been signalled, or returns 0 for the timeout condition. +// Timeout specified in milliseconds. +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +void *WaitForEvent::Wait() +{ +#ifdef HAVE_KQUEUE + // Event return structure + struct kevent e; + ::memset(&e, 0, sizeof(e)); + + switch(::kevent(mKQueue, NULL, 0, &e, 1, mpTimeout)) + { + case 0: + // Timeout + return 0; + break; + + case 1: + // Event happened! + return e.udata; + break; + + default: + // Interrupted system calls aren't an error, just equivalent to a timeout + if(errno != EINTR) + { + THROW_EXCEPTION(CommonException, KEventErrorWait) + } + return 0; + break; + } +#else + // Use poll() instead. + // Need to build the structures? + if(mpPollInfo == 0) + { + // Yes... + mpPollInfo = (struct pollfd *)::malloc((sizeof(struct pollfd) * mItems.size()) + 4); + if(mpPollInfo == 0) + { + throw std::bad_alloc(); + } + + // Build... + for(unsigned int l = 0; l < mItems.size(); ++l) + { + mpPollInfo[l].fd = mItems[l].fd; + mpPollInfo[l].events = mItems[l].events; + mpPollInfo[l].revents = 0; + } + } + + // Make sure everything is reset (don't really have to do this, but don't trust the OS) + for(unsigned int l = 0; l < mItems.size(); ++l) + { + mpPollInfo[l].revents = 0; + } + + // Poll! + switch(::poll(mpPollInfo, mItems.size(), mTimeout)) + { + case -1: + // Interrupted system calls aren't an error, just equivalent to a timeout + if(errno != EINTR) + { + THROW_EXCEPTION(CommonException, KEventErrorWait) + } + return 0; + break; + case 0: // timed out + return 0; + break; + default: // got some thing... + // control flows on... + break; + } + + // Find the item which was ready + for(unsigned int s = 0; s < mItems.size(); ++s) + { + if(mpPollInfo[s].revents & POLLIN) + { + return mItems[s].item; + break; + } + } +#endif + + return 0; +} + diff --git a/lib/common/WaitForEvent.h b/lib/common/WaitForEvent.h new file mode 100644 index 00000000..a80761ef --- /dev/null +++ b/lib/common/WaitForEvent.h @@ -0,0 +1,152 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WaitForEvent.h +// Purpose: Generic waiting for events, using an efficient method (platform dependent) +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef WAITFOREVENT__H +#define WAITFOREVENT__H + +#include <cstdlib> + +#ifdef HAVE_KQUEUE + #include <sys/event.h> + #include <sys/time.h> +#else + #include <vector> + #ifndef WIN32 + #include <poll.h> + #endif +#endif + +#include <cstdlib> + +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +class WaitForEvent +{ +public: + WaitForEvent(int Timeout = TimeoutInfinite); + ~WaitForEvent(); +private: + // No copying. + WaitForEvent(const WaitForEvent &); + WaitForEvent &operator=(const WaitForEvent &); +public: + + enum + { + TimeoutInfinite = -1 + }; + + void SetTimeout(int Timeout = TimeoutInfinite); + + void *Wait(); + +#ifndef HAVE_KQUEUE + typedef struct + { + int fd; + short events; + void *item; + } ItemInfo; +#endif + + // -------------------------------------------------------------------------- + // + // Function + // Name: WaitForEvent::Add(const Type &, int) + // Purpose: Adds an event to the list of items to wait on. The flags are passed to the object. + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + template<typename T> + void Add(const T *pItem, int Flags = 0) + { + ASSERT(pItem != 0); +#ifdef HAVE_KQUEUE + struct kevent e; + pItem->FillInKEvent(e, Flags); + // Fill in extra flags to say what to do + e.flags |= EV_ADD; + e.udata = (void*)pItem; + if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) + { + THROW_EXCEPTION(CommonException, KEventErrorAdd) + } +#else + // Add item + ItemInfo i; + pItem->FillInPoll(i.fd, i.events, Flags); + i.item = (void*)pItem; + mItems.push_back(i); + // Delete any pre-prepared poll info, as it's now out of date + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } +#endif + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: WaitForEvent::Remove(const Type &, int) + // Purpose: Removes an event from the list of items to wait on. The flags are passed to the object. + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + template<typename T> + void Remove(const T *pItem, int Flags = 0) + { + ASSERT(pItem != 0); +#ifdef HAVE_KQUEUE + struct kevent e; + pItem->FillInKEvent(e, Flags); + // Fill in extra flags to say what to do + e.flags |= EV_DELETE; + e.udata = (void*)pItem; + if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) + { + THROW_EXCEPTION(CommonException, KEventErrorRemove) + } +#else + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } + for(std::vector<ItemInfo>::iterator i(mItems.begin()); i != mItems.end(); ++i) + { + if(i->item == pItem) + { + mItems.erase(i); + return; + } + } +#endif + } + +private: +#ifdef HAVE_KQUEUE + int mKQueue; + struct timespec mTimeout; + struct timespec *mpTimeout; +#else + int mTimeout; + std::vector<ItemInfo> mItems; + struct pollfd *mpPollInfo; +#endif +}; + +#include "MemLeakFindOff.h" + +#endif // WAITFOREVENT__H + + diff --git a/lib/common/ZeroStream.cpp b/lib/common/ZeroStream.cpp new file mode 100644 index 00000000..9d87d76a --- /dev/null +++ b/lib/common/ZeroStream.cpp @@ -0,0 +1,170 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ZeroStream.cpp +// Purpose: An IOStream which returns all zeroes up to a certain size +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "ZeroStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::ZeroStream(IOStream::pos_type) +// Purpose: Constructor +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- +ZeroStream::ZeroStream(IOStream::pos_type size) +: mSize(size), mPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int ZeroStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + ASSERT(NBytes > 0); + + int bytesToRead = NBytes; + + if (bytesToRead > mSize - mPosition) + { + bytesToRead = mSize - mPosition; + } + + memset(pBuffer, 0, bytesToRead); + mPosition += bytesToRead; + + return bytesToRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ZeroStream::BytesLeftToRead() +{ + return mSize - mPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ZeroStream::GetPosition() const +{ + return mPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + switch (SeekType) + { + case SeekType_Absolute: + { + mPosition = Offset; + } + break; + + case SeekType_Relative: + { + mPosition += Offset; + } + break; + + case SeekType_End: + { + mPosition = mSize - Offset; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ZeroStream::StreamDataLeft() +{ + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ZeroStream::StreamClosed() +{ + return false; +} + diff --git a/lib/common/ZeroStream.h b/lib/common/ZeroStream.h new file mode 100644 index 00000000..0119045b --- /dev/null +++ b/lib/common/ZeroStream.h @@ -0,0 +1,39 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ZeroStream.h +// Purpose: An IOStream which returns all zeroes up to a certain size +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- + +#ifndef ZEROSTREAM__H +#define ZEROSTREAM__H + +#include "IOStream.h" + +class ZeroStream : public IOStream +{ +private: + IOStream::pos_type mSize, mPosition; + +public: + ZeroStream(IOStream::pos_type mSize); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + ZeroStream(const ZeroStream &rToCopy); +}; + +#endif // ZEROSTREAM__H + + diff --git a/lib/common/makeexception.pl.in b/lib/common/makeexception.pl.in new file mode 100755 index 00000000..76b9b02b --- /dev/null +++ b/lib/common/makeexception.pl.in @@ -0,0 +1,283 @@ +#!@PERL@ + +# global exception list file +my $global_list = '../../ExceptionCodes.txt'; + + +my @exception; +my @exception_desc; +my $class; +my $class_number; + +# read the description! + +open EXCEPTION_DESC,$ARGV[0] or die "Can't open $ARGV[0]"; + +while(<EXCEPTION_DESC>) +{ + chomp; s/\A\s+//; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + if(m/\AEXCEPTION\s+(.+)\s+(\d+)\Z/) + { + $class = $1; + $class_number = $2; + } + else + { + my ($name,$number,$description) = split /\s+/,$_,3; + if($name eq '' || $number =~ m/\D/) + { + die "Bad line '$_'"; + } + if($exception[$number] ne '') + { + die "Duplicate exception number $number"; + } + $exception[$number] = $name; + $exception_desc[$number] = $description; + } +} + +die "Exception class and number not specified" unless $class ne '' && $class_number ne ''; + +close EXCEPTION_DESC; + +# write the code +print "Generating $class exception...\n"; + +open CPP,">autogen_${class}Exception.cpp" or die "Can't open cpp file for writing"; +open H,">autogen_${class}Exception.h" or die "Can't open h file for writing"; + +# write header file +my $guardname = uc 'AUTOGEN_'.$class.'EXCEPTION_H'; +print H <<__E; + +// Auto-generated file -- do not edit + +#ifndef $guardname +#define $guardname + +#include "BoxException.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ${class}Exception +// Purpose: Exception +// Created: autogen +// +// -------------------------------------------------------------------------- +class ${class}Exception : public BoxException +{ +public: + ${class}Exception(unsigned int SubType, + const std::string& rMessage = "") + : mSubType(SubType), mMessage(rMessage) + { + } + + ${class}Exception(const ${class}Exception &rToCopy) + : mSubType(rToCopy.mSubType), mMessage(rToCopy.mMessage) + { + } + + ~${class}Exception() throw () + { + } + + enum + { + ExceptionType = $class_number + }; + + enum + { +__E + +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + print H "\t\t".$exception[$e].' = '.$e.(($e==$#exception)?'':',')."\n" + } +} + +print H <<__E; + }; + + virtual unsigned int GetType() const throw(); + virtual unsigned int GetSubType() const throw(); + virtual const char *what() const throw(); + virtual const std::string& GetMessage() const + { + return mMessage; + } + +private: + unsigned int mSubType; + std::string mMessage; +}; + +#endif // $guardname +__E + +# ----------------------------------------------------------------------------------------------------------- + +print CPP <<__E; + +// Auto-generated file -- do not edit + +#include "Box.h" +#include "autogen_${class}Exception.h" + +#include "MemLeakFindOn.h" + +#ifdef EXCEPTION_CODENAMES_EXTENDED + #ifdef EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION +static const char *whats[] = { +__E + +my $last_seen = -1; +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + for(my $s = $last_seen + 1; $s < $e; $s++) + { + print CPP "\t\"UNUSED\",\n" + } + my $ext = ($exception_desc[$e] ne '')?" ($exception_desc[$e])":''; + print CPP "\t\"${class} ".$exception[$e].$ext.'"'.(($e==$#exception)?'':',')."\n"; + $last_seen = $e; + } +} + +print CPP <<__E; +}; + #else +static const char *whats[] = { +__E + +$last_seen = -1; +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + for(my $s = $last_seen + 1; $s < $e; $s++) + { + print CPP "\t\"UNUSED\",\n" + } + print CPP "\t\"${class} ".$exception[$e].'"'.(($e==$#exception)?'':',')."\n"; + $last_seen = $e; + } +} + +print CPP <<__E; +}; + #endif +#endif + +unsigned int ${class}Exception::GetType() const throw() +{ + return ${class}Exception::ExceptionType; +} + +unsigned int ${class}Exception::GetSubType() const throw() +{ + return mSubType; +} + +const char *${class}Exception::what() const throw() +{ +#ifdef EXCEPTION_CODENAMES_EXTENDED + if(mSubType < 0 || mSubType > (sizeof(whats) / sizeof(whats[0]))) + { + return "${class}"; + } + return whats[mSubType]; +#else + return "${class}"; +#endif +} + +__E + +close H; +close CPP; + +# update the global exception list +my $list_before; +my $list_after; +my $is_after = 0; +if(open CURRENT,$global_list) +{ + while(<CURRENT>) + { + next if m/\A#/; + + if(m/\AEXCEPTION TYPE (\w+) (\d+)/) + { + # check that the number isn't being reused + if($2 == $class_number && $1 ne $class) + { + die "Class number $class_number is being used by $class and $1 -- correct this.\n"; + } + if($2 > $class_number) + { + # This class comes after the current one (ensures numerical ordering) + $is_after = 1; + } + if($1 eq $class) + { + # skip this entry + while(<CURRENT>) + { + last if m/\AEND TYPE/; + } + $_ = ''; + } + } + + if($is_after) + { + $list_after .= $_; + } + else + { + $list_before .= $_; + } + } + + close CURRENT; +} + +open GLOBAL,">$global_list" or die "Can't open global exception code listing for writing"; + +print GLOBAL <<__E; +# +# automatically generated file, do not edit. +# +# This file lists all the exception codes used by the system. +# Use to look up more detailed descriptions of meanings of errors. +# +__E + +print GLOBAL $list_before; + +print GLOBAL "EXCEPTION TYPE $class $class_number\n"; +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + my $ext = ($exception_desc[$e] ne '')?" - $exception_desc[$e]":''; + print GLOBAL "($class_number/$e) - ${class} ".$exception[$e].$ext."\n"; + } +} +print GLOBAL "END TYPE\n"; + +print GLOBAL $list_after; + +close GLOBAL; + + diff --git a/lib/compress/Compress.h b/lib/compress/Compress.h new file mode 100644 index 00000000..de38af2c --- /dev/null +++ b/lib/compress/Compress.h @@ -0,0 +1,197 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Compress.h +// Purpose: Interface to zlib compression +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSCONTEXT__H +#define COMPRESSCONTEXT__H + +#include <zlib.h> + +#include "CompressException.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: Compress +// Purpose: Interface to zlib compression, only very slight wrapper. +// (Use CompressStream for a more friendly interface.) +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- +template<bool Compressing> +class Compress +{ +public: + Compress() + : mFinished(false), + mFlush(Z_NO_FLUSH) + { + // initialise stream + mStream.zalloc = Z_NULL; + mStream.zfree = Z_NULL; + mStream.opaque = Z_NULL; + mStream.data_type = Z_BINARY; + + if((Compressing)?(deflateInit(&mStream, Z_DEFAULT_COMPRESSION)) + :(inflateInit(&mStream)) != Z_OK) + { + THROW_EXCEPTION(CompressException, InitFailed) + } + + mStream.avail_in = 0; + } + + ~Compress() + { + int r = 0; + if((r = ((Compressing)?(deflateEnd(&mStream)) + :(inflateEnd(&mStream)))) != Z_OK) + { + BOX_WARNING("zlib error code = " << r); + if(r == Z_DATA_ERROR) + { + BOX_WARNING("End of compress/decompress " + "without all input being consumed, " + "possible corruption?"); + } + else + { + THROW_EXCEPTION(CompressException, EndFailed) + } + } + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::InputRequired() + // Purpose: Input required yet? + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + bool InputRequired() + { + return mStream.avail_in <= 0; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::Input(const void *, int) + // Purpose: Set the input buffer ready for next output call. + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + void Input(const void *pInBuffer, int InLength) + { + // Check usage + if(mStream.avail_in != 0) + { + THROW_EXCEPTION(CompressException, BadUsageInputNotRequired) + } + + // Store info + mStream.next_in = (unsigned char *)pInBuffer; + mStream.avail_in = InLength; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::FinishInput() + // Purpose: When compressing, no more input will be given. + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + void FinishInput() + { + mFlush = Z_FINISH; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::Output(void *, int) + // Purpose: Get some output data + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + int Output(void *pOutBuffer, int OutLength, bool SyncFlush = false) + { + // need more input? + if(mStream.avail_in == 0 && mFlush != Z_FINISH && !SyncFlush) + { + return 0; + } + + // Buffers + mStream.next_out = (unsigned char *)pOutBuffer; + mStream.avail_out = OutLength; + + // Call one of the functions + int flush = mFlush; + if(SyncFlush && mFlush != Z_FINISH) + { + flush = Z_SYNC_FLUSH; + } + int ret = (Compressing)?(deflate(&mStream, flush)):(inflate(&mStream, flush)); + + if(SyncFlush && ret == Z_BUF_ERROR) + { + // No progress possible. Just return 0. + return 0; + } + + // Check errors + if(ret < 0) + { + BOX_WARNING("zlib error code = " << ret); + THROW_EXCEPTION(CompressException, TransformFailed) + } + + // Parse result + if(ret == Z_STREAM_END) + { + mFinished = true; + } + + // Return how much data was output + return OutLength - mStream.avail_out; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::OutputHasFinished() + // Purpose: No more output to be recieved + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + bool OutputHasFinished() + { + return mFinished; + } + + +private: + z_stream mStream; + bool mFinished; + int mFlush; +}; + +template<typename Integer> +Integer Compress_MaxSizeForCompressedData(Integer InLen) +{ + // Conservative rendition of the info found here: http://www.gzip.org/zlib/zlib_tech.html + int blocks = (InLen + 32*1024 - 1) / (32*1024); + return InLen + (blocks * 6) + 8; +} + + +#endif // COMPRESSCONTEXT__H + diff --git a/lib/compress/CompressException.h b/lib/compress/CompressException.h new file mode 100644 index 00000000..7e8c9ca2 --- /dev/null +++ b/lib/compress/CompressException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSEXCEPTION__H +#define COMPRESSEXCEPTION__H + +// Compatibility +#include "autogen_CompressException.h" + +#endif // COMPRESSEXCEPTION__H + diff --git a/lib/compress/CompressException.txt b/lib/compress/CompressException.txt new file mode 100644 index 00000000..278b76d3 --- /dev/null +++ b/lib/compress/CompressException.txt @@ -0,0 +1,12 @@ +EXCEPTION Compress 6 + +Internal 0 +InitFailed 1 +EndFailed 2 +BadUsageInputNotRequired 3 +TransformFailed 4 +CopyCompressStreamNotAllowed 5 +NullPointerPassedToCompressStream 6 +CompressStreamReadSupportNotRequested 7 Specify read in the constructor +CompressStreamWriteSupportNotRequested 8 Specify write in the constructor +CannotWriteToClosedCompressStream 9 diff --git a/lib/compress/CompressStream.cpp b/lib/compress/CompressStream.cpp new file mode 100644 index 00000000..9bb73e3d --- /dev/null +++ b/lib/compress/CompressStream.cpp @@ -0,0 +1,425 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CompressStream.h +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <memory> + +#include "CompressStream.h" +#include "Compress.h" +#include "autogen_CompressException.h" + +#include "MemLeakFindOn.h" + +// How big a buffer to use +#ifndef BOX_RELEASE_BUILD + // debug! + #define BUFFER_SIZE 256 +#else + #define BUFFER_SIZE (32*1024) +#endif + +#define USE_READ_COMPRESSOR \ + CheckRead(); \ + Compress<false> *pDecompress = (Compress<false> *)mpReadCompressor; + +#define USE_WRITE_COMPRESSOR \ + CheckWrite(); \ + Compress<true> *pCompress = (Compress<true> *)mpWriteCompressor; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CompressStream(IOStream *, bool, bool, bool, bool) +// Purpose: Constructor +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed) + : mpStream(pStream), + mHaveOwnership(TakeOwnership), + mDecompressRead(DecompressRead), + mCompressWrite(CompressWrite), + mPassThroughWhenNotCompressed(PassThroughWhenNotCompressed), + mpReadCompressor(0), + mpWriteCompressor(0), + mpBuffer(0), + mIsClosed(false) +{ + if(mpStream == 0) + { + THROW_EXCEPTION(CompressException, NullPointerPassedToCompressStream) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::~CompressStream() +// Purpose: Destructor +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::~CompressStream() +{ + // Clean up compressors + if(mpReadCompressor) + { + delete ((Compress<false>*)mpReadCompressor); + mpReadCompressor = 0; + } + if(mpWriteCompressor) + { + delete ((Compress<true>*)mpWriteCompressor); + mpWriteCompressor = 0; + } + + // Delete the stream, if we have ownership + if(mHaveOwnership) + { + delete mpStream; + mpStream = 0; + } + + // Free any buffer + if(mpBuffer != 0) + { + ::free(mpBuffer); + mpBuffer = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CompressStream(const CompressStream &) +// Purpose: Copy constructor, will exception +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::CompressStream(const CompressStream &) +{ + THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::operator=(const CompressStream &) +// Purpose: Assignment operator, will exception +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream &CompressStream::operator=(const CompressStream &) +{ + THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Read(void *, int, int) +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +int CompressStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + USE_READ_COMPRESSOR + if(pDecompress == 0) + { + return mpStream->Read(pBuffer, NBytes, Timeout); + } + + // Where is the buffer? (note if writing as well, read buffer is second in a block of two buffer sizes) + void *pbuf = (mDecompressRead && mCompressWrite)?(((uint8_t*)mpBuffer) + BUFFER_SIZE):mpBuffer; + + // Any data left to go? + if(!pDecompress->InputRequired()) + { + // Output some data from the existing data read + return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); + } + + // Read data into the buffer -- read as much as possible in one go + int s = mpStream->Read(pbuf, BUFFER_SIZE, Timeout); + if(s == 0) + { + return 0; + } + + // Give input to the compressor + pDecompress->Input(pbuf, s); + + // Output as much as possible + return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Write(const void *, int) +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::Write(const void *pBuffer, int NBytes) +{ + USE_WRITE_COMPRESSOR + if(pCompress == 0) + { + mpStream->Write(pBuffer, NBytes); + return; + } + + if(mIsClosed) + { + THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) + } + + // Give the data to the compressor + pCompress->Input(pBuffer, NBytes); + + // Write the data to the stream + WriteCompressedData(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::WriteAllBuffered() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::WriteAllBuffered() +{ + if(mIsClosed) + { + THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) + } + + // Just ask compressed data to be written out, but with the sync flag set + WriteCompressedData(true); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Close() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::Close() +{ + if(mCompressWrite) + { + USE_WRITE_COMPRESSOR + if(pCompress != 0) + { + // Flush anything from the write buffer + pCompress->FinishInput(); + WriteCompressedData(); + + // Mark as definately closed + mIsClosed = true; + } + } + + // Close + mpStream->Close(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::WriteCompressedData(bool) +// Purpose: Private. Writes the output of the compressor to the stream, +// optionally doing a sync flush. +// Created: 28/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::WriteCompressedData(bool SyncFlush) +{ + USE_WRITE_COMPRESSOR + if(pCompress == 0) {THROW_EXCEPTION(CompressException, Internal)} + + int s = 0; + do + { + s = pCompress->Output(mpBuffer, BUFFER_SIZE, SyncFlush); + if(s > 0) + { + mpStream->Write(mpBuffer, s); + } + } while(s > 0); + // Check assumption -- all input has been consumed + if(!pCompress->InputRequired()) {THROW_EXCEPTION(CompressException, Internal)} +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::StreamDataLeft() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +bool CompressStream::StreamDataLeft() +{ + USE_READ_COMPRESSOR + if(pDecompress == 0) + { + return mpStream->StreamDataLeft(); + } + + // Any bytes left in our buffer? + if(!pDecompress->InputRequired()) + { + // Still buffered data to decompress + return true; + } + + // Otherwise ask the stream + return mpStream->StreamDataLeft(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::StreamClosed() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +bool CompressStream::StreamClosed() +{ + if(!mIsClosed && mpStream->StreamClosed()) + { + mIsClosed = true; + } + return mIsClosed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckRead() +// Purpose: Checks that everything is set up to read +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckRead() +{ + // Has the read compressor already been created? + if(mpReadCompressor != 0) + { + return; + } + + // Need to create one? + if(mDecompressRead) + { + mpReadCompressor = new Compress<false>(); + // And make sure there's a buffer + CheckBuffer(); + } + else + { + // Not decompressing. Should be passing through data? + if(!mPassThroughWhenNotCompressed) + { + THROW_EXCEPTION(CompressException, CompressStreamReadSupportNotRequested) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckWrite() +// Purpose: Checks that everything is set up to write +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckWrite() +{ + // Has the read compressor already been created? + if(mpWriteCompressor != 0) + { + return; + } + + // Need to create one? + if(mCompressWrite) + { + mpWriteCompressor = new Compress<true>(); + // And make sure there's a buffer + CheckBuffer(); + } + else + { + // Not decompressing. Should be passing through data? + if(!mPassThroughWhenNotCompressed) + { + THROW_EXCEPTION(CompressException, CompressStreamWriteSupportNotRequested) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckBuffer() +// Purpose: Allocates the buffer for (de)compression operations +// Created: 28/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckBuffer() +{ + // Already done + if(mpBuffer != 0) + { + return; + } + + // Allocate the buffer -- which may actually be two buffers in one + // The temporary use buffer is first (used by write only, so only present if writing) + // and the read buffer follows. + int size = BUFFER_SIZE; + if(mDecompressRead && mCompressWrite) + { + size *= 2; + } + BOX_TRACE("Allocating CompressStream buffer, size " << size); + mpBuffer = ::malloc(size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } +} + + diff --git a/lib/compress/CompressStream.h b/lib/compress/CompressStream.h new file mode 100644 index 00000000..7959e3dc --- /dev/null +++ b/lib/compress/CompressStream.h @@ -0,0 +1,62 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CompressStream.h +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSSTREAM__H +#define COMPRESSSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CompressStream +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +class CompressStream : public IOStream +{ +public: + CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed = false); + ~CompressStream(); +private: + // No copying (have implementations which exception) + CompressStream(const CompressStream &); + CompressStream &operator=(const CompressStream &); +public: + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes); + virtual void WriteAllBuffered(); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +protected: + void CheckRead(); + void CheckWrite(); + void CheckBuffer(); + void WriteCompressedData(bool SyncFlush = false); + +private: + IOStream *mpStream; + bool mHaveOwnership; + bool mDecompressRead; + bool mCompressWrite; + bool mPassThroughWhenNotCompressed; + // Avoid having to include Compress.h + void *mpReadCompressor; + void *mpWriteCompressor; + void *mpBuffer; + bool mIsClosed; +}; + +#endif // COMPRESSSTREAM__H + diff --git a/lib/compress/Makefile.extra b/lib/compress/Makefile.extra new file mode 100644 index 00000000..a29fcdb4 --- /dev/null +++ b/lib/compress/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CompressException.h autogen_CompressException.cpp: $(MAKEEXCEPTION) CompressException.txt + $(_PERL) $(MAKEEXCEPTION) CompressException.txt + diff --git a/lib/crypto/CipherAES.cpp b/lib/crypto/CipherAES.cpp new file mode 100644 index 00000000..fad8b968 --- /dev/null +++ b/lib/crypto/CipherAES.cpp @@ -0,0 +1,163 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherAES.cpp +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +// Only available in new versions of openssl +#ifndef HAVE_OLD_SSL + +#include <openssl/evp.h> + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherAES.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::CipherAES(CipherDescription::CipherMode, const void *, unsigned int, const void *) +// Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) + : CipherDescription(), + mMode(Mode), + mpKey(pKey), + mKeyLength(KeyLength), + mpInitialisationVector(pInitialisationVector) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::CipherAES(const CipherAES &) +// Purpose: Copy constructor +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::CipherAES(const CipherAES &rToCopy) + : CipherDescription(rToCopy), + mMode(rToCopy.mMode), + mpKey(rToCopy.mpKey), + mKeyLength(rToCopy.mKeyLength), + mpInitialisationVector(rToCopy.mpInitialisationVector) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherAES::CipherAES() +// Purpose: Destructor +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::~CipherAES() +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::operator=(const CipherAES &) +// Purpose: Assignment operator +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES &CipherAES::operator=(const CipherAES &rToCopy) +{ + CipherDescription::operator=(rToCopy); + + mMode = rToCopy.mMode; + mpKey = rToCopy.mpKey; + mKeyLength = rToCopy.mKeyLength; + mpInitialisationVector = rToCopy.mpInitialisationVector; + + return *this; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::GetCipher() +// Purpose: Returns cipher object +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +const EVP_CIPHER *CipherAES::GetCipher() const +{ + switch(mMode) + { + case CipherDescription::Mode_ECB: + switch(mKeyLength) + { + case (128/8): return EVP_aes_128_ecb(); break; + case (192/8): return EVP_aes_192_ecb(); break; + case (256/8): return EVP_aes_256_ecb(); break; + default: + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + break; + } + break; + + case CipherDescription::Mode_CBC: + switch(mKeyLength) + { + case (128/8): return EVP_aes_128_cbc(); break; + case (192/8): return EVP_aes_192_cbc(); break; + case (256/8): return EVP_aes_256_cbc(); break; + default: + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + break; + } + break; + + default: + break; + } + + // Unknown! + THROW_EXCEPTION(CipherException, UnknownCipherMode) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::SetupParameters(EVP_CIPHER_CTX *) +// Purpose: Set up various parameters for cipher +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +void CipherAES::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const +{ + ASSERT(pCipherContext != 0); + + // Set key (key length is implied) + if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +} + + + +#endif // n HAVE_OLD_SSL + diff --git a/lib/crypto/CipherAES.h b/lib/crypto/CipherAES.h new file mode 100644 index 00000000..50b96dc3 --- /dev/null +++ b/lib/crypto/CipherAES.h @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherAES.h +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERAES__H +#define CIPHERAES__H + +// Only available in new versions of openssl +#ifndef HAVE_OLD_SSL + +#include "CipherDescription.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherAES +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +class CipherAES : public CipherDescription +{ +public: + CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); + CipherAES(const CipherAES &rToCopy); + virtual ~CipherAES(); + CipherAES &operator=(const CipherAES &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; + +private: + CipherDescription::CipherMode mMode; + const void *mpKey; + unsigned int mKeyLength; + const void *mpInitialisationVector; +}; + +#endif // n HAVE_OLD_SSL + +#endif // CIPHERAES__H + diff --git a/lib/crypto/CipherBlowfish.cpp b/lib/crypto/CipherBlowfish.cpp new file mode 100644 index 00000000..e16cc6ed --- /dev/null +++ b/lib/crypto/CipherBlowfish.cpp @@ -0,0 +1,220 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherBlowfish.cpp +// Purpose: Blowfish cipher description +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/evp.h> + +#ifdef HAVE_OLD_SSL + #include <string.h> + #include <strings.h> +#endif + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherBlowfish.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode, const void *, unsigned int, const void *) +// Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) + : CipherDescription(), + mMode(Mode) +#ifndef HAVE_OLD_SSL + , mpKey(pKey), + mKeyLength(KeyLength), + mpInitialisationVector(pInitialisationVector) +{ +} +#else +{ + mKey.assign((const char *)pKey, KeyLength); + if(pInitialisationVector == 0) + { + bzero(mInitialisationVector, sizeof(mInitialisationVector)); + } + else + { + ::memcpy(mInitialisationVector, pInitialisationVector, sizeof(mInitialisationVector)); + } +} +#endif + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::CipherBlowfish(const CipherBlowfish &) +// Purpose: Copy constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::CipherBlowfish(const CipherBlowfish &rToCopy) + : CipherDescription(rToCopy), + mMode(rToCopy.mMode), +#ifndef HAVE_OLD_SSL + mpKey(rToCopy.mpKey), + mKeyLength(rToCopy.mKeyLength), + mpInitialisationVector(rToCopy.mpInitialisationVector) +{ +} +#else + mKey(rToCopy.mKey) +{ + ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); +} +#endif + + +#ifdef HAVE_OLD_SSL +// Hack functions to support old OpenSSL API +CipherDescription *CipherBlowfish::Clone() const +{ + return new CipherBlowfish(*this); +} +void CipherBlowfish::SetIV(const void *pIV) +{ + if(pIV == 0) + { + bzero(mInitialisationVector, sizeof(mInitialisationVector)); + } + else + { + ::memcpy(mInitialisationVector, pIV, sizeof(mInitialisationVector)); + } +} +#endif + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherBlowfish::CipherBlowfish() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::~CipherBlowfish() +{ +#ifdef HAVE_OLD_SSL + // Zero copy of key + for(unsigned int l = 0; l < mKey.size(); ++l) + { + mKey[l] = '\0'; + } +#endif +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::operator=(const CipherBlowfish &) +// Purpose: Assignment operator +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish &CipherBlowfish::operator=(const CipherBlowfish &rToCopy) +{ + CipherDescription::operator=(rToCopy); + + mMode = rToCopy.mMode; +#ifndef HAVE_OLD_SSL + mpKey = rToCopy.mpKey; + mKeyLength = rToCopy.mKeyLength; + mpInitialisationVector = rToCopy.mpInitialisationVector; +#else + mKey = rToCopy.mKey; + ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); +#endif + + return *this; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::GetCipher() +// Purpose: Returns cipher object +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +const EVP_CIPHER *CipherBlowfish::GetCipher() const +{ + switch(mMode) + { + case CipherDescription::Mode_ECB: + return EVP_bf_ecb(); + break; + + case CipherDescription::Mode_CBC: + return EVP_bf_cbc(); + break; + + case CipherDescription::Mode_CFB: + return EVP_bf_cfb(); + break; + + case CipherDescription::Mode_OFB: + return EVP_bf_ofb(); + break; + + default: + break; + } + + // Unknown! + THROW_EXCEPTION(CipherException, UnknownCipherMode) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *) +// Purpose: Set up various parameters for cipher +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const +{ + ASSERT(pCipherContext != 0); + + // Set key length +#ifndef HAVE_OLD_SSL + if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKeyLength) != 1) +#else + if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKey.size()) != 1) +#endif + { + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + } + // Set key +#ifndef HAVE_OLD_SSL + if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) +#else + if(EVP_CipherInit(pCipherContext, NULL, (unsigned char*)mKey.c_str(), (unsigned char*)mInitialisationVector, -1) != 1) +#endif + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +} + + + diff --git a/lib/crypto/CipherBlowfish.h b/lib/crypto/CipherBlowfish.h new file mode 100644 index 00000000..b3bcf028 --- /dev/null +++ b/lib/crypto/CipherBlowfish.h @@ -0,0 +1,60 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherBlowfish.h +// Purpose: Blowfish cipher description +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERBLOWFISH__H +#define CIPHERBLOWFISH__H + +#ifdef HAVE_OLD_SSL + #include <string> +#endif + +#include "CipherDescription.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherBlowfish +// Purpose: Description of Blowfish cipher parameters -- note that copies are not made of key material and IV, careful with object lifetimes. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherBlowfish : public CipherDescription +{ +public: + CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); + CipherBlowfish(const CipherBlowfish &rToCopy); + virtual ~CipherBlowfish(); + CipherBlowfish &operator=(const CipherBlowfish &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; + +#ifdef HAVE_OLD_SSL + CipherDescription *Clone() const; + void SetIV(const void *pIV); +#endif + +private: + CipherDescription::CipherMode mMode; +#ifndef HAVE_OLD_SSL + const void *mpKey; + unsigned int mKeyLength; + const void *mpInitialisationVector; +#else + std::string mKey; + uint8_t mInitialisationVector[8]; +#endif +}; + + +#endif // CIPHERBLOWFISH__H + diff --git a/lib/crypto/CipherContext.cpp b/lib/crypto/CipherContext.cpp new file mode 100644 index 00000000..e5cd9b0e --- /dev/null +++ b/lib/crypto/CipherContext.cpp @@ -0,0 +1,620 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherContext.cpp +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE +#include "CipherContext.h" +#include "CipherDescription.h" +#include "CipherException.h" +#include "Random.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::CipherContext() +// Purpose: Constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherContext::CipherContext() + : mInitialised(false), + mWithinTransform(false), + mPaddingOn(true) +#ifdef HAVE_OLD_SSL + , mFunction(Decrypt), + mpDescription(0) +#endif +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::~CipherContext() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherContext::~CipherContext() +{ + if(mInitialised) + { + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + mInitialised = false; + } +#ifdef HAVE_OLD_SSL + if(mpDescription != 0) + { + delete mpDescription; + mpDescription = 0; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// 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 +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription) +{ + // Check for bad usage + if(mInitialised) + { + THROW_EXCEPTION(CipherException, AlreadyInitialised) + } + if(Function != Decrypt && Function != Encrypt) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Initialise the cipher +#ifndef HAVE_OLD_SSL + EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does + + if(EVP_CipherInit_ex(&ctx, rDescription.GetCipher(), NULL, NULL, NULL, Function) != 1) +#else + // Store function for later + mFunction = Function; + + // Use old version of init call + if(EVP_CipherInit(&ctx, rDescription.GetCipher(), NULL, NULL, Function) != 1) +#endif + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + + try + { +#ifndef HAVE_OLD_SSL + // Let the description set up everything else + rDescription.SetupParameters(&ctx); +#else + // With the old version, a copy needs to be taken first. + mpDescription = rDescription.Clone(); + // Mark it as not a leak, otherwise static cipher contexts + // cause spurious memory leaks to be reported + MEMLEAKFINDER_NOT_A_LEAK(mpDescription); + mpDescription->SetupParameters(&ctx); +#endif + } + catch(...) + { + EVP_CIPHER_CTX_cleanup(&ctx); + throw; + } + + // mark as initialised + mInitialised = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Reset() +// Purpose: Reset the context, so it can be initialised again with a different key +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Reset() +{ + if(mInitialised) + { + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + mInitialised = false; + } +#ifdef HAVE_OLD_SSL + if(mpDescription != 0) + { + delete mpDescription; + mpDescription = 0; + } +#endif + mWithinTransform = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Begin() +// Purpose: Begin a transformation +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Begin() +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation (not an error, because a context might not have been finalised if an exception occured) + if(mWithinTransform) + { + BOX_WARNING("CipherContext::Begin called when context " + "flagged as within a transform"); + } + + // Initialise the cipher context again + if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + + // Mark as being within a transform + mWithinTransform = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Transform(void *, int, const void *, int) +// Purpose: Transforms the data in the in buffer to the out buffer. If pInBuffer == 0 && InLength == 0 +// then Final() is called instead. +// Returns the number of bytes placed in the out buffer. +// There must be room in the out buffer for all the data in the in buffer. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + if(!mWithinTransform) + { + THROW_EXCEPTION(CipherException, BeginNotCalled) + } + + // Check parameters + if(pOutBuffer == 0 || OutLength < 0 || (pInBuffer != 0 && InLength <= 0) || (pInBuffer == 0 && InLength != 0)) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Is this the final call? + if(pInBuffer == 0) + { + return Final(pOutBuffer, OutLength); + } + + // Check output buffer size + if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + + // Do the transform + int outLength = OutLength; + if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPUpdateFailure) + } + + return outLength; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Final(void *, int) +// Purpose: Transforms the data as per Transform, and returns the final data in the out buffer. +// Returns the number of bytes written in the out buffer. +// Two main causes of exceptions being thrown: 1) Data is corrupt, and so the end isn't +// padded properly. 2) Padding is off, and the data to be encrypted isn't a multiple +// of a block long. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::Final(void *pOutBuffer, int OutLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + if(!mWithinTransform) + { + THROW_EXCEPTION(CipherException, BeginNotCalled) + } + + // Check parameters + if(pOutBuffer == 0 || OutLength < 0) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Check output buffer size + if(OutLength < (2 * EVP_CIPHER_CTX_block_size(&ctx))) + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + + // Do the transform + int outLength = OutLength; +#ifndef HAVE_OLD_SSL + if(EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } +#else + OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength); +#endif + + mWithinTransform = false; + + return outLength; +} + + +#ifdef HAVE_OLD_SSL +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::OldOpenSSLFinal(unsigned char *, int &) +// Purpose: The old version of OpenSSL needs more work doing to finalise the cipher, +// and reset it so that it's ready for another go. +// Created: 27/3/04 +// +// -------------------------------------------------------------------------- +void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut) +{ + // Old version needs to use a different form, and then set up the cipher again for next time around + int outLength = rOutLengthOut; + // Have to emulate padding off... + int blockSize = EVP_CIPHER_CTX_block_size(&ctx); + if(mPaddingOn) + { + // Just use normal final call + if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + } + else + { + // Padding is off. OpenSSL < 0.9.7 doesn't support this, so it has to be + // bodged in there. Which isn't nice. + if(mFunction == Decrypt) + { + // NASTY -- fiddling around with internals like this is bad. + // But only way to get this working on old versions of OpenSSL. + if(!EVP_EncryptUpdate(&ctx,Buffer,&outLength,ctx.buf,0) + || outLength != blockSize) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + } + else + { + // Check that the length is correct + if((ctx.buf_len % blockSize) != 0) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // For encryption, assume that the last block entirely is + // padding, and remove it. + char temp[1024]; + outLength = sizeof(temp); + if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // Remove last block, assuming it's full of padded bytes only. + outLength -= blockSize; + // Copy anything to the main buffer + // (can't just use main buffer, because it might overwrite something important) + if(outLength > 0) + { + ::memcpy(Buffer, temp, outLength); + } + } + } + // Reinitialise the cipher for the next time around + if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL, mFunction) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + mpDescription->SetupParameters(&ctx); + + // Update length for caller + rOutLengthOut = outLength; +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::InSizeForOutBufferSize(int) +// Purpose: Returns the maximum amount of data that can be sent in +// given a output buffer size. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::InSizeForOutBufferSize(int OutLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Strictly speaking, the *2 is unnecessary. However... + // Final() is paranoid, and requires two input blocks of space to work. + return OutLength - (EVP_CIPHER_CTX_block_size(&ctx) * 2); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::MaxOutSizeForInBufferSize(int) +// Purpose: Returns the maximum output size for an input of a given length. +// Will tend to over estimate, as it needs to allow space for Final() to be called. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::MaxOutSizeForInBufferSize(int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Final() is paranoid, and requires two input blocks of space to work, and so we need to add + // three blocks on to be absolutely sure. + return InLength + (EVP_CIPHER_CTX_block_size(&ctx) * 3); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::TransformBlock(void *, int, const void *, int) +// Purpose: Transform one block to another all in one go, no Final required. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::TransformBlock called when " + "context flagged as within a transform"); + } + + // Check output buffer size + if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) + { + // Check if padding is off, in which case the buffer can be smaller + if(!mPaddingOn && OutLength <= InLength) + { + // This is OK. + } + else + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + } + + // Initialise the cipher context again + if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + + // Do the entire block + int outLength = 0; + try + { + // Update + outLength = OutLength; + if(EVP_CipherUpdate(&ctx, (unsigned char*)pOutBuffer, &outLength, (unsigned char*)pInBuffer, InLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPUpdateFailure) + } + // Finalise + int outLength2 = OutLength - outLength; +#ifndef HAVE_OLD_SSL + if(EVP_CipherFinal_ex(&ctx, ((unsigned char*)pOutBuffer) + outLength, &outLength2) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } +#else + OldOpenSSLFinal(((unsigned char*)pOutBuffer) + outLength, outLength2); +#endif + outLength += outLength2; + } + catch(...) + { + // Finalise the context, so definately ready for the next caller + int outs = OutLength; +#ifndef HAVE_OLD_SSL + EVP_CipherFinal_ex(&ctx, (unsigned char*)pOutBuffer, &outs); +#else + OldOpenSSLFinal((unsigned char*)pOutBuffer, outs); +#endif + throw; + } + + return outLength; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::GetIVLength() +// Purpose: Returns the size of the IV for this context +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::GetIVLength() +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + return EVP_CIPHER_CTX_iv_length(&ctx); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::SetIV(const void *) +// Purpose: Sets the IV for this context (must be correctly sized, use GetIVLength) +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::SetIV(const void *pIV) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::SetIV called when context " + "flagged as within a transform"); + } + + // Set IV + if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +#ifdef HAVE_OLD_SSL + // Update description + if(mpDescription != 0) + { + mpDescription->SetIV(pIV); + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::SetRandomIV(int &) +// Purpose: Set a random IV for the context, and return a pointer to the IV used, +// and the length of this IV in the rLengthOut arg. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +const void *CipherContext::SetRandomIV(int &rLengthOut) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::SetRandomIV called when " + "context flagged as within a transform"); + } + + // Get length of IV + unsigned int ivLen = EVP_CIPHER_CTX_iv_length(&ctx); + if(ivLen > sizeof(mGeneratedIV)) + { + THROW_EXCEPTION(CipherException, IVSizeImplementationLimitExceeded) + } + + // Generate some random data + Random::Generate(mGeneratedIV, ivLen); + + // Set IV + if(EVP_CipherInit(&ctx, NULL, NULL, mGeneratedIV, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +#ifdef HAVE_OLD_SSL + // Update description + if(mpDescription != 0) + { + mpDescription->SetIV(mGeneratedIV); + } +#endif + + // Return the IV and it's length + rLengthOut = ivLen; + return mGeneratedIV; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::UsePadding(bool) +// Purpose: Set whether or not the context uses padding. +// Created: 12/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::UsePadding(bool Padding) +{ +#ifndef HAVE_OLD_SSL + if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1) + { + THROW_EXCEPTION(CipherException, EVPSetPaddingFailure) + } +#endif + mPaddingOn = Padding; +} + + + diff --git a/lib/crypto/CipherContext.h b/lib/crypto/CipherContext.h new file mode 100644 index 00000000..64ce52d8 --- /dev/null +++ b/lib/crypto/CipherContext.h @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherContext.h +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERCONTEXT__H +#define CIPHERCONTEXT__H + +#ifdef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE + always include CipherContext.h first in any .cpp file +#endif +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE +#include <openssl/evp.h> +class CipherDescription; + +#define CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH 32 + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherContext +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherContext +{ +public: + CipherContext(); + ~CipherContext(); +private: + CipherContext(const CipherContext &); // no copying + CipherContext &operator=(const CipherContext &); // no assignment +public: + + typedef enum + { + Decrypt = 0, + Encrypt = 1 + } CipherFunction; + + void Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription); + void Reset(); + + void Begin(); + int Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); + int Final(void *pOutBuffer, int OutLength); + int InSizeForOutBufferSize(int OutLength); + int MaxOutSizeForInBufferSize(int InLength); + + int TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); + + bool IsInitialised() {return mInitialised;} + + int GetIVLength(); + void SetIV(const void *pIV); + const void *SetRandomIV(int &rLengthOut); + + void UsePadding(bool Padding = true); + +#ifdef HAVE_OLD_SSL + void OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut); +#endif + +private: + EVP_CIPHER_CTX ctx; + bool mInitialised; + bool mWithinTransform; + bool mPaddingOn; + uint8_t mGeneratedIV[CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH]; +#ifdef HAVE_OLD_SSL + CipherFunction mFunction; + CipherDescription *mpDescription; +#endif +}; + + +#endif // CIPHERCONTEXT__H + diff --git a/lib/crypto/CipherDescription.cpp b/lib/crypto/CipherDescription.cpp new file mode 100644 index 00000000..f0ba6ec8 --- /dev/null +++ b/lib/crypto/CipherDescription.cpp @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherDescription.cpp +// Purpose: Pure virtual base class for describing ciphers +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/evp.h> + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherDescription.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::CipherDescription() +// Purpose: Constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::CipherDescription() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::CipherDescription(const CipherDescription &) +// Purpose: Copy constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::CipherDescription(const CipherDescription &rToCopy) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherDescription::CipherDescription() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::~CipherDescription() +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::operator=(const CipherDescription &) +// Purpose: Assignment operator +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription &CipherDescription::operator=(const CipherDescription &rToCopy) +{ + return *this; +} + + diff --git a/lib/crypto/CipherDescription.h b/lib/crypto/CipherDescription.h new file mode 100644 index 00000000..f825eefa --- /dev/null +++ b/lib/crypto/CipherDescription.h @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherDescription.h +// Purpose: Pure virtual base class for describing ciphers +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERDESCRIPTION__H +#define CIPHERDESCRIPTION__H + +#ifndef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE + class EVP_CIPHER; + class EVP_CIPHER_CTX; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherDescription +// Purpose: Describes a cipher +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherDescription +{ +public: + CipherDescription(); + CipherDescription(const CipherDescription &rToCopy); + virtual ~CipherDescription(); + CipherDescription &operator=(const CipherDescription &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const = 0; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const = 0; + + // Mode parameter for cipher -- used in derived classes + typedef enum + { + Mode_ECB = 0, + Mode_CBC = 1, + Mode_CFB = 2, + Mode_OFB = 3 + } CipherMode; + +#ifdef HAVE_OLD_SSL + // For the old version of OpenSSL, we need to be able to store cipher descriptions. + virtual CipherDescription *Clone() const = 0; + // And to be able to store new IVs + virtual void SetIV(const void *pIV) = 0; +#endif +}; + +#endif // CIPHERDESCRIPTION__H + diff --git a/lib/crypto/CipherException.h b/lib/crypto/CipherException.h new file mode 100644 index 00000000..b0c9f8cd --- /dev/null +++ b/lib/crypto/CipherException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHEREXCEPTION__H +#define CIPHEREXCEPTION__H + +// Compatibility +#include "autogen_CipherException.h" + +#endif // CIPHEREXCEPTION__H + diff --git a/lib/crypto/CipherException.txt b/lib/crypto/CipherException.txt new file mode 100644 index 00000000..abdbac87 --- /dev/null +++ b/lib/crypto/CipherException.txt @@ -0,0 +1,18 @@ +EXCEPTION Cipher 5 + +Internal 0 +UnknownCipherMode 1 +AlreadyInitialised 2 +BadArguments 3 +EVPInitFailure 4 +EVPUpdateFailure 5 +EVPFinalFailure 6 +NotInitialised 7 +OutputBufferTooSmall 8 +EVPBadKeyLength 9 +BeginNotCalled 10 +IVSizeImplementationLimitExceeded 11 +PseudoRandNotAvailable 12 +EVPSetPaddingFailure 13 +RandomInitFailed 14 Failed to read from random device +LengthRequestedTooLongForRandomHex 15 diff --git a/lib/crypto/MD5Digest.cpp b/lib/crypto/MD5Digest.cpp new file mode 100644 index 00000000..58cc90ee --- /dev/null +++ b/lib/crypto/MD5Digest.cpp @@ -0,0 +1,82 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MD5Digest.cpp +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include "MD5Digest.h" + +#include "MemLeakFindOn.h" + + +MD5Digest::MD5Digest() +{ + MD5_Init(&md5); + for(unsigned int l = 0; l < sizeof(mDigest); ++l) + { + mDigest[l] = 0; + } +} + +MD5Digest::~MD5Digest() +{ +} + +void MD5Digest::Add(const std::string &rString) +{ + MD5_Update(&md5, rString.c_str(), rString.size()); +} + +void MD5Digest::Add(const void *pData, int Length) +{ + MD5_Update(&md5, pData, Length); +} + +void MD5Digest::Finish() +{ + MD5_Final(mDigest, &md5); +} + +std::string MD5Digest::DigestAsString() +{ + std::string r; + + static const char *hex = "0123456789abcdef"; + + for(unsigned int l = 0; l < sizeof(mDigest); ++l) + { + r += hex[(mDigest[l] & 0xf0) >> 4]; + r += hex[(mDigest[l] & 0x0f)]; + } + + return r; +} + +int MD5Digest::CopyDigestTo(uint8_t *to) +{ + for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) + { + to[l] = mDigest[l]; + } + + return MD5_DIGEST_LENGTH; +} + + +bool MD5Digest::DigestMatches(uint8_t *pCompareWith) const +{ + for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) + { + if(pCompareWith[l] != mDigest[l]) + return false; + } + + return true; +} + diff --git a/lib/crypto/MD5Digest.h b/lib/crypto/MD5Digest.h new file mode 100644 index 00000000..1be01ea9 --- /dev/null +++ b/lib/crypto/MD5Digest.h @@ -0,0 +1,57 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MD5Digest.h +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef MD5DIGEST_H +#define MD5DIGEST_H + +#include <openssl/md5.h> +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MD5Digest +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- +class MD5Digest +{ +public: + MD5Digest(); + virtual ~MD5Digest(); + + void Add(const std::string &rString); + void Add(const void *pData, int Length); + + void Finish(); + + std::string DigestAsString(); + uint8_t *DigestAsData(int *pLength = 0) + { + if(pLength) *pLength = sizeof(mDigest); + return mDigest; + } + + enum + { + DigestLength = MD5_DIGEST_LENGTH + }; + + int CopyDigestTo(uint8_t *to); + + bool DigestMatches(uint8_t *pCompareWith) const; + +private: + MD5_CTX md5; + uint8_t mDigest[MD5_DIGEST_LENGTH]; +}; + +#endif // MD5DIGEST_H + diff --git a/lib/crypto/Makefile.extra b/lib/crypto/Makefile.extra new file mode 100644 index 00000000..3c94389f --- /dev/null +++ b/lib/crypto/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CipherException.cpp autogen_CipherException.h: $(MAKEEXCEPTION) CipherException.txt + $(_PERL) $(MAKEEXCEPTION) CipherException.txt + diff --git a/lib/crypto/Random.cpp b/lib/crypto/Random.cpp new file mode 100644 index 00000000..1d6a07f0 --- /dev/null +++ b/lib/crypto/Random.cpp @@ -0,0 +1,128 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Random.cpp +// Purpose: Random numbers +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/rand.h> +#include <stdio.h> + +#include "Random.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::Initialise() +// Purpose: Add additional randomness to the standard library initialisation +// Created: 18/6/04 +// +// -------------------------------------------------------------------------- +void Random::Initialise() +{ +#ifdef HAVE_RANDOM_DEVICE + if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) + { + THROW_EXCEPTION(CipherException, RandomInitFailed) + } +#else + BOX_ERROR("No random device -- additional seeding of random number " + "generator not performed."); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::Generate(void *, int) +// Purpose: Generate Length bytes of random data +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- +void Random::Generate(void *pOutput, int Length) +{ + if(RAND_pseudo_bytes((uint8_t*)pOutput, Length) == -1) + { + THROW_EXCEPTION(CipherException, PseudoRandNotAvailable) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::GenerateHex(int) +// Purpose: Generate Length bytes of hex encoded data. Note that the +// maximum length requested is limited. (Returns a string +// 2 x Length characters long.) +// Created: 1/11/04 +// +// -------------------------------------------------------------------------- +std::string Random::GenerateHex(int Length) +{ + uint8_t r[256]; + if(Length > (int)sizeof(r)) + { + THROW_EXCEPTION(CipherException, LengthRequestedTooLongForRandomHex) + } + Random::Generate(r, Length); + + std::string o; + static const char *h = "0123456789abcdef"; + for(int l = 0; l < Length; ++l) + { + o += h[r[l] >> 4]; + o += h[r[l] & 0xf]; + } + + return o; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::RandomInt(int) +// Purpose: Return a random integer between 0 and MaxValue inclusive. +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +uint32_t Random::RandomInt(uint32_t MaxValue) +{ + uint32_t v = 0; + + // Generate a mask + uint32_t mask = 0; + while(mask < MaxValue) + { + mask = (mask << 1) | 1; + } + + do + { + // Generate a random number + uint32_t r = 0; + Random::Generate(&r, sizeof(r)); + + // Mask off relevant bits + v = r & mask; + + // Check that it's in the right range. + } while(v > MaxValue); + + // NOTE: don't do a mod, because this doesn't give a correct random distribution + + return v; +} + + + diff --git a/lib/crypto/Random.h b/lib/crypto/Random.h new file mode 100644 index 00000000..da3e4335 --- /dev/null +++ b/lib/crypto/Random.h @@ -0,0 +1,25 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Random.h +// Purpose: Random numbers +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef RANDOM__H +#define RANDOM__H + +#include <string> + +namespace Random +{ + void Initialise(); + void Generate(void *pOutput, int Length); + std::string GenerateHex(int Length); + uint32_t RandomInt(uint32_t MaxValue); +}; + + +#endif // RANDOM__H + diff --git a/lib/crypto/RollingChecksum.cpp b/lib/crypto/RollingChecksum.cpp new file mode 100644 index 00000000..a2954e3a --- /dev/null +++ b/lib/crypto/RollingChecksum.cpp @@ -0,0 +1,62 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RollingChecksum.cpp +// Purpose: A simple rolling checksum over a block of data +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "RollingChecksum.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: RollingChecksum::RollingChecksum(const void *, unsigned int) +// Purpose: Constructor -- does initial computation of the checksum. +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- +RollingChecksum::RollingChecksum(const void * const data, const unsigned int Length) + : a(0), + b(0) +{ + const uint8_t *block = (const uint8_t *)data; + for(unsigned int x = Length; x >= 1; --x) + { + a += (*block); + b += x * (*block); + + ++block; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) +// Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, +// and a pointer just after the last byte of the current block and the length of the block and of the skip. +// Created: 7/14/05 +// +// -------------------------------------------------------------------------- +void RollingChecksum::RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip) +{ + // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. + unsigned int i; + uint16_t sumBegin=0, j,k; + + for(i=0; i < Skip; i++) + { + j = StartOfThisBlock[i]; + k = LastOfNextBlock[i]; + sumBegin += j; + a += (k - j); + b += a; + } + + b -= Length * sumBegin; +} diff --git a/lib/crypto/RollingChecksum.h b/lib/crypto/RollingChecksum.h new file mode 100644 index 00000000..be79c36f --- /dev/null +++ b/lib/crypto/RollingChecksum.h @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RollingChecksum.h +// Purpose: A simple rolling checksum over a block of data +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef ROLLINGCHECKSUM__H +#define ROLLINGCHECKSUM__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: RollingChecksum +// Purpose: A simple rolling checksum over a block of data -- can move the block +// "forwards" in memory and get the next checksum efficiently. +// +// Implementation of http://rsync.samba.org/tech_report/node3.html +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- +class RollingChecksum +{ +public: + RollingChecksum(const void * const data, const unsigned int Length); + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::RollForward(uint8_t, uint8_t, unsigned int) + // Purpose: Move the checksum forward a block, given the first byte of the current block, + // last byte of the next block (it's rolling forward to) and the length of the block. + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline void RollForward(const uint8_t StartOfThisBlock, const uint8_t LastOfNextBlock, const unsigned int Length) + { + // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. + a -= StartOfThisBlock; + a += LastOfNextBlock; + b -= Length * StartOfThisBlock; + b += a; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) + // Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, + // and a pointer just after the last byte of the current block and the length of the block and of the skip. + // Created: 7/14/05 + // + // -------------------------------------------------------------------------- + void RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip); + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::GetChecksum() + // Purpose: Returns the checksum + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline uint32_t GetChecksum() const + { + return ((uint32_t)a) | (((uint32_t)b) << 16); + } + + // Components, just in case they're handy + inline uint16_t GetComponent1() const {return a;} + inline uint16_t GetComponent2() const {return b;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::GetComponentForHashing() + // Purpose: Return the 16 bit component used for hashing and/or quick checks + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline uint16_t GetComponentForHashing() const + { + return b; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::ExtractHashingComponent(uint32_t) + // Purpose: Static. Given a full checksum, extract the component used in the hashing table. + // Created: 14/1/04 + // + // -------------------------------------------------------------------------- + static inline uint16_t ExtractHashingComponent(const uint32_t Checksum) + { + return Checksum >> 16; + } + +private: + uint16_t a; + uint16_t b; +}; + +#endif // ROLLINGCHECKSUM__H + diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt new file mode 100644 index 00000000..52630cda --- /dev/null +++ b/lib/httpserver/HTTPException.txt @@ -0,0 +1,16 @@ +EXCEPTION HTTP 10 + +Internal 0 +RequestReadFailed 1 +RequestAlreadyBeenRead 2 +BadRequest 3 +UnknownResponseCodeUsed 4 +NoContentTypeSet 5 +POSTContentTooLong 6 +CannotSetRedirectIfReponseHasData 7 +CannotSetNotFoundIfReponseHasData 8 +NotImplemented 9 +RequestNotInitialised 10 +BadResponse 11 +ResponseReadFailed 12 +NoStreamConfigured 13 diff --git a/lib/httpserver/HTTPQueryDecoder.cpp b/lib/httpserver/HTTPQueryDecoder.cpp new file mode 100644 index 00000000..c49ac2ce --- /dev/null +++ b/lib/httpserver/HTTPQueryDecoder.cpp @@ -0,0 +1,159 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPQueryDecoder.cpp +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> + +#include "HTTPQueryDecoder.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::HTTPQueryDecoder( +// HTTPRequest::Query_t &) +// Purpose: Constructor. Pass in the query contents you want +// to decode the query string into. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto) + : mrDecodeInto(rDecodeInto), + mInKey(true), + mEscapedState(0) +{ + // Insert the terminator for escaped characters + mEscaped[2] = '\0'; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::~HTTPQueryDecoder() +// Purpose: Destructor. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPQueryDecoder::~HTTPQueryDecoder() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::Decode(const char *, int) +// Purpose: Decode a chunk of query string -- call several times with +// the bits as they are received, and then call Finish() +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPQueryDecoder::DecodeChunk(const char *pQueryString, int QueryStringSize) +{ + for(int l = 0; l < QueryStringSize; ++l) + { + char c = pQueryString[l]; + + // BEFORE unescaping, check to see if we need to flip key / value + if(mEscapedState == 0) + { + if(mInKey && c == '=') + { + // Set to store characters in the value + mInKey = false; + continue; + } + else if(!mInKey && c == '&') + { + // Need to store the current key/value pair + mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); + // Blank the strings + mCurrentKey.erase(); + mCurrentValue.erase(); + + // Set to store characters in the key + mInKey = true; + continue; + } + } + + // Decode an escaped value? + if(mEscapedState == 1) + { + // Waiting for char one of the escaped hex value + mEscaped[0] = c; + mEscapedState = 2; + continue; + } + else if(mEscapedState == 2) + { + // Escaped value, decode it + mEscaped[1] = c; // str terminated in constructor + mEscapedState = 0; // stop being in escaped mode + long ch = ::strtol(mEscaped, NULL, 16); + if(ch <= 0 || ch > 255) + { + // Bad character, just ignore + continue; + } + + // Use this instead + c = (char)ch; + } + else if(c == '+') + { + c = ' '; + } + else if(c == '%') + { + mEscapedState = 1; + continue; + } + + // Store decoded value into the appropriate string + if(mInKey) + { + mCurrentKey += c; + } + else + { + mCurrentValue += c; + } + } + + // Don't do anything here with left over values, DecodeChunk might be called + // again. Let Finish() clean up. +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::Finish() +// Purpose: Finish the decoding. Necessary to get the last item! +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPQueryDecoder::Finish() +{ + // Insert any remaining value. + if(!mCurrentKey.empty()) + { + mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); + // Blank values, just in case + mCurrentKey.erase(); + mCurrentValue.erase(); + } +} + + diff --git a/lib/httpserver/HTTPQueryDecoder.h b/lib/httpserver/HTTPQueryDecoder.h new file mode 100644 index 00000000..ca5afe7e --- /dev/null +++ b/lib/httpserver/HTTPQueryDecoder.h @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPQueryDecoder.h +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPQUERYDECODER__H +#define HTTPQUERYDECODER__H + +#include "HTTPRequest.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPQueryDecoder +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPQueryDecoder +{ +public: + HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto); + ~HTTPQueryDecoder(); +private: + // no copying + HTTPQueryDecoder(const HTTPQueryDecoder &); + HTTPQueryDecoder &operator=(const HTTPQueryDecoder &); +public: + + void DecodeChunk(const char *pQueryString, int QueryStringSize); + void Finish(); + +private: + HTTPRequest::Query_t &mrDecodeInto; + std::string mCurrentKey; + std::string mCurrentValue; + bool mInKey; + char mEscaped[4]; + int mEscapedState; +}; + +#endif // HTTPQUERYDECODER__H + diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp new file mode 100644 index 00000000..4c5dc149 --- /dev/null +++ b/lib/httpserver/HTTPRequest.cpp @@ -0,0 +1,780 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPRequest.cpp +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> +#include <strings.h> +#include <stdlib.h> +#include <stdio.h> + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPQueryDecoder.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "IOStreamGetLine.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#define MAX_CONTENT_SIZE (128*1024) + +#define ENSURE_COOKIE_JAR_ALLOCATED \ + if(mpCookies == 0) {mpCookies = new CookieJar_t;} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::HTTPRequest() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPRequest::HTTPRequest() + : mMethod(Method_UNINITIALISED), + mHostPort(80), // default if not specified + mHTTPVersion(0), + mContentLength(-1), + mpCookies(0), + mClientKeepAliveRequested(false), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::HTTPRequest(enum Method, +// const std::string&) +// Purpose: Alternate constructor for hand-crafted requests +// Created: 03/01/09 +// +// -------------------------------------------------------------------------- +HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI) + : mMethod(method), + mRequestURI(rURI), + mHostPort(80), // default if not specified + mHTTPVersion(HTTPVersion_1_1), + mContentLength(-1), + mpCookies(0), + mClientKeepAliveRequested(false), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::~HTTPRequest() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPRequest::~HTTPRequest() +{ + // Clean up any cookies + if(mpCookies != 0) + { + delete mpCookies; + mpCookies = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::Receive(IOStreamGetLine &, int) +// Purpose: Read the request from an IOStreamGetLine (and +// attached stream). +// Returns false if there was no valid request, +// probably due to a kept-alive connection closing. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout) +{ + // Check caller's logic + if(mMethod != Method_UNINITIALISED) + { + THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead) + } + + // Read the first line, which is of a different format to the rest of the lines + std::string requestLine; + if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout)) + { + // Didn't get the request line, probably end of connection which had been kept alive + return false; + } + BOX_TRACE("Request line: " << requestLine); + + // Check the method + size_t p = 0; // current position in string + p = requestLine.find(' '); // end of first word + + if (p == std::string::npos) + { + // No terminating space, looks bad + p = requestLine.size(); + } + else + { + mHttpVerb = requestLine.substr(0, p); + if (mHttpVerb == "GET") + { + mMethod = Method_GET; + } + else if (mHttpVerb == "HEAD") + { + mMethod = Method_HEAD; + } + else if (mHttpVerb == "POST") + { + mMethod = Method_POST; + } + else if (mHttpVerb == "PUT") + { + mMethod = Method_PUT; + } + else + { + mMethod = Method_UNKNOWN; + } + } + + // Skip spaces to find URI + const char *requestLinePtr = requestLine.c_str(); + while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') + { + ++p; + } + + // Check there's a URI following... + if(requestLinePtr[p] == '\0') + { + // Didn't get the request line, probably end of connection which had been kept alive + return false; + } + + // Read the URI, unescaping any %XX hex codes + while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') + { + // End of URI, on to query string? + if(requestLinePtr[p] == '?') + { + // Put the rest into the query string, without escaping anything + ++p; + while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') + { + mQueryString += requestLinePtr[p]; + ++p; + } + break; + } + // Needs unescaping? + else if(requestLinePtr[p] == '+') + { + mRequestURI += ' '; + } + else if(requestLinePtr[p] == '%') + { + // Be tolerant about this... bad things are silently accepted, + // rather than throwing an error. + char code[4] = {0,0,0,0}; + code[0] = requestLinePtr[++p]; + if(code[0] != '\0') + { + code[1] = requestLinePtr[++p]; + } + + // Convert into a char code + long c = ::strtol(code, NULL, 16); + + // Accept it? + if(c > 0 && c <= 255) + { + mRequestURI += (char)c; + } + } + else + { + // Simple copy of character + mRequestURI += requestLinePtr[p]; + } + + ++p; + } + + // End of URL? + if(requestLinePtr[p] == '\0') + { + // Assume HTTP 0.9 + mHTTPVersion = HTTPVersion_0_9; + } + else + { + // Skip any more spaces + while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') + { + ++p; + } + + // Check to see if there's the right string next... + if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0) + { + // Find the version numbers + int major, minor; + if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2) + { + THROW_EXCEPTION(HTTPException, BadRequest) + } + + // Store version + mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor; + } + else + { + // Not good -- wrong string found + THROW_EXCEPTION(HTTPException, BadRequest) + } + } + + BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" << + mRequestURI << ", version=" << mHTTPVersion); + + // If HTTP 1.1 or greater, assume keep-alive + if(mHTTPVersion >= HTTPVersion_1_1) + { + mClientKeepAliveRequested = true; + } + + // Decode query string? + if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty()) + { + HTTPQueryDecoder decoder(mQuery); + decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size()); + decoder.Finish(); + } + + // Now parse the headers + ParseHeaders(rGetLine, Timeout); + + std::string expected; + if (GetHeader("Expect", &expected)) + { + if (expected == "100-continue") + { + mExpectContinue = true; + } + } + + // Parse form data? + if(mMethod == Method_POST && mContentLength >= 0) + { + // Too long? Don't allow people to be nasty by sending lots of data + if(mContentLength > MAX_CONTENT_SIZE) + { + THROW_EXCEPTION(HTTPException, POSTContentTooLong) + } + + // Some data in the request to follow, parsing it bit by bit + HTTPQueryDecoder decoder(mQuery); + // Don't forget any data left in the GetLine object + int fromBuffer = rGetLine.GetSizeOfBufferedData(); + if(fromBuffer > mContentLength) fromBuffer = mContentLength; + if(fromBuffer > 0) + { + BOX_TRACE("Decoding " << fromBuffer << " bytes of " + "data from getline buffer"); + decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer); + // And tell the getline object to ignore the data we just used + rGetLine.IgnoreBufferedData(fromBuffer); + } + // Then read any more data, as required + int bytesToGo = mContentLength - fromBuffer; + while(bytesToGo > 0) + { + char buf[4096]; + int toRead = sizeof(buf); + if(toRead > bytesToGo) toRead = bytesToGo; + IOStream &rstream(rGetLine.GetUnderlyingStream()); + int r = rstream.Read(buf, toRead, Timeout); + if(r == 0) + { + // Timeout, just error + THROW_EXCEPTION(HTTPException, RequestReadFailed) + } + decoder.DecodeChunk(buf, r); + bytesToGo -= r; + } + // Finish off + decoder.Finish(); + } + else if (mContentLength > 0) + { + IOStream::pos_type bytesToCopy = rGetLine.GetSizeOfBufferedData(); + if (bytesToCopy > mContentLength) + { + bytesToCopy = mContentLength; + } + Write(rGetLine.GetBufferedData(), bytesToCopy); + SetForReading(); + mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream()); + } + + return true; +} + +void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo) +{ + Seek(0, SeekType_Absolute); + + CopyStreamTo(rStreamToWriteTo); + IOStream::pos_type bytesCopied = GetSize(); + + while (bytesCopied < mContentLength) + { + char buffer[1024]; + IOStream::pos_type bytesToCopy = sizeof(buffer); + if (bytesToCopy > mContentLength - bytesCopied) + { + bytesToCopy = mContentLength - bytesCopied; + } + bytesToCopy = mpStreamToReadFrom->Read(buffer, bytesToCopy); + rStreamToWriteTo.Write(buffer, bytesToCopy); + bytesCopied += bytesToCopy; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::Send(IOStream &, int) +// Purpose: Write the request to an IOStream using HTTP. +// Created: 03/01/09 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue) +{ + switch (mMethod) + { + case Method_UNINITIALISED: + THROW_EXCEPTION(HTTPException, RequestNotInitialised); break; + case Method_UNKNOWN: + THROW_EXCEPTION(HTTPException, BadRequest); break; + case Method_GET: + rStream.Write("GET"); break; + case Method_HEAD: + rStream.Write("HEAD"); break; + case Method_POST: + rStream.Write("POST"); break; + case Method_PUT: + rStream.Write("PUT"); break; + } + + rStream.Write(" "); + rStream.Write(mRequestURI.c_str()); + rStream.Write(" "); + + switch (mHTTPVersion) + { + case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break; + case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break; + case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break; + default: + THROW_EXCEPTION(HTTPException, NotImplemented); + } + + rStream.Write("\n"); + std::ostringstream oss; + + if (mContentLength != -1) + { + oss << "Content-Length: " << mContentLength << "\n"; + } + + if (mContentType != "") + { + oss << "Content-Type: " << mContentType << "\n"; + } + + if (mHostName != "") + { + if (mHostPort != 80) + { + oss << "Host: " << mHostName << ":" << mHostPort << + "\n"; + } + else + { + oss << "Host: " << mHostName << "\n"; + } + } + + if (mpCookies) + { + THROW_EXCEPTION(HTTPException, NotImplemented); + } + + if (mClientKeepAliveRequested) + { + oss << "Connection: keep-alive\n"; + } + else + { + oss << "Connection: close\n"; + } + + for (std::vector<Header>::iterator i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + oss << i->first << ": " << i->second << "\n"; + } + + if (ExpectContinue) + { + oss << "Expect: 100-continue\n"; + } + + rStream.Write(oss.str().c_str()); + rStream.Write("\n"); + + return true; +} + +void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse) +{ + IOStream::pos_type size = pStreamToSend->BytesLeftToRead(); + + if (size != IOStream::SizeOfStreamUnknown) + { + mContentLength = size; + } + + Send(rStreamToSendTo, Timeout, true); + + rResponse.Receive(rStreamToSendTo, Timeout); + if (rResponse.GetResponseCode() != 100) + { + // bad response, abort now + return; + } + + pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout); + + // receive the final response + rResponse.Receive(rStreamToSendTo, Timeout); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::ParseHeaders(IOStreamGetLine &, int) +// Purpose: Private. Parse the headers of the request +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) +{ + std::string header; + bool haveHeader = false; + while(true) + { + if(rGetLine.IsEOF()) + { + // Header terminates unexpectedly + THROW_EXCEPTION(HTTPException, BadRequest) + } + + std::string currentLine; + if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION(HTTPException, RequestReadFailed) + } + + // Is this a continuation of the previous line? + bool processHeader = haveHeader; + if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) + { + // A continuation, don't process anything yet + processHeader = false; + } + //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); + + // Parse the header -- this will actually process the header + // from the previous run around the loop. + if(processHeader) + { + // Find where the : is in the line + const char *h = header.c_str(); + int p = 0; + while(h[p] != '\0' && h[p] != ':') + { + ++p; + } + // Skip white space + int dataStart = p + 1; + while(h[dataStart] == ' ' || h[dataStart] == '\t') + { + ++dataStart; + } + + std::string header_name(ToLowerCase(std::string(h, + p))); + + if (header_name == "content-length") + { + // Decode number + long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK + if(len < 0) len = 0; + // Store + mContentLength = len; + } + else if (header_name == "content-type") + { + // Store rest of string as content type + mContentType = h + dataStart; + } + else if (header_name == "host") + { + // Store host header + mHostName = h + dataStart; + + // Is there a port number to split off? + std::string::size_type colon = mHostName.find_first_of(':'); + if(colon != std::string::npos) + { + // There's a port in the string... attempt to turn it into an int + mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10); + + // Truncate the string to just the hostname + mHostName = mHostName.substr(0, colon); + + BOX_TRACE("Host: header, hostname = " << + "'" << mHostName << "', host " + "port = " << mHostPort); + } + } + else if (header_name == "cookie") + { + // Parse cookies + ParseCookies(header, dataStart); + } + else if (header_name == "connection") + { + // Connection header, what is required? + const char *v = h + dataStart; + if(::strcasecmp(v, "close") == 0) + { + mClientKeepAliveRequested = false; + } + else if(::strcasecmp(v, "keep-alive") == 0) + { + mClientKeepAliveRequested = true; + } + // else don't understand, just assume default for protocol version + } + else + { + mExtraHeaders.push_back(Header(header_name, + h + dataStart)); + } + + // Unset have header flag, as it's now been processed + haveHeader = false; + } + + // Store the chunk of header the for next time round + if(haveHeader) + { + header += currentLine; + } + else + { + header = currentLine; + haveHeader = true; + } + + // End of headers? + if(currentLine.empty()) + { + // All done! + break; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::ParseCookies(const std::string &, int) +// Purpose: Parse the cookie header +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts) +{ + const char *data = rHeader.c_str() + DataStarts; + const char *pos = data; + const char *itemStart = pos; + std::string name; + + enum + { + s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME + } state = s_NAME; + + do + { + switch(state) + { + case s_NAME: + { + if(*pos == '=') + { + // Found the name. Store + name.assign(itemStart, pos - itemStart); + // Looking at values now + state = s_VALUE; + if((*(pos + 1)) == '"') + { + // Actually it's a quoted value, skip over that + ++pos; + state = s_VALUE_QUOTED; + } + // Record starting point for this item + itemStart = pos + 1; + } + } + break; + + case s_VALUE: + { + if(*pos == ';' || *pos == ',' || *pos == '\0') + { + // Name ends + ENSURE_COOKIE_JAR_ALLOCATED + std::string value(itemStart, pos - itemStart); + (*mpCookies)[name] = value; + // And move to the waiting stage + state = s_FIND_NEXT_NAME; + } + } + break; + + case s_VALUE_QUOTED: + { + if(*pos == '"') + { + // That'll do nicely, save it + ENSURE_COOKIE_JAR_ALLOCATED + std::string value(itemStart, pos - itemStart); + (*mpCookies)[name] = value; + // And move to the waiting stage + state = s_FIND_NEXT_NAME; + } + } + break; + + case s_FIND_NEXT_NAME: + { + // Skip over terminators and white space to get to the next name + if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t') + { + // Name starts here + itemStart = pos; + state = s_NAME; + } + } + break; + + default: + // Ooops + THROW_EXCEPTION(HTTPException, Internal) + break; + } + } + while(*(pos++) != 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::GetCookie(const char *, std::string &) const +// Purpose: Fetch a cookie's value. If cookie not present, returns false +// and string is unaltered. +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const +{ + // Got any cookies? + if(mpCookies == 0) + { + return false; + } + + // See if it's there + CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); + if(v != mpCookies->end()) + { + // Return the value + rValueOut = v->second; + return true; + } + + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::GetCookie(const char *) +// Purpose: Return a string for the given cookie, or the null string if the +// cookie has not been recieved. +// Created: 22/8/04 +// +// -------------------------------------------------------------------------- +const std::string &HTTPRequest::GetCookie(const char *CookieName) const +{ + static const std::string noCookie; + + // Got any cookies? + if(mpCookies == 0) + { + return noCookie; + } + + // See if it's there + CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); + if(v != mpCookies->end()) + { + // Return the value + return v->second; + } + + return noCookie; +} + + + diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h new file mode 100644 index 00000000..25effb70 --- /dev/null +++ b/lib/httpserver/HTTPRequest.h @@ -0,0 +1,189 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPRequest.h +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPREQUEST__H +#define HTTPREQUEST__H + +#include <string> +#include <map> + +#include "CollectInBufferStream.h" + +class HTTPResponse; +class IOStream; +class IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPRequest +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPRequest : public CollectInBufferStream +{ +public: + enum Method + { + Method_UNINITIALISED = -1, + Method_UNKNOWN = 0, + Method_GET = 1, + Method_HEAD = 2, + Method_POST = 3, + Method_PUT = 4 + }; + + HTTPRequest(); + HTTPRequest(enum Method method, const std::string& rURI); + ~HTTPRequest(); +private: + // no copying + HTTPRequest(const HTTPRequest &); + HTTPRequest &operator=(const HTTPRequest &); +public: + typedef std::multimap<std::string, std::string> Query_t; + typedef Query_t::value_type QueryEn_t; + typedef std::pair<std::string, std::string> Header; + + enum + { + HTTPVersion__MajorMultiplier = 1000, + HTTPVersion_0_9 = 9, + HTTPVersion_1_0 = 1000, + HTTPVersion_1_1 = 1001 + }; + + bool Receive(IOStreamGetLine &rGetLine, int Timeout); + bool Send(IOStream &rStream, int Timeout, bool ExpectContinue = false); + void SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse); + void ReadContent(IOStream& rStreamToWriteTo); + + typedef std::map<std::string, std::string> CookieJar_t; + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::Get*() + // Purpose: Various Get accessors + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + enum Method GetMethod() const {return mMethod;} + const std::string &GetRequestURI() const {return mRequestURI;} + + // Note: the HTTPRequest generates and parses the Host: header + // Do not attempt to set one yourself with AddHeader(). + const std::string &GetHostName() const {return mHostName;} + void SetHostName(const std::string& rHostName) + { + mHostName = rHostName; + } + + const int GetHostPort() const {return mHostPort;} + const std::string &GetQueryString() const {return mQueryString;} + int GetHTTPVersion() const {return mHTTPVersion;} + const Query_t &GetQuery() const {return mQuery;} + int GetContentLength() const {return mContentLength;} + const std::string &GetContentType() const {return mContentType;} + const CookieJar_t *GetCookies() const {return mpCookies;} // WARNING: May return NULL + bool GetCookie(const char *CookieName, std::string &rValueOut) const; + const std::string &GetCookie(const char *CookieName) const; + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + std::string header = ToLowerCase(rName); + + for (std::vector<Header>::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == header) + { + *pValueOut = i->second; + return true; + } + } + + return false; + } + std::vector<Header> GetHeaders() { return mExtraHeaders; } + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPRequest::GetClientKeepAliveRequested() + // Purpose: Returns true if the client requested that the connection + // should be kept open for further requests. + // Created: 22/12/04 + // + // -------------------------------------------------------------------------- + bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;} + void SetClientKeepAliveRequested(bool keepAlive) + { + mClientKeepAliveRequested = keepAlive; + } + + void AddHeader(const std::string& rName, const std::string& rValue) + { + mExtraHeaders.push_back(Header(ToLowerCase(rName), rValue)); + } + bool IsExpectingContinue() const { return mExpectContinue; } + const char* GetVerb() const + { + if (!mHttpVerb.empty()) + { + return mHttpVerb.c_str(); + } + switch (mMethod) + { + case Method_UNINITIALISED: return "Uninitialized"; + case Method_UNKNOWN: return "Unknown"; + case Method_GET: return "GET"; + case Method_HEAD: return "HEAD"; + case Method_POST: return "POST"; + case Method_PUT: return "PUT"; + } + return "Bad"; + } + +private: + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); + void ParseCookies(const std::string &rHeader, int DataStarts); + + enum Method mMethod; + std::string mRequestURI; + std::string mHostName; + int mHostPort; + std::string mQueryString; + int mHTTPVersion; + Query_t mQuery; + int mContentLength; + std::string mContentType; + CookieJar_t *mpCookies; + bool mClientKeepAliveRequested; + std::vector<Header> mExtraHeaders; + bool mExpectContinue; + IOStream* mpStreamToReadFrom; + std::string mHttpVerb; + + std::string ToLowerCase(const std::string& rInput) const + { + std::string output = rInput; + for (std::string::iterator c = output.begin(); + c != output.end(); c++) + { + *c = tolower(*c); + } + return output; + } +}; + +#endif // HTTPREQUEST__H + diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp new file mode 100644 index 00000000..1a8c8447 --- /dev/null +++ b/lib/httpserver/HTTPResponse.cpp @@ -0,0 +1,648 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPResponse.cpp +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.h> + +#include "HTTPResponse.h" +#include "IOStreamGetLine.h" +#include "autogen_HTTPException.h" + +#include "MemLeakFindOn.h" + +// Static variables +std::string HTTPResponse::msDefaultURIPrefix; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse(IOStream*) +// Purpose: Constructor for response to be sent to a stream +// Created: 04/01/09 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo) + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(pStreamToSendTo) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse() + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::~HTTPResponse() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPResponse::~HTTPResponse() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::ResponseCodeToString(int) +// Purpose: Return string equivalent of the response code, +// suitable for Status: headers +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const char *HTTPResponse::ResponseCodeToString(int ResponseCode) +{ + switch(ResponseCode) + { + case Code_OK: return "200 OK"; break; + case Code_NoContent: return "204 No Content"; break; + case Code_MovedPermanently: return "301 Moved Permanently"; break; + case Code_Found: return "302 Found"; break; + case Code_NotModified: return "304 Not Modified"; break; + case Code_TemporaryRedirect: return "307 Temporary Redirect"; break; + case Code_MethodNotAllowed: return "400 Method Not Allowed"; break; + case Code_Unauthorized: return "401 Unauthorized"; break; + case Code_Forbidden: return "403 Forbidden"; break; + case Code_NotFound: return "404 Not Found"; break; + case Code_InternalServerError: return "500 Internal Server Error"; break; + case Code_NotImplemented: return "501 Not Implemented"; break; + default: + { + THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed) + } + } + return "500 Internal Server Error"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetResponseCode(int) +// Purpose: Set the response code to be returned +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetResponseCode(int Code) +{ + mResponseCode = Code; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetContentType(const char *) +// Purpose: Set content type +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetContentType(const char *ContentType) +{ + mContentType = ContentType; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::Send(IOStream &, bool) +// Purpose: Build the response, and send via the stream. +// Optionally omitting the content. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::Send(bool OmitContent) +{ + if (!mpStreamToSendTo) + { + THROW_EXCEPTION(HTTPException, NoStreamConfigured); + } + + if (GetSize() != 0 && mContentType.empty()) + { + THROW_EXCEPTION(HTTPException, NoContentTypeSet); + } + + // Build and send header + { + std::string header("HTTP/1.1 "); + header += ResponseCodeToString(mResponseCode); + header += "\r\nContent-Type: "; + header += mContentType; + header += "\r\nContent-Length: "; + { + char len[32]; + ::sprintf(len, "%d", OmitContent?(0):(GetSize())); + header += len; + } + // Extra headers... + for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i) + { + header += "\r\n"; + header += i->first + ": " + i->second; + } + // NOTE: a line ending must be included here in all cases + // Control whether the response is cached + if(mResponseIsDynamicContent) + { + // dynamic is private and can't be cached + header += "\r\nCache-Control: no-cache, private"; + } + else + { + // static is allowed to be cached for a day + header += "\r\nCache-Control: max-age=86400"; + } + if(mKeepAlive) + { + header += "\r\nConnection: keep-alive\r\n\r\n"; + } + else + { + header += "\r\nConnection: close\r\n\r\n"; + } + // NOTE: header ends with blank line in all cases + + // Write to stream + mpStreamToSendTo->Write(header.c_str(), header.size()); + } + + // Send content + if(!OmitContent) + { + mpStreamToSendTo->Write(GetBuffer(), GetSize()); + } +} + +void HTTPResponse::SendContinue() +{ + mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n"); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int) +// Purpose: Private. Parse the headers of the response +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) +{ + std::string header; + bool haveHeader = false; + while(true) + { + if(rGetLine.IsEOF()) + { + // Header terminates unexpectedly + THROW_EXCEPTION(HTTPException, BadRequest) + } + + std::string currentLine; + if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION(HTTPException, RequestReadFailed) + } + + // Is this a continuation of the previous line? + bool processHeader = haveHeader; + if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) + { + // A continuation, don't process anything yet + processHeader = false; + } + //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); + + // Parse the header -- this will actually process the header + // from the previous run around the loop. + if(processHeader) + { + // Find where the : is in the line + const char *h = header.c_str(); + int p = 0; + while(h[p] != '\0' && h[p] != ':') + { + ++p; + } + // Skip white space + int dataStart = p + 1; + while(h[dataStart] == ' ' || h[dataStart] == '\t') + { + ++dataStart; + } + + if(p == sizeof("Content-Length")-1 + && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0) + { + // Decode number + long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK + if(len < 0) len = 0; + // Store + mContentLength = len; + } + else if(p == sizeof("Content-Type")-1 + && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0) + { + // Store rest of string as content type + mContentType = h + dataStart; + } + else if(p == sizeof("Cookie")-1 + && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0) + { + THROW_EXCEPTION(HTTPException, NotImplemented); + /* + // Parse cookies + ParseCookies(header, dataStart); + */ + } + else if(p == sizeof("Connection")-1 + && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0) + { + // Connection header, what is required? + const char *v = h + dataStart; + if(::strcasecmp(v, "close") == 0) + { + mKeepAlive = false; + } + else if(::strcasecmp(v, "keep-alive") == 0) + { + mKeepAlive = true; + } + // else don't understand, just assume default for protocol version + } + else + { + std::string headerName = header.substr(0, p); + AddHeader(headerName, h + dataStart); + } + + // Unset have header flag, as it's now been processed + haveHeader = false; + } + + // Store the chunk of header the for next time round + if(haveHeader) + { + header += currentLine; + } + else + { + header = currentLine; + haveHeader = true; + } + + // End of headers? + if(currentLine.empty()) + { + // All done! + break; + } + } +} + +void HTTPResponse::Receive(IOStream& rStream, int Timeout) +{ + IOStreamGetLine rGetLine(rStream); + + if(rGetLine.IsEOF()) + { + // Connection terminated unexpectedly + THROW_EXCEPTION(HTTPException, BadResponse) + } + + std::string statusLine; + if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION(HTTPException, ResponseReadFailed) + } + + if (statusLine.substr(0, 7) != "HTTP/1." || + statusLine[8] != ' ') + { + // Status line terminated unexpectedly + BOX_ERROR("Bad response status line: " << statusLine); + THROW_EXCEPTION(HTTPException, BadResponse) + } + + if (statusLine[5] == '1' && statusLine[7] == '1') + { + // HTTP/1.1 default is to keep alive + mKeepAlive = true; + } + + // Decode the status code + long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10); + // returns zero in error case, this is OK + if (status < 0) status = 0; + // Store + mResponseCode = status; + + // 100 Continue responses have no headers, terminating newline, or body + if (status == 100) + { + return; + } + + ParseHeaders(rGetLine, Timeout); + + // push back whatever bytes we have left + // rGetLine.DetachFile(); + if (mContentLength > 0) + { + if (mContentLength < rGetLine.GetSizeOfBufferedData()) + { + // very small response, not good! + THROW_EXCEPTION(HTTPException, NotImplemented); + } + + mContentLength -= rGetLine.GetSizeOfBufferedData(); + + Write(rGetLine.GetBufferedData(), + rGetLine.GetSizeOfBufferedData()); + } + + while (mContentLength != 0) // could be -1 as well + { + char buffer[4096]; + int readSize = sizeof(buffer); + if (mContentLength > 0 && mContentLength < readSize) + { + readSize = mContentLength; + } + readSize = rStream.Read(buffer, readSize, Timeout); + if (readSize == 0) + { + break; + } + mContentLength -= readSize; + Write(buffer, readSize); + } + + SetForReading(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *) +// Purpose: Add header, given entire line +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +/* +void HTTPResponse::AddHeader(const char *EntireHeaderLine) +{ + mExtraHeaders.push_back(std::string(EntireHeaderLine)); +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const std::string &) +// Purpose: Add header, given entire line +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +/* +void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine) +{ + mExtraHeaders.push_back(rEntireHeaderLine); +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *, const char *) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const char *pHeader, const char *pValue) +{ + mExtraHeaders.push_back(Header(pHeader, pValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *, const std::string &) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue) +{ + mExtraHeaders.push_back(Header(pHeader, rValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const std::string &, const std::string &) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue) +{ + mExtraHeaders.push_back(Header(rHeader, rValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetCookie(const char *, const char *, const char *, int) +// Purpose: Sets a cookie, using name, value, path and expiry time. +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt) +{ + if(ExpiresAt != 0) + { + THROW_EXCEPTION(HTTPException, NotImplemented) + } + + // Appears you shouldn't use quotes when you generate set-cookie headers. + // Oh well. It was fun finding that out. +/* std::string h("Set-Cookie: "); + h += Name; + h += "=\""; + h += Value; + h += "\"; Version=\"1\"; Path=\""; + h += Path; + h += "\""; +*/ + std::string h; + h += Name; + h += "="; + h += Value; + h += "; Version=1; Path="; + h += Path; + + mExtraHeaders.push_back(Header("Set-Cookie", h)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetAsRedirect(const char *, bool) +// Purpose: Sets the response objects to be a redirect to another page. +// If IsLocalURL == true, the default prefix will be added. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI) +{ + if(mResponseCode != HTTPResponse::Code_NoContent + || !mContentType.empty() + || GetSize() != 0) + { + THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData) + } + + // Set response code + mResponseCode = Code_Found; + + // Set location to redirect to + std::string header; + if(IsLocalURI) header += msDefaultURIPrefix; + header += RedirectTo; + mExtraHeaders.push_back(Header("Location", header)); + + // Set up some default content + mContentType = "text/html"; + #define REDIRECT_HTML_1 "<html><head><title>Redirection\n

    Redirect to content

    \n" + Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1); + if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size()); + Write(RedirectTo, ::strlen(RedirectTo)); + Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetAsNotFound(const char *) +// Purpose: Set the response object to be a standard page not found 404 response. +// Created: 7/4/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetAsNotFound(const char *URI) +{ + if(mResponseCode != HTTPResponse::Code_NoContent + || mExtraHeaders.size() != 0 + || !mContentType.empty() + || GetSize() != 0) + { + THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData) + } + + // Set response code + mResponseCode = Code_NotFound; + + // Set data + mContentType = "text/html"; + #define NOT_FOUND_HTML_1 "404 Not Found\n

    404 Not Found

    \n

    The URI " + #define NOT_FOUND_HTML_2 " was not found on this server.

    \n" + Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1); + WriteStringDefang(std::string(URI)); + Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::WriteStringDefang(const char *, unsigned int) +// Purpose: Writes a string 'defanged', ie has HTML special characters escaped +// so that people can't output arbitary HTML by playing with +// URLs and form parameters, and it's safe to write strings into +// HTML element attribute values. +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen) +{ + while(StringLen > 0) + { + unsigned int toWrite = 0; + while(toWrite < StringLen + && String[toWrite] != '<' + && String[toWrite] != '>' + && String[toWrite] != '&' + && String[toWrite] != '"') + { + ++toWrite; + } + if(toWrite > 0) + { + Write(String, toWrite); + StringLen -= toWrite; + String += toWrite; + } + + // Is it a bad character next? + while(StringLen > 0) + { + bool notSpecial = false; + switch(*String) + { + case '<': Write("<", 4); break; + case '>': Write(">", 4); break; + case '&': Write("&", 5); break; + case '"': Write(""", 6); break; + default: + // Stop this loop + notSpecial = true; + break; + } + if(notSpecial) break; + ++String; + --StringLen; + } + } +} + + diff --git a/lib/httpserver/HTTPResponse.h b/lib/httpserver/HTTPResponse.h new file mode 100644 index 00000000..04051958 --- /dev/null +++ b/lib/httpserver/HTTPResponse.h @@ -0,0 +1,175 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPResponse.h +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPRESPONSE__H +#define HTTPRESPONSE__H + +#include +#include + +#include "CollectInBufferStream.h" + +class IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPResponse +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPResponse : public CollectInBufferStream +{ +public: + HTTPResponse(IOStream* pStreamToSendTo); + HTTPResponse(); + ~HTTPResponse(); + + // allow copying, but be very careful with the response stream, + // you can only read it once! (this class doesn't police it). + HTTPResponse(const HTTPResponse& rOther) + : mResponseCode(rOther.mResponseCode), + mResponseIsDynamicContent(rOther.mResponseIsDynamicContent), + mKeepAlive(rOther.mKeepAlive), + mContentType(rOther.mContentType), + mExtraHeaders(rOther.mExtraHeaders), + mContentLength(rOther.mContentLength), + mpStreamToSendTo(rOther.mpStreamToSendTo) + { + Write(rOther.GetBuffer(), rOther.GetSize()); + } + + HTTPResponse &operator=(const HTTPResponse &rOther) + { + Reset(); + Write(rOther.GetBuffer(), rOther.GetSize()); + mResponseCode = rOther.mResponseCode; + mResponseIsDynamicContent = rOther.mResponseIsDynamicContent; + mKeepAlive = rOther.mKeepAlive; + mContentType = rOther.mContentType; + mExtraHeaders = rOther.mExtraHeaders; + mContentLength = rOther.mContentLength; + mpStreamToSendTo = rOther.mpStreamToSendTo; + return *this; + } + + typedef std::pair Header; + + void SetResponseCode(int Code); + int GetResponseCode() { return mResponseCode; } + void SetContentType(const char *ContentType); + const std::string& GetContentType() { return mContentType; } + + void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true); + void SetAsNotFound(const char *URI); + + void Send(bool OmitContent = false); + void SendContinue(); + void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite); + + // void AddHeader(const char *EntireHeaderLine); + // void AddHeader(const std::string &rEntireHeaderLine); + void AddHeader(const char *Header, const char *Value); + void AddHeader(const char *Header, const std::string &rValue); + void AddHeader(const std::string &rHeader, const std::string &rValue); + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + for (std::vector
    ::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == rName) + { + *pValueOut = i->second; + return true; + } + } + return false; + } + std::string GetHeaderValue(const std::string& rName) + { + std::string value; + if (!GetHeader(rName, &value)) + { + THROW_EXCEPTION(CommonException, ConfigNoKey); + } + return value; + } + + // Set dynamic content flag, default is content is dynamic + void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;} + // Set keep alive control, default is to mark as to be closed + void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;} + + void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0); + + enum + { + Code_OK = 200, + Code_NoContent = 204, + Code_MovedPermanently = 301, + Code_Found = 302, // redirection + Code_NotModified = 304, + Code_TemporaryRedirect = 307, + Code_MethodNotAllowed = 400, + Code_Unauthorized = 401, + Code_Forbidden = 403, + Code_NotFound = 404, + Code_InternalServerError = 500, + Code_NotImplemented = 501 + }; + + static const char *ResponseCodeToString(int ResponseCode); + + void WriteStringDefang(const char *String, unsigned int StringLen); + void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());} + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::WriteString(const std::string &) + // Purpose: Write a string to the response (simple sugar function) + // Created: 9/4/04 + // + // -------------------------------------------------------------------------- + void WriteString(const std::string &rString) + { + Write(rString.c_str(), rString.size()); + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::SetDefaultURIPrefix(const std::string &) + // Purpose: Set default prefix used to local redirections + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + static void SetDefaultURIPrefix(const std::string &rPrefix) + { + msDefaultURIPrefix = rPrefix; + } + +private: + int mResponseCode; + bool mResponseIsDynamicContent; + bool mKeepAlive; + std::string mContentType; + std::vector
    mExtraHeaders; + int mContentLength; // only used when reading response from stream + IOStream* mpStreamToSendTo; // nonzero only when constructed with a stream + + static std::string msDefaultURIPrefix; + + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); +}; + +#endif // HTTPRESPONSE__H + diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp new file mode 100644 index 00000000..be1db687 --- /dev/null +++ b/lib/httpserver/HTTPServer.cpp @@ -0,0 +1,247 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPServer.cpp +// Purpose: HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "HTTPServer.h" +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "IOStreamGetLine.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPServer() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPServer::HTTPServer() + : mTimeout(20000) // default timeout leaves a little while for clients to get the second request in. +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::~HTTPServer() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPServer::~HTTPServer() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::DaemonName() +// Purpose: As interface, generic name for daemon +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const char *HTTPServer::DaemonName() const +{ + return "generic-httpserver"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::GetConfigVerify() +// Purpose: As interface -- return most basic config so it's only necessary to +// provide this if you want to add extra directives. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *HTTPServer::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerifyKey verifyrootkeys[] = + { + HTTPSERVER_VERIFY_ROOT_KEYS + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::Run() +// Purpose: As interface. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::Run() +{ + // Do some configuration stuff + const Configuration &conf(GetConfiguration()); + HTTPResponse::SetDefaultURIPrefix(conf.GetKeyValue("AddressPrefix")); + + // Let the base class do the work + ServerStream::Run(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::Connection(SocketStream &) +// Purpose: As interface, handle connection +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::Connection(SocketStream &rStream) +{ + // Create a get line object to use + IOStreamGetLine getLine(rStream); + + // Notify dervived claases + HTTPConnectionOpening(); + + bool handleRequests = true; + while(handleRequests) + { + // Parse the request + HTTPRequest request; + if(!request.Receive(getLine, mTimeout)) + { + // Didn't get request, connection probably closed. + break; + } + + // Generate a response + HTTPResponse response(&rStream); + + try + { + Handle(request, response); + } + catch(BoxException &e) + { + char exceptionCode[256]; + ::sprintf(exceptionCode, "%s (%d/%d)", e.what(), + e.GetType(), e.GetSubType()); + SendInternalErrorResponse(exceptionCode, response); + } + catch(...) + { + SendInternalErrorResponse("unknown", response); + } + + // Keep alive? + if(request.GetClientKeepAliveRequested()) + { + // Mark the response to the client as supporting keepalive + response.SetKeepAlive(true); + } + else + { + // Stop now + handleRequests = false; + } + + // Send the response (omit any content if this is a HEAD method request) + response.Send(request.GetMethod() == HTTPRequest::Method_HEAD); + } + + // Notify derived classes + HTTPConnectionClosing(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::SendInternalErrorResponse(const char*, +// HTTPResponse&) +// Purpose: Generates an error message in the provided response +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse) +{ + #define ERROR_HTML_1 "Internal Server Error\n" \ + "

    Internal Server Error

    \n" \ + "

    An error, type " + #define ERROR_HTML_2 " occured when processing the request.

    " \ + "

    Please try again later.

    " \ + "\n\n" + + // Generate the error page + // rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/html"); + rResponse.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1); + rResponse.IOStream::Write(rErrorMsg.c_str()); + rResponse.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPConnectionOpening() +// Purpose: Override to get notifications of connections opening +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::HTTPConnectionOpening() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPConnectionClosing() +// Purpose: Override to get notifications of connections closing +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::HTTPConnectionClosing() +{ +} + + diff --git a/lib/httpserver/HTTPServer.h b/lib/httpserver/HTTPServer.h new file mode 100644 index 00000000..d9f74949 --- /dev/null +++ b/lib/httpserver/HTTPServer.h @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPServer.h +// Purpose: HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPSERVER__H +#define HTTPSERVER__H + +#include "ServerStream.h" +#include "SocketStream.h" + +class HTTPRequest; +class HTTPResponse; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPServer +// Purpose: HTTP server +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPServer : public ServerStream +{ +public: + HTTPServer(); + ~HTTPServer(); +private: + // no copying + HTTPServer(const HTTPServer &); + HTTPServer &operator=(const HTTPServer &); +public: + + int GetTimeout() const {return mTimeout;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPServer::Handle(const HTTPRequest &, HTTPResponse &) + // Purpose: Response to a request, filling in the response object for sending + // at some point in the future. + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) = 0; + + // For notifications to derived classes + virtual void HTTPConnectionOpening(); + virtual void HTTPConnectionClosing(); + +protected: + void SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse); + int GetTimeout() { return mTimeout; } + +private: + int mTimeout; // Timeout for read operations + const char *DaemonName() const; + const ConfigurationVerify *GetConfigVerify() const; + void Run(); + void Connection(SocketStream &rStream); +}; + +// Root level +#define HTTPSERVER_VERIFY_ROOT_KEYS \ + ConfigurationVerifyKey("AddressPrefix", \ + ConfigTest_Exists | ConfigTest_LastEntry) + +// AddressPrefix is, for example, http://localhost:1080 -- ie the beginning of the URI +// This is used for handling redirections. + +// Server level +#define HTTPSERVER_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) + +#endif // HTTPSERVER__H + diff --git a/lib/httpserver/Makefile.extra b/lib/httpserver/Makefile.extra new file mode 100644 index 00000000..ef47f398 --- /dev/null +++ b/lib/httpserver/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_HTTPException.h autogen_HTTPException.cpp: $(MAKEEXCEPTION) HTTPException.txt + $(_PERL) $(MAKEEXCEPTION) HTTPException.txt + diff --git a/lib/httpserver/S3Client.cpp b/lib/httpserver/S3Client.cpp new file mode 100644 index 00000000..cd5988d5 --- /dev/null +++ b/lib/httpserver/S3Client.cpp @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +// #include +// #include + +#include + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Client.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::GetObject(const std::string& rObjectURI) +// Purpose: Retrieve the object with the specified URI (key) +// from your S3 bucket. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::GetObject(const std::string& rObjectURI) +{ + return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::PutObject(const std::string& rObjectURI, +// IOStream& rStreamToSend, const char* pContentType) +// Purpose: Upload the stream to S3, creating or overwriting the +// object with the specified URI (key) in your S3 +// bucket. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType) +{ + return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, + &rStreamToSend, pContentType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::FinishAndSendRequest( +// HTTPRequest::Method Method, +// const std::string& rRequestURI, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which creates an HTTP request to S3, +// populates the date and authorization header fields, +// and sends it to S3 (or the simulator), attaching +// the specified stream if any to the request. Opens a +// connection to the server if necessary, which may +// throw a ConnectionException. Returns the HTTP +// response returned by S3, which may be a 500 error. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, IOStream* pStreamToSend, + const char* pStreamContentType) +{ + HTTPRequest request(Method, rRequestURI); + request.SetHostName(mHostName); + + std::ostringstream date; + time_t tt = time(NULL); + struct tm *tp = gmtime(&tt); + if (!tp) + { + BOX_ERROR("Failed to get current time"); + THROW_EXCEPTION(HTTPException, Internal); + } + const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + date << dow[tp->tm_wday] << ", "; + const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + date << std::internal << std::setfill('0') << + std::setw(2) << tp->tm_mday << " " << + month[tp->tm_mon] << " " << + (tp->tm_year + 1900) << " "; + date << std::setw(2) << tp->tm_hour << ":" << + std::setw(2) << tp->tm_min << ":" << + std::setw(2) << tp->tm_sec << " GMT"; + request.AddHeader("Date", date.str()); + + if (pStreamContentType) + { + request.AddHeader("Content-Type", pStreamContentType); + } + + std::string s3suffix = ".s3.amazonaws.com"; + std::string bucket; + if (mHostName.size() > s3suffix.size()) + { + std::string suffix = mHostName.substr(mHostName.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = mHostName.substr(0, mHostName.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << request.GetVerb() << "\n"; + data << "\n"; /* Content-MD5 */ + data << request.GetContentType() << "\n"; + data << date.str() << "\n"; + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << request.GetRequestURI(); + std::string data_string = data.str(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + mSecretKey.c_str(), mSecretKey.size(), + (const unsigned char*)data_string.c_str(), + data_string.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + + base64::encoder encoder; + std::string auth_code = "AWS " + mAccessKey + ":" + + encoder.encode(digest); + + if (auth_code[auth_code.size() - 1] == '\n') + { + auth_code = auth_code.substr(0, auth_code.size() - 1); + } + + request.AddHeader("Authorization", auth_code); + + if (mpSimulator) + { + if (pStreamToSend) + { + pStreamToSend->CopyStreamTo(request); + } + + request.SetForReading(); + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + mpSimulator->Handle(request, response); + return response; + } + else + { + try + { + if (!mapClientSocket.get()) + { + mapClientSocket.reset(new SocketStream()); + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + } + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + catch (ConnectionException &ce) + { + if (ce.GetType() == ConnectionException::SocketWriteError) + { + // server may have disconnected us, + // try to reconnect, just once + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + else + { + throw; + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::SendRequest(HTTPRequest& rRequest, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which sends a pre-existing HTTP +// request to S3. Attaches the specified stream if any +// to the request. Opens a connection to the server if +// necessary, which may throw a ConnectionException. +// Returns the HTTP response returned by S3, which may +// be a 500 error. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend, const char* pStreamContentType) +{ + HTTPResponse response; + + if (pStreamToSend) + { + rRequest.SendWithStream(*mapClientSocket, + 30000 /* milliseconds */, + pStreamToSend, response); + } + else + { + rRequest.Send(*mapClientSocket, 30000 /* milliseconds */); + response.Receive(*mapClientSocket, 30000 /* milliseconds */); + } + + return response; +} diff --git a/lib/httpserver/S3Client.h b/lib/httpserver/S3Client.h new file mode 100644 index 00000000..3c4126ac --- /dev/null +++ b/lib/httpserver/S3Client.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.h +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3CLIENT__H +#define S3CLIENT__H + +#include +#include + +#include "HTTPRequest.h" +#include "SocketStream.h" + +class HTTPResponse; +class HTTPServer; +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Client +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Client +{ + public: + S3Client(HTTPServer* pSimulator, const std::string& rHostName, + const std::string& rAccessKey, const std::string& rSecretKey) + : mpSimulator(pSimulator), + mHostName(rHostName), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + S3Client(std::string HostName, int Port, const std::string& rAccessKey, + const std::string& rSecretKey) + : mpSimulator(NULL), + mHostName(HostName), + mPort(Port), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey) + { } + + HTTPResponse GetObject(const std::string& rObjectURI); + HTTPResponse PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType = NULL); + + private: + HTTPServer* mpSimulator; + std::string mHostName; + int mPort; + std::auto_ptr mapClientSocket; + std::string mAccessKey, mSecretKey; + + HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); + HTTPResponse SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); +}; + +#endif // S3CLIENT__H + diff --git a/lib/httpserver/S3Simulator.cpp b/lib/httpserver/S3Simulator.cpp new file mode 100644 index 00000000..4f6bb3e6 --- /dev/null +++ b/lib/httpserver/S3Simulator.cpp @@ -0,0 +1,309 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +// #include +// #include + +#include + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Simulator.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::GetConfigVerify() +// Purpose: Returns additional configuration options for the +// S3 simulator. Currently the access key, secret key +// and store directory can be configured. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify* S3Simulator::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerifyKey verifyrootkeys[] = + { + ConfigurationVerifyKey("AccessKey", ConfigTest_Exists), + ConfigurationVerifyKey("SecretKey", ConfigTest_Exists), + ConfigurationVerifyKey("StoreDirectory", ConfigTest_Exists), + HTTPSERVER_VERIFY_ROOT_KEYS + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::Handle(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles any incoming S3 request, by checking +// authorization and then dispatching to one of the +// private Handle* methods. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + // if anything goes wrong, return a 500 error + rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/plain"); + + try + { + const Configuration& rConfig(GetConfiguration()); + std::string access_key = rConfig.GetKeyValue("AccessKey"); + std::string secret_key = rConfig.GetKeyValue("SecretKey"); + + std::string md5, date, bucket; + rRequest.GetHeader("content-md5", &md5); + rRequest.GetHeader("date", &date); + + std::string host = rRequest.GetHostName(); + std::string s3suffix = ".s3.amazonaws.com"; + if (host.size() > s3suffix.size()) + { + std::string suffix = host.substr(host.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = host.substr(0, host.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << rRequest.GetVerb() << "\n"; + data << md5 << "\n"; + data << rRequest.GetContentType() << "\n"; + data << date << "\n"; + + // header names are already in lower case, i.e. canonical form + + std::vector headers = rRequest.GetHeaders(); + std::sort(headers.begin(), headers.end()); + + for (std::vector::iterator + i = headers.begin(); i != headers.end(); i++) + { + if (i->first.substr(0, 5) == "x-amz") + { + data << i->first << ":" << i->second << "\n"; + } + } + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << rRequest.GetRequestURI(); + std::string data_string = data.str(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + secret_key.c_str(), secret_key.size(), + (const unsigned char*)data_string.c_str(), + data_string.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + + base64::encoder encoder; + std::string expectedAuth = "AWS " + access_key + ":" + + encoder.encode(digest); + + if (expectedAuth[expectedAuth.size() - 1] == '\n') + { + expectedAuth = expectedAuth.substr(0, + expectedAuth.size() - 1); + } + + std::string actualAuth; + if (!rRequest.GetHeader("authorization", &actualAuth) || + actualAuth != expectedAuth) + { + rResponse.SetResponseCode(HTTPResponse::Code_Unauthorized); + SendInternalErrorResponse("Authentication Failed", + rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_GET) + { + HandleGet(rRequest, rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_PUT) + { + HandlePut(rRequest, rResponse); + } + else + { + rResponse.SetResponseCode(HTTPResponse::Code_MethodNotAllowed); + SendInternalErrorResponse("Unsupported Method", + rResponse); + } + } + catch (CommonException &ce) + { + SendInternalErrorResponse(ce.what(), rResponse); + } + catch (std::exception &e) + { + SendInternalErrorResponse(e.what(), rResponse); + } + catch (...) + { + SendInternalErrorResponse("Unknown exception", rResponse); + } + + if (rResponse.GetResponseCode() != 200 && + rResponse.GetSize() == 0) + { + // no error message written, provide a default + std::ostringstream s; + s << rResponse.GetResponseCode(); + SendInternalErrorResponse(s.str().c_str(), rResponse); + } + + return; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandleGet(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 GET request, i.e. downloading an +// existing object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr apFile; + + try + { + apFile.reset(new FileStream(path)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingRESTOperations.html + apFile->CopyStreamTo(rResponse); + rResponse.AddHeader("x-amz-id-2", "qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandlePut(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 PUT request, i.e. uploading data to +// create or replace an object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr apFile; + + try + { + apFile.reset(new FileStream(path, O_CREAT | O_WRONLY)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + if (rRequest.IsExpectingContinue()) + { + rResponse.SendContinue(); + } + + rRequest.ReadContent(*apFile); + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectPUT.html + rResponse.AddHeader("x-amz-id-2", "LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.SetContentType(""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} diff --git a/lib/httpserver/S3Simulator.h b/lib/httpserver/S3Simulator.h new file mode 100644 index 00000000..f80770ee --- /dev/null +++ b/lib/httpserver/S3Simulator.h @@ -0,0 +1,40 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Simulator.h +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3SIMULATOR__H +#define S3SIMULATOR__H + +#include "HTTPServer.h" + +class ConfigurationVerify; +class HTTPRequest; +class HTTPResponse; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Simulator +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Simulator : public HTTPServer +{ +public: + S3Simulator() { } + ~S3Simulator() { } + + const ConfigurationVerify* GetConfigVerify() const; + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse); +}; + +#endif // S3SIMULATOR__H + diff --git a/lib/httpserver/cdecode.cpp b/lib/httpserver/cdecode.cpp new file mode 100644 index 00000000..e632f182 --- /dev/null +++ b/lib/httpserver/cdecode.cpp @@ -0,0 +1,92 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cdecode.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +} diff --git a/lib/httpserver/cdecode.h b/lib/httpserver/cdecode.h new file mode 100644 index 00000000..d0d7f489 --- /dev/null +++ b/lib/httpserver/cdecode.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/lib/httpserver/cencode.cpp b/lib/httpserver/cencode.cpp new file mode 100644 index 00000000..b33c0683 --- /dev/null +++ b/lib/httpserver/cencode.cpp @@ -0,0 +1,113 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cencode.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return codechar - code_out; +} + +} diff --git a/lib/httpserver/cencode.h b/lib/httpserver/cencode.h new file mode 100644 index 00000000..cf321312 --- /dev/null +++ b/lib/httpserver/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/lib/httpserver/decode.h b/lib/httpserver/decode.h new file mode 100644 index 00000000..fe59ef7a --- /dev/null +++ b/lib/httpserver/decode.h @@ -0,0 +1,77 @@ +// :mode=c++: +/* +decode.h - c++ wrapper for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_DECODE_H +#define BASE64_DECODE_H + +#include + +namespace base64 +{ + + extern "C" + { + #include "cdecode.h" + } + + struct decoder + { + base64_decodestate _state; + int _buffersize; + + decoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int decode(char value_in) + { + return base64_decode_value(value_in); + } + int decode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_decode_block(code_in, length_in, plaintext_out, &_state); + } + std::string decode(const std::string& input) + { + base64_init_decodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = decode(input.c_str(), input.size(), + output); + std::string output_string(output, outlength); + base64_init_decodestate(&_state); + delete [] output; + return output_string; + } + void decode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_decodestate(&_state); + // + const int N = _buffersize; + char* code = new char[N]; + char* plaintext = new char[N]; + int codelength; + int plainlength; + + do + { + istream_in.read((char*)code, N); + codelength = istream_in.gcount(); + plainlength = decode(code, codelength, plaintext); + ostream_in.write((const char*)plaintext, plainlength); + } + while (istream_in.good() && codelength > 0); + // + base64_init_decodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_DECODE_H diff --git a/lib/httpserver/encode.h b/lib/httpserver/encode.h new file mode 100644 index 00000000..81957a0f --- /dev/null +++ b/lib/httpserver/encode.h @@ -0,0 +1,87 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include + +namespace base64 +{ + + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int encode(char value_in) + { + return base64_encode_value(value_in); + } + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + std::string encode(const std::string& input) + { + base64_init_encodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = encode(input.c_str(), input.size(), + output); + outlength += encode_end(output + outlength); + std::string output_string(output, outlength); + base64_init_encodestate(&_state); + delete [] output; + return output_string; + } + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = istream_in.gcount(); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + diff --git a/lib/intercept/intercept.cpp b/lib/intercept/intercept.cpp new file mode 100644 index 00000000..7a33b610 --- /dev/null +++ b/lib/intercept/intercept.cpp @@ -0,0 +1,673 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: intercept.cpp +// Purpose: Syscall interception code for the raidfile test +// Created: 2003/07/22 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "intercept.h" + +#ifdef HAVE_SYS_SYSCALL_H + #include +#endif +#include +#include + +#ifdef HAVE_SYS_UIO_H + #include +#endif + +#include +#include + +#ifdef HAVE_DLFCN_H +#include +#endif + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + +#if !defined(HAVE_SYSCALL) && !defined(HAVE___SYSCALL) && !defined(HAVE___SYSCALL_NEED_DEFN) + #define PLATFORM_NO_SYSCALL +#endif + +#ifdef PLATFORM_NO_SYSCALL + // For some reason, syscall just doesn't work on Darwin + // so instead, we build functions using assembler in a varient + // of the technique used in the Darwin Libc + extern "C" int + TEST_open(const char *path, int flags, mode_t mode); + extern "C" int + TEST_close(int d); + extern "C" ssize_t + TEST_write(int d, const void *buf, size_t nbytes); + extern "C" ssize_t + TEST_read(int d, void *buf, size_t nbytes); + extern "C" ssize_t + TEST_readv(int d, const struct iovec *iov, int iovcnt); + extern "C" off_t + TEST_lseek(int fildes, off_t offset, int whence); +#else + // if we have __syscall, we should use it for everything + // (on FreeBSD 7 this is required for 64-bit alignment of off_t). + // if not, we should continue to use the old syscall(). + #ifdef HAVE___SYSCALL_NEED_DEFN + // Need this, not declared in syscall.h nor unistd.h + extern "C" off_t __syscall(quad_t number, ...); + #endif + #ifdef HAVE___SYSCALL + #undef syscall + #define syscall __syscall + #endif +#endif + +#include +#include + +#include "MemLeakFindOn.h" + +int intercept_count = 0; +const char *intercept_filename = 0; +int intercept_filedes = -1; +off_t intercept_errorafter = 0; +int intercept_errno = 0; +int intercept_syscall = 0; +off_t intercept_filepos = 0; +int intercept_delay_ms = 0; + +static opendir_t* opendir_real = NULL; +static readdir_t* readdir_real = NULL; +static readdir_t* readdir_hook = NULL; +static closedir_t* closedir_real = NULL; +static lstat_t* lstat_real = NULL; +static lstat_t* lstat_hook = NULL; +static const char* lstat_file = NULL; +static lstat_t* stat_real = NULL; +static lstat_t* stat_hook = NULL; +static const char* stat_file = NULL; + +static lstat_post_hook_t* lstat_post_hook = NULL; +static lstat_post_hook_t* stat_post_hook = NULL; + +#define SIZE_ALWAYS_ERROR -773 + +void intercept_clear_setup() +{ + intercept_count = 0; + intercept_filename = 0; + intercept_filedes = -1; + intercept_errorafter = 0; + intercept_syscall = 0; + intercept_filepos = 0; + intercept_delay_ms = 0; + readdir_hook = NULL; + stat_hook = NULL; + lstat_hook = NULL; + stat_post_hook = NULL; + lstat_post_hook = NULL; +} + +bool intercept_triggered() +{ + return intercept_count == 0; +} + +void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror) +{ + BOX_TRACE("Setup for error: " << filename << + ", after " << errorafter << + ", err " << errortoreturn << + ", syscall " << syscalltoerror); + + intercept_count = 1; + intercept_filename = filename; + intercept_filedes = -1; + intercept_errorafter = errorafter; + intercept_syscall = syscalltoerror; + intercept_errno = errortoreturn; + intercept_filepos = 0; + intercept_delay_ms = 0; +} + +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay, int num_delays) +{ + BOX_TRACE("Setup for delay: " << filename << + ", after " << delay_after << + ", wait " << delay_ms << " ms" << + ", times " << num_delays << + ", syscall " << syscall_to_delay); + + intercept_count = num_delays; + intercept_filename = filename; + intercept_filedes = -1; + intercept_errorafter = delay_after; + intercept_syscall = syscall_to_delay; + intercept_errno = 0; + intercept_filepos = 0; + intercept_delay_ms = delay_ms; +} + +bool intercept_errornow(int d, int size, int syscallnum) +{ + ASSERT(intercept_count > 0) + + if (intercept_filedes == -1) + { + return false; // no error please! + } + + if (d != intercept_filedes) + { + return false; // no error please! + } + + if (syscallnum != intercept_syscall) + { + return false; // no error please! + } + + bool ret = false; // no error unless one of the conditions matches + + //printf("Checking for err, %d, %d, %d\n", d, size, syscallnum); + + if (intercept_delay_ms != 0) + { + BOX_TRACE("Delaying " << intercept_delay_ms << " ms " << + " for syscall " << syscallnum << + " at " << intercept_filepos); + + struct timespec tm; + tm.tv_sec = intercept_delay_ms / 1000; + tm.tv_nsec = (intercept_delay_ms % 1000) * 1000000; + while (nanosleep(&tm, &tm) != 0 && + errno == EINTR) { } + } + + if (size == SIZE_ALWAYS_ERROR) + { + // Looks good for an error! + BOX_TRACE("Returning error " << intercept_errno << + " for syscall " << syscallnum); + ret = true; + } + else if (intercept_filepos + size < intercept_errorafter) + { + return false; // no error please + } + else if (intercept_errno != 0) + { + BOX_TRACE("Returning error " << intercept_errno << + " for syscall " << syscallnum << + " at " << intercept_filepos); + ret = true; + } + + intercept_count--; + if (intercept_count == 0) + { + intercept_clear_setup(); + } + + return ret; +} + +int intercept_reterr() +{ + int err = intercept_errno; + intercept_clear_setup(); + return err; +} + +#define CHECK_FOR_FAKE_ERROR_COND(D, S, CALL, FAILRES) \ + if(intercept_count > 0) \ + { \ + if(intercept_errornow(D, S, CALL)) \ + { \ + errno = intercept_reterr(); \ + return FAILRES; \ + } \ + } + +#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 + #define DEFINE_ONLY_OPEN64 +#endif + +extern "C" int +#ifdef DEFINE_ONLY_OPEN64 + open64(const char *path, int flags, ...) +#else + open(const char *path, int flags, ...) +#endif // DEFINE_ONLY_OPEN64 +{ + if(intercept_count > 0) + { + if(intercept_filename != NULL && + intercept_syscall == SYS_open && + strcmp(path, intercept_filename) == 0) + { + errno = intercept_reterr(); + return -1; + } + } + + mode_t mode = 0; + if (flags & O_CREAT) + { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_open(path, flags, mode); +#else + int r = syscall(SYS_open, path, flags, mode); +#endif + + if(intercept_filename != NULL && + intercept_count > 0 && + intercept_filedes == -1) + { + // Right file? + if(strcmp(intercept_filename, path) == 0) + { + intercept_filedes = r; + //printf("Found file to intercept, h = %d\n", r); + } + } + + return r; +} + +#ifndef DEFINE_ONLY_OPEN64 +extern "C" int +// open64(const char *path, int flags, mode_t mode) +// open64(const char *path, int flags, ...) +open64 (__const char *path, int flags, ...) +{ + mode_t mode = 0; + if (flags & O_CREAT) + { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + + // With _FILE_OFFSET_BITS set to 64 this should really use (flags | + // O_LARGEFILE) here, but not actually necessary for the tests and not + // worth the trouble finding O_LARGEFILE + return open(path, flags, mode); +} +#endif // !DEFINE_ONLY_OPEN64 + +extern "C" int +close(int d) +{ + CHECK_FOR_FAKE_ERROR_COND(d, SIZE_ALWAYS_ERROR, SYS_close, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_close(d); +#else + int r = syscall(SYS_close, d); +#endif + if(r == 0) + { + if(d == intercept_filedes) + { + intercept_filedes = -1; + } + } + return r; +} + +extern "C" ssize_t +write(int d, const void *buf, size_t nbytes) +{ + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_write, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_write(d, buf, nbytes); +#else + int r = syscall(SYS_write, d, buf, nbytes); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" ssize_t +read(int d, void *buf, size_t nbytes) +{ + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_read, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_read(d, buf, nbytes); +#else + int r = syscall(SYS_read, d, buf, nbytes); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" ssize_t +readv(int d, const struct iovec *iov, int iovcnt) +{ + // how many bytes? + int nbytes = 0; + for(int b = 0; b < iovcnt; ++b) + { + nbytes += iov[b].iov_len; + } + + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_readv, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_readv(d, iov, iovcnt); +#else + int r = syscall(SYS_readv, d, iov, iovcnt); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" off_t +lseek(int fildes, off_t offset, int whence) +{ + // random magic for lseek syscall, see /usr/src/lib/libc/sys/lseek.c + CHECK_FOR_FAKE_ERROR_COND(fildes, 0, SYS_lseek, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_lseek(fildes, offset, whence); +#else + #ifdef HAVE_LSEEK_DUMMY_PARAM + off_t r = syscall(SYS_lseek, fildes, 0 /* extra 0 required here! */, offset, whence); + #elif defined(_FILE_OFFSET_BITS) + // Don't bother trying to call SYS__llseek on 32 bit since it is + // fiddly and not needed for the tests + off_t r = syscall(SYS_lseek, fildes, (uint32_t)offset, whence); + #else + off_t r = syscall(SYS_lseek, fildes, offset, whence); + #endif +#endif + if(r != -1) + { + intercept_filepos = r; + } + return r; +} + +void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn) +{ + if (hookfn != NULL && dirname == NULL) + { + dirname = intercept_filename; + ASSERT(dirname != NULL); + } + + if (hookfn != NULL) + { + BOX_TRACE("readdir hooked to " << hookfn << " for " << dirname); + } + else if (intercept_filename != NULL) + { + BOX_TRACE("readdir unhooked from " << readdir_hook << + " for " << intercept_filename); + } + + intercept_filename = dirname; + readdir_hook = hookfn; +} + +void intercept_setup_lstat_hook(const char *filename, lstat_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + lstat_file = filename; + lstat_hook = hookfn; +} + +void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + lstat_post_hook = hookfn; +} + +void intercept_setup_stat_post_hook(lstat_post_hook_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + stat_post_hook = hookfn; +} + +static void * find_function(const char *pName) +{ + dlerror(); + void *result = NULL; + + #ifdef HAVE_LARGE_FILE_SUPPORT + { + // search for the 64-bit version first + std::string name64(pName); + name64 += "64"; + result = dlsym(RTLD_NEXT, name64.c_str()); + if (dlerror() == NULL && result != NULL) + { + return result; + } + } + #endif + + result = dlsym(RTLD_NEXT, pName); + const char *errmsg = (const char *)dlerror(); + + if (errmsg == NULL) + { + return result; + } + + BOX_ERROR("Failed to find real " << pName << " function: " << errmsg); + return NULL; +} + +extern "C" +DIR *opendir(const char *dirname) +{ + if (opendir_real == NULL) + { + opendir_real = (opendir_t*)find_function("opendir"); + } + + if (opendir_real == NULL) + { + perror("cannot find real opendir"); + return NULL; + } + + DIR* r = opendir_real(dirname); + + if (readdir_hook != NULL && + intercept_filename != NULL && + intercept_filedes == -1 && + strcmp(intercept_filename, dirname) == 0) + { + intercept_filedes = dirfd(r); + //printf("Found file to intercept, h = %d\n", r); + } + + return r; +} + +extern "C" +struct dirent *readdir(DIR *dir) +{ + if (readdir_hook != NULL && dirfd(dir) == intercept_filedes) + { + return readdir_hook(dir); + } + + if (readdir_real == NULL) + { + readdir_real = (readdir_t*)find_function("readdir"); + } + + if (readdir_real == NULL) + { + perror("cannot find real readdir"); + return NULL; + } + + return readdir_real(dir); +} + +extern "C" +int closedir(DIR *dir) +{ + if (dirfd(dir) == intercept_filedes) + { + intercept_filedes = -1; + } + + if (closedir_real == NULL) + { + closedir_real = (closedir_t*)find_function("closedir"); + } + + if (closedir_real == NULL) + { + perror("cannot find real closedir"); + errno = ENOSYS; + return -1; + } + + return closedir_real(dir); +} + +extern "C" int +#ifdef LINUX_WEIRD_LSTAT +__lxstat(int ver, const char *file_name, STAT_STRUCT *buf) +#else +lstat(const char *file_name, STAT_STRUCT *buf) +#endif +{ + if (lstat_real == NULL) + { + #ifdef LINUX_WEIRD_LSTAT + lstat_real = (lstat_t*)find_function("__lxstat"); + #else + lstat_real = (lstat_t*)find_function("lstat"); + #endif + } + + if (lstat_real == NULL) + { + perror("cannot find real lstat"); + errno = ENOSYS; + return -1; + } + + if (lstat_hook == NULL || strcmp(file_name, lstat_file) != 0) + { + #ifdef LINUX_WEIRD_LSTAT + int ret = lstat_real(ver, file_name, buf); + #else + int ret = lstat_real(file_name, buf); + #endif + if (lstat_post_hook != NULL) + { + ret = lstat_post_hook(ret, file_name, buf); + } + return ret; + } + + #ifdef LINUX_WEIRD_LSTAT + return lstat_hook(ver, file_name, buf); + #else + return lstat_hook(file_name, buf); + #endif +} + +extern "C" int +#ifdef LINUX_WEIRD_LSTAT +__xstat(int ver, const char *file_name, STAT_STRUCT *buf) +#else +stat(const char *file_name, STAT_STRUCT *buf) +#endif +{ + if (stat_real == NULL) + { + #ifdef LINUX_WEIRD_LSTAT + stat_real = (lstat_t*)find_function("__xstat"); + #else + stat_real = (lstat_t*)find_function("stat"); + #endif + } + + if (stat_real == NULL) + { + perror("cannot find real stat"); + errno = ENOSYS; + return -1; + } + + if (stat_hook == NULL || strcmp(file_name, stat_file) != 0) + { + #ifdef LINUX_WEIRD_LSTAT + int ret = stat_real(ver, file_name, buf); + #else + int ret = stat_real(file_name, buf); + #endif + if (stat_post_hook != NULL) + { + ret = stat_post_hook(ret, file_name, buf); + } + return ret; + } + + #ifdef LINUX_WEIRD_LSTAT + return stat_hook(ver, file_name, buf); + #else + return stat_hook(file_name, buf); + #endif +} + +#endif // n PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE diff --git a/lib/intercept/intercept.h b/lib/intercept/intercept.h new file mode 100644 index 00000000..80a17d3f --- /dev/null +++ b/lib/intercept/intercept.h @@ -0,0 +1,54 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: intercept.h +// Purpose: Syscall interception code for unit tests +// Created: 2006/11/29 +// +// -------------------------------------------------------------------------- + +#ifndef INTERCEPT_H +#define INTERCEPT_H +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + +#include + +#include +#include + +extern "C" +{ + typedef DIR *(opendir_t) (const char *name); + typedef struct dirent *(readdir_t) (DIR *dir); + typedef struct dirent *(readdir_t) (DIR *dir); + typedef int (closedir_t)(DIR *dir); +#if defined __GNUC__ && __GNUC__ >= 2 + #define LINUX_WEIRD_LSTAT + #define STAT_STRUCT struct stat /* should be stat64 */ + typedef int (lstat_t) (int ver, const char *file_name, + STAT_STRUCT *buf); +#else + #define STAT_STRUCT struct stat + typedef int (lstat_t) (const char *file_name, + STAT_STRUCT *buf); +#endif +} + +typedef int (lstat_post_hook_t) (int old_ret, const char *file_name, + struct stat *buf); + +void intercept_setup_error(const char *filename, unsigned int errorafter, + int errortoreturn, int syscalltoerror); +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay, int num_delays); +bool intercept_triggered(); + +void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn); +void intercept_setup_lstat_hook (const char *filename, lstat_t hookfn); +void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn); +void intercept_setup_stat_post_hook (lstat_post_hook_t hookfn); + +void intercept_clear_setup(); + +#endif // !PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE +#endif // !INTERCEPT_H diff --git a/lib/raidfile/Makefile.extra b/lib/raidfile/Makefile.extra new file mode 100644 index 00000000..bf06ed2f --- /dev/null +++ b/lib/raidfile/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_RaidFileException.h autogen_RaidFileException.cpp: $(MAKEEXCEPTION) RaidFileException.txt + $(_PERL) $(MAKEEXCEPTION) RaidFileException.txt + diff --git a/lib/raidfile/RaidFileController.cpp b/lib/raidfile/RaidFileController.cpp new file mode 100644 index 00000000..2cc6976b --- /dev/null +++ b/lib/raidfile/RaidFileController.cpp @@ -0,0 +1,227 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileController.cpp +// Purpose: Controls config and daemon comms for RaidFile classes +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "Configuration.h" + +#include "MemLeakFindOn.h" + +RaidFileController RaidFileController::mController; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::RaidFileController() +// Purpose: Constructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::RaidFileController() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::~RaidFileController() +// Purpose: Destructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::~RaidFileController() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::RaidFileController() +// Purpose: Copy constructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::RaidFileController(const RaidFileController &rController) +{ + THROW_EXCEPTION(RaidFileException, Internal) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::Initialise(const std::string&) +// Purpose: Initialises the system, loading the configuration file. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +void RaidFileController::Initialise(const std::string& rConfigFilename) +{ + MEMLEAKFINDER_NO_LEAKS; + + static const ConfigurationVerifyKey verifykeys[] = + { + ConfigurationVerifyKey("SetNumber", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("BlockSize", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("Dir0", ConfigTest_Exists), + ConfigurationVerifyKey("Dir1", ConfigTest_Exists), + ConfigurationVerifyKey("Dir2", + ConfigTest_Exists | ConfigTest_LastEntry) + }; + + static const ConfigurationVerify subverify = + { + "*", + 0, + verifykeys, + ConfigTest_LastEntry, + 0 + }; + + static const ConfigurationVerify verify = + { + "RAID FILE CONFIG", + &subverify, + 0, + ConfigTest_LastEntry, + 0 + }; + + // Load the configuration + std::string err; + std::auto_ptr pconfig = Configuration::LoadAndVerify( + rConfigFilename, &verify, err); + + if(pconfig.get() == 0 || !err.empty()) + { + BOX_ERROR("RaidFile configuration file errors: " << err); + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + + // Allow reinitializing the controller by remove any existing + // disc sets. Used by Boxi unit tests. + mSetList.clear(); + + // Use the values + int expectedSetNum = 0; + std::vector confdiscs(pconfig->GetSubConfigurationNames()); + for(std::vector::const_iterator i(confdiscs.begin()); i != confdiscs.end(); ++i) + { + const Configuration &disc(pconfig->GetSubConfiguration((*i).c_str())); + + int setNum = disc.GetKeyValueInt("SetNumber"); + if(setNum != expectedSetNum) + { + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + RaidFileDiscSet set(setNum, (unsigned int)disc.GetKeyValueInt("BlockSize")); + // Get the values of the directory keys + std::string d0(disc.GetKeyValue("Dir0")); + std::string d1(disc.GetKeyValue("Dir1")); + std::string d2(disc.GetKeyValue("Dir2")); + // Are they all different (using RAID) or all the same (not using RAID) + if(d0 != d1 && d1 != d2 && d0 != d2) + { + set.push_back(d0); + set.push_back(d1); + set.push_back(d2); + } + else if(d0 == d1 && d0 == d2) + { + // Just push the first one, which is the non-RAID place to store files + set.push_back(d0); + } + else + { + // One must be the same as another! Which is bad. + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + mSetList.push_back(set); + expectedSetNum++; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::GetDiscSet(int) +// Purpose: Returns the numbered disc set +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileDiscSet &RaidFileController::GetDiscSet(unsigned int DiscSetNum) +{ + if(DiscSetNum < 0 || DiscSetNum >= mSetList.size()) + { + THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) + } + + return mSetList[DiscSetNum]; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &) +// Purpose: Returns the set number the 'temporary' written files should +// be stored on, given a filename. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +int RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &rFilename) const +{ + // Simple hash function, add up the ASCII values of all the characters, + // and get modulo number of partitions in the set. + std::string::const_iterator i(rFilename.begin()); + int h = 0; + for(; i != rFilename.end(); ++i) + { + h += (*i); + } + return h % size(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::DiscSetPathToFileSystemPath(unsigned int, const std::string &, int) +// Purpose: Given a Raid File style file name, return a filename for the physical filing system. +// DiscOffset is effectively the disc number (but remember files are rotated around the +// discs in a disc set) +// Created: 19/1/04 +// +// -------------------------------------------------------------------------- +std::string RaidFileController::DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset) +{ + if(DiscSetNum < 0 || DiscSetNum >= mController.mSetList.size()) + { + THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) + } + + // Work out which disc it's to be on + int disc = (mController.mSetList[DiscSetNum].GetSetNumForWriteFiles(rFilename) + DiscOffset) + % mController.mSetList[DiscSetNum].size(); + + // Make the string + std::string r((mController.mSetList[DiscSetNum])[disc]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + return r; +} + + + diff --git a/lib/raidfile/RaidFileController.h b/lib/raidfile/RaidFileController.h new file mode 100644 index 00000000..216bdf3a --- /dev/null +++ b/lib/raidfile/RaidFileController.h @@ -0,0 +1,108 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileController.h +// Purpose: Controls config and daemon comms for RaidFile classes +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +/* NOTE: will log to local5: include a line like + local5.info /var/log/raidfile + in /etc/syslog.conf +*/ + +#ifndef RAIDFILECONTROLLER__H +#define RAIDFILECONTROLLER__H + +#include +#include + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileDiscSet +// Purpose: Describes a set of paritions for RAID like files. +// Use as list of directories containing the files. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +class RaidFileDiscSet : public std::vector +{ +public: + RaidFileDiscSet(int SetID, unsigned int BlockSize) + : mSetID(SetID), + mBlockSize(BlockSize) + { + } + RaidFileDiscSet(const RaidFileDiscSet &rToCopy) + : std::vector(rToCopy), + mSetID(rToCopy.mSetID), + mBlockSize(rToCopy.mBlockSize) + { + } + + ~RaidFileDiscSet() + { + } + + int GetSetID() const {return mSetID;} + + int GetSetNumForWriteFiles(const std::string &rFilename) const; + + unsigned int GetBlockSize() const {return mBlockSize;} + + // Is this disc set a non-RAID disc set? (ie files never get transformed to raid storage) + bool IsNonRaidSet() const {return 1 == size();} + +private: + int mSetID; + unsigned int mBlockSize; +}; + +class _RaidFileController; // compiler warning avoidance + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileController +// Purpose: Manages the configuration of the RaidFile system, handles +// communication with the daemon. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +class RaidFileController +{ + friend class _RaidFileController; // to avoid compiler warning +private: + RaidFileController(); + RaidFileController(const RaidFileController &rController); +public: + ~RaidFileController(); + +public: + void Initialise(const std::string& rConfigFilename = + "/etc/boxbackup/raidfile.conf"); + int GetNumDiscSets() {return mSetList.size();} + + // -------------------------------------------------------------------------- + // + // Function + // Name: RaidFileController::GetController() + // Purpose: Gets the one and only controller object. + // Created: 2003/07/08 + // + // -------------------------------------------------------------------------- + static RaidFileController &GetController() {return mController;} + RaidFileDiscSet &GetDiscSet(unsigned int DiscSetNum); + + static std::string DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset); + +private: + std::vector mSetList; + + static RaidFileController mController; +}; + +#endif // RAIDFILECONTROLLER__H + diff --git a/lib/raidfile/RaidFileException.h b/lib/raidfile/RaidFileException.h new file mode 100644 index 00000000..809d4d5a --- /dev/null +++ b/lib/raidfile/RaidFileException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEEXCEPTION__H +#define RAIDFILEEXCEPTION__H + +// Compatibility +#include "autogen_RaidFileException.h" + +#endif // RAIDFILEEXCEPTION__H + diff --git a/lib/raidfile/RaidFileException.txt b/lib/raidfile/RaidFileException.txt new file mode 100644 index 00000000..c7ddfcc5 --- /dev/null +++ b/lib/raidfile/RaidFileException.txt @@ -0,0 +1,28 @@ +EXCEPTION RaidFile 2 + +Internal 0 +CantOpenConfigFile 1 The raidfile.conf file is not accessible. Check that it is present in the default location or daemon configuration files point to the correct location. +BadConfigFile 2 +NoSuchDiscSet 3 +CannotOverwriteExistingFile 4 +AlreadyOpen 5 +ErrorOpeningWriteFile 6 +NotOpen 7 +OSError 8 Error when accessing an underlying file. Check file permissions allow files to be read and written in the configured raid directories. +WriteFileOpenOnTransform 9 +WrongNumberOfDiscsInSet 10 There should be three directories in each disc set. +RaidFileDoesntExist 11 Error when accessing a file on the store. Check the store with bbstoreaccounts check. +ErrorOpeningFileForRead 12 +FileIsDamagedNotRecoverable 13 +InvalidRaidFile 14 +DirectoryIncomplete 15 +UnexpectedFileInDirPlace 16 +FileExistsInDirectoryCreation 17 +UnsupportedReadWriteOrClose 18 +CanOnlyGetUsageBeforeCommit 19 +CanOnlyGetFileSizeBeforeCommit 20 +ErrorOpeningWriteFileOnTruncate 21 +FileIsCurrentlyOpenForWriting 22 +RequestedModifyUnreferencedFile 23 Internal error: the server attempted to modify a file which has no references. +RequestedModifyMultiplyReferencedFile 24 Internal error: the server attempted to modify a file which has multiple references. +RequestedDeleteReferencedFile 25 Internal error: the server attempted to delete a file which is still referenced. diff --git a/lib/raidfile/RaidFileRead.cpp b/lib/raidfile/RaidFileRead.cpp new file mode 100644 index 00000000..0a79be57 --- /dev/null +++ b/lib/raidfile/RaidFileRead.cpp @@ -0,0 +1,1724 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileRead.cpp +// Purpose: Read Raid like Files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_SYS_UIO_H + #include +#endif + +#ifdef HAVE_DIRENT_H + #include +#endif + +#include +#include +#include +#include +#include + +#include "RaidFileRead.h" +#include "RaidFileException.h" +#include "RaidFileController.h" +#include "RaidFileUtil.h" + +#include "MemLeakFindOn.h" + +#define READ_NUMBER_DISCS_REQUIRED 3 +#define READV_MAX_BLOCKS 64 + +// We want to use POSIX fstat() for now, not the emulated one +#undef fstat + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead_NonRaid +// Purpose: Internal class for reading RaidFiles which haven't been transformed +// into the RAID like form yet. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead_NonRaid : public RaidFileRead +{ +public: + RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle); + virtual ~RaidFileRead_NonRaid(); +private: + RaidFileRead_NonRaid(const RaidFileRead_NonRaid &rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + virtual pos_type GetFileSize() const; + virtual bool StreamDataLeft(); + +private: + int mOSFileHandle; + bool mEOF; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid(int, const std::string &, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_NonRaid::RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle) + : RaidFileRead(SetNumber, Filename), + mOSFileHandle(OSFileHandle), + mEOF(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::~RaidFileRead_NonRaid() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_NonRaid::~RaidFileRead_NonRaid() +{ + if(mOSFileHandle != -1) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Read(const void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +int RaidFileRead_NonRaid::Read(void *pBuffer, int NBytes, int Timeout) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Read data + int bytesRead = ::read(mOSFileHandle, pBuffer, NBytes); + if(bytesRead == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Check for EOF + if(bytesRead == 0) + { + mEOF = true; + } + + return bytesRead; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::GetPosition() +// Purpose: Returns current position +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_NonRaid::GetPosition() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Use lseek to find the current file position + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return p; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Seek(pos_type, int) +// Purpose: Seek within the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_NonRaid::Seek(IOStream::pos_type Offset, int SeekType) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Seek... + if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Not EOF any more + mEOF = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Close() +// Purpose: Close the file (automatically done by destructor) +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_NonRaid::Close() +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + mOSFileHandle = -1; + mEOF = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::GetFileSize() +// Purpose: Returns file size. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_NonRaid::GetFileSize() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // stat the file + struct stat st; + if(::fstat(mOSFileHandle, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return st.st_size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::StreamDataLeft() +// Purpose: Any data left? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead_NonRaid::StreamDataLeft() +{ + return !mEOF; +} + +// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead_Raid +// Purpose: Internal class for reading RaidFiles have been transformed. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead_Raid : public RaidFileRead +{ +public: + friend class RaidFileRead; + RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, + int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, + bool LastBlockHasSize); + virtual ~RaidFileRead_Raid(); +private: + RaidFileRead_Raid(const RaidFileRead_Raid &rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + virtual pos_type GetFileSize() const; + virtual bool StreamDataLeft(); + +private: + int ReadRecovered(void *pBuffer, int NBytes); + void AttemptToRecoverFromIOError(bool Stripe1); + void SetPosition(pos_type FilePosition); + static void MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1); + +private: + int mStripe1Handle; + int mStripe2Handle; + int mParityHandle; + pos_type mFileSize; + unsigned int mBlockSize; + pos_type mCurrentPosition; + char *mRecoveryBuffer; + pos_type mRecoveryBufferStart; + bool mLastBlockHasSize; + bool mEOF; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid(int, const std::string &, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_Raid::RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, bool LastBlockHasSize) + : RaidFileRead(SetNumber, Filename), + mStripe1Handle(Stripe1Handle), + mStripe2Handle(Stripe2Handle), + mParityHandle(ParityHandle), + mFileSize(FileSize), + mBlockSize(BlockSize), + mCurrentPosition(0), + mRecoveryBuffer(0), + mRecoveryBufferStart(-1), + mLastBlockHasSize(LastBlockHasSize), + mEOF(false) +{ + // Make sure size of the IOStream::pos_type matches the pos_type used + ASSERT(sizeof(pos_type) >= sizeof(off_t)); + + // Sanity check handles + if(mStripe1Handle != -1 && mStripe2Handle != -1) + { + // Everything is lovely, got two perfect files + } + else + { + // Check we have at least one stripe and a parity file + if((mStripe1Handle == -1 && mStripe2Handle == -1) || mParityHandle == -1) + { + // Should never have got this far + THROW_EXCEPTION(RaidFileException, Internal) + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::~RaidFileRead_Raid() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_Raid::~RaidFileRead_Raid() +{ + Close(); + if(mRecoveryBuffer != 0) + { + ::free(mRecoveryBuffer); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Read(const void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +int RaidFileRead_Raid::Read(void *pBuffer, int NBytes, int Timeout) +{ + // How many more bytes could we read? + unsigned int maxRead = mFileSize - mCurrentPosition; + if((unsigned int)NBytes > maxRead) + { + NBytes = maxRead; + } + + // Return immediately if there's nothing to read, and set EOF + if(NBytes == 0) + { + mEOF = true; + return 0; + } + + // Can we use the normal file reading routine? + if(mStripe1Handle == -1 || mStripe2Handle == -1) + { + // File is damaged, try a the recovery read function + return ReadRecovered(pBuffer, NBytes); + } + + // Vectors for reading stuff from the files + struct iovec stripe1Reads[READV_MAX_BLOCKS]; + struct iovec stripe2Reads[READV_MAX_BLOCKS]; + struct iovec *stripeReads[2] = {stripe1Reads, stripe2Reads}; + unsigned int stripeReadsDataSize[2] = {0, 0}; + unsigned int stripeReadsSize[2] = {0, 0}; + int stripeHandles[2] = {mStripe1Handle, mStripe2Handle}; + + // Which block are we doing? + unsigned int currentBlock = mCurrentPosition / mBlockSize; + unsigned int bytesLeftInCurrentBlock = mBlockSize - (mCurrentPosition % mBlockSize); + ASSERT(bytesLeftInCurrentBlock > 0) + unsigned int leftToRead = NBytes; + char *bufferPtr = (char*)pBuffer; + + // Now... add some whole block entries in... + try + { + while(leftToRead > 0) + { + int whichStripe = (currentBlock & 1); + size_t rlen = mBlockSize; + // Adjust if it's the first block + if(bytesLeftInCurrentBlock != 0) + { + rlen = bytesLeftInCurrentBlock; + bytesLeftInCurrentBlock = 0; + } + // Adjust if we're out of bytes + if(rlen > leftToRead) + { + rlen = leftToRead; + } + stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_base = bufferPtr; + stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_len = rlen; + stripeReadsSize[whichStripe]++; + stripeReadsDataSize[whichStripe] += rlen; + leftToRead -= rlen; + bufferPtr += rlen; + currentBlock++; + + // Read data? + for(int s = 0; s < 2; ++s) + { + if((leftToRead == 0 || stripeReadsSize[s] >= READV_MAX_BLOCKS) && stripeReadsSize[s] > 0) + { + int r = ::readv(stripeHandles[s], stripeReads[s], stripeReadsSize[s]); + if(r == -1) + { + // Bad news... IO error? + if(errno == EIO) + { + // Attempt to recover from this failure + AttemptToRecoverFromIOError((s == 0) /* is stripe 1 */); + // Retry + return Read(pBuffer, NBytes, Timeout); + } + else + { + // Can't do anything, throw + THROW_EXCEPTION(RaidFileException, OSError) + } + } + else if(r != (int)stripeReadsDataSize[s]) + { + // Got the file sizes wrong/logic error! + THROW_EXCEPTION(RaidFileException, Internal) + } + stripeReadsSize[s] = 0; + stripeReadsDataSize[s] = 0; + } + } + } + } + catch(...) + { + // Get file pointers to right place (to meet exception safe stuff) + SetPosition(mCurrentPosition); + + throw; + } + + // adjust current position + mCurrentPosition += NBytes; + + return NBytes; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::MoveDamagedFileAlertDaemon(bool) +// Purpose: Moves a file into the damaged directory, and alerts the Daemon to recover it properly later. +// Created: 2003/07/22 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1) +{ + // Move the dodgy file away + // Get the controller and the disc set we're on + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + // Start disc + int startDisc = rdiscSet.GetSetNumForWriteFiles(Filename); + int errOnDisc = (startDisc + (Stripe1?0:1)) % READ_NUMBER_DISCS_REQUIRED; + + // Make a munged filename for renaming + std::string mungeFn(Filename + RAIDFILE_EXTENSION); + std::string awayName; + for(std::string::const_iterator i = mungeFn.begin(); i != mungeFn.end(); ++i) + { + char c = (*i); + if(c == DIRECTORY_SEPARATOR_ASCHAR) + { + awayName += '_'; + } + else if(c == '_') + { + awayName += "__"; + } + else + { + awayName += c; + } + } + // Make sure the error files directory exists + std::string dirname(rdiscSet[errOnDisc] + DIRECTORY_SEPARATOR ".raidfile-unreadable"); + int mdr = ::mkdir(dirname.c_str(), 0750); + if(mdr != 0 && errno != EEXIST) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Attempt to rename the file there -- ignore any return code here, as it's dubious anyway + std::string errorFile(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, errOnDisc)); + ::rename(errorFile.c_str(), (dirname + DIRECTORY_SEPARATOR_ASCHAR + awayName).c_str()); + + // TODO: Inform the recovery daemon +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::AttemptToRecoverFromIOError(bool) +// Purpose: Attempt to recover from an IO error, setting up to read from parity instead. +// Will exception if this isn't possible. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::AttemptToRecoverFromIOError(bool Stripe1) +{ + BOX_WARNING("Attempting to recover from I/O error: " << mSetNumber << + " " << mFilename << ", on stripe " << (Stripe1?1:2)); + + // Close offending file + if(Stripe1) + { + if(mStripe1Handle != -1) + { + ::close(mStripe1Handle); + mStripe1Handle = -1; + } + } + else + { + if(mStripe2Handle != -1) + { + ::close(mStripe2Handle); + mStripe2Handle = -1; + } + } + + // Check... + ASSERT((Stripe1?mStripe2Handle:mStripe1Handle) != -1); + + // Get rid of the damaged file + MoveDamagedFileAlertDaemon(mSetNumber, mFilename, Stripe1); + + // Get the controller and the disc set we're on + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + // Start disc + int startDisc = rdiscSet.GetSetNumForWriteFiles(mFilename); + + // Mark as nothing in recovery buffer + mRecoveryBufferStart = -1; + + // Seek to zero on the remaining file -- get to nice state + if(::lseek(Stripe1?mStripe2Handle:mStripe1Handle, 0, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Open the parity file + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + mParityHandle = ::open(parityFilename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(mParityHandle == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Work out whether or not there's a size XORed into the last block + unsigned int bytesInLastTwoBlocks = mFileSize % (mBlockSize * 2); + if(bytesInLastTwoBlocks > mBlockSize && bytesInLastTwoBlocks < ((mBlockSize * 2) - sizeof(FileSizeType))) + { + // Yes, there's something to XOR in the last block + mLastBlockHasSize = true; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::ReadRecovered(const void *, int) +// Purpose: Reads data recreating from the parity stripe +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +int RaidFileRead_Raid::ReadRecovered(void *pBuffer, int NBytes) +{ + // Note: NBytes has been adjusted to definately be a range + // inside the given file length. + + // Make sure a buffer is allocated + if(mRecoveryBuffer == 0) + { + mRecoveryBuffer = (char*)::malloc(mBlockSize * 2); + if(mRecoveryBuffer == 0) + { + throw std::bad_alloc(); + } + } + + // Which stripe? + int stripe = (mStripe1Handle != -1)?mStripe1Handle:mStripe2Handle; + if(stripe == -1) + { + // Not enough file handles around + THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable) + } + + char *outptr = (char*)pBuffer; + int bytesToGo = NBytes; + + pos_type preservedCurrentPosition = mCurrentPosition; + + try + { + // Start offset within buffer + int offset = (mCurrentPosition - mRecoveryBufferStart); + // Let's go! + while(bytesToGo > 0) + { + int bytesLeftInBuffer = 0; + if(mRecoveryBufferStart != -1) + { + bytesLeftInBuffer = (mRecoveryBufferStart + (mBlockSize*2)) - mCurrentPosition; + ASSERT(bytesLeftInBuffer >= 0); + } + + // How many bytes can be copied out? + int toCopy = bytesLeftInBuffer; + if(toCopy > bytesToGo) toCopy = bytesToGo; + //printf("offset = %d, tocopy = %d, bytestogo = %d, leftinbuffer = %d\n", (int)offset, toCopy, bytesToGo, bytesLeftInBuffer); + if(toCopy > 0) + { + for(int l = 0; l < toCopy; ++l) + { + *(outptr++) = mRecoveryBuffer[offset++]; + } + bytesToGo -= toCopy; + mCurrentPosition += toCopy; + } + + // Load in the next buffer? + if(bytesToGo > 0) + { + // Calculate the blocks within the file that are needed to be loaded. + pos_type fileBlock = mCurrentPosition / (mBlockSize * 2); + // Is this the last block + bool isLastBlock = (fileBlock == (mFileSize / (mBlockSize * 2))); + + // Need to reposition file pointers? + if(mRecoveryBufferStart == -1) + { + // Yes! + // And the offset from which to read it + pos_type filePos = fileBlock * mBlockSize; + // Then seek + if(::lseek(stripe, filePos, SEEK_SET) == -1 + || ::lseek(mParityHandle, filePos, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Load a block from each file, getting the ordering the right way round + int r1 = ::read((mStripe1Handle != -1)?stripe:mParityHandle, mRecoveryBuffer, mBlockSize); + int r2 = ::read((mStripe1Handle != -1)?mParityHandle:stripe, mRecoveryBuffer + mBlockSize, mBlockSize); + if(r1 == -1 || r2 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // error checking and manipulation + if(isLastBlock) + { + // Allow not full reads, and append zeros if necessary to fill the space. + int r1zeros = mBlockSize - r1; + if(r1zeros > 0) + { + ::memset(mRecoveryBuffer + r1, 0, r1zeros); + } + int r2zeros = mBlockSize - r2; + if(r2zeros > 0) + { + ::memset(mRecoveryBuffer + mBlockSize + r2, 0, r2zeros); + } + + // if it's got the file size in it, XOR it off + if(mLastBlockHasSize) + { + int sizeXorOffset = (mBlockSize - sizeof(FileSizeType)) + ((mStripe1Handle != -1)?mBlockSize:0); + *((FileSizeType*)(mRecoveryBuffer + sizeXorOffset)) ^= box_ntoh64(mFileSize); + } + } + else + { + // Must have got a full block, otherwise things are a bit bad here. + if(r1 != (int)mBlockSize || r2 != (int)mBlockSize) + { + THROW_EXCEPTION(RaidFileException, InvalidRaidFile) + } + } + + // Go XORing! + unsigned int *b1 = (unsigned int*)mRecoveryBuffer; + unsigned int *b2 = (unsigned int *)(mRecoveryBuffer + mBlockSize); + if((mStripe1Handle == -1)) + { + b1 = b2; + b2 = (unsigned int*)mRecoveryBuffer; + } + for(int x = ((mBlockSize/sizeof(unsigned int)) - 1); x >= 0; --x) + { + *b2 = (*b1) ^ (*b2); + ++b1; + ++b2; + } + + // New block location + mRecoveryBufferStart = fileBlock * (mBlockSize * 2); + + // New offset withing block + offset = (mCurrentPosition - mRecoveryBufferStart); + ASSERT(offset >= 0); + } + } + } + catch(...) + { + // Change variables so 1) buffer is invalidated and 2) the file will be seeked properly the next time round + mRecoveryBufferStart = -1; + mCurrentPosition = preservedCurrentPosition; + throw; + } + + return NBytes; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::GetPosition() +// Purpose: Returns current position +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead_Raid::GetPosition() const +{ + return mCurrentPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Seek(RaidFileRead::pos_type, bool) +// Purpose: Seek within the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::Seek(IOStream::pos_type Offset, int SeekType) +{ + pos_type newpos = mCurrentPosition; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newpos = Offset; + break; + + case IOStream::SeekType_Relative: + newpos += Offset; + break; + + case IOStream::SeekType_End: + newpos = mFileSize + Offset; + break; + + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + } + + if(newpos != mCurrentPosition) + { + SetPosition(newpos); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::SetPosition(pos_type) +// Purpose: Move the file pointers +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::SetPosition(pos_type FilePosition) +{ + if(FilePosition > mFileSize) + { + FilePosition = mFileSize; + } + + if(mStripe1Handle != -1 && mStripe2Handle != -1) + { + // right then... which block is it in? + pos_type block = FilePosition / mBlockSize; + pos_type offset = FilePosition % mBlockSize; + + // Calculate offsets for each file + pos_type basepos = (block / 2) * mBlockSize; + pos_type s1p, s2p; + if((block & 1) == 0) + { + s1p = basepos + offset; + s2p = basepos; + } + else + { + s1p = basepos + mBlockSize; + s2p = basepos + offset; + } + // Note: lseek isn't in the man pages to return EIO, but assuming that it can return this, + // as it calls various OS bits and returns their error codes, and those fns look like they might. + if(::lseek(mStripe1Handle, s1p, SEEK_SET) == -1) + { + if(errno == EIO) + { + BOX_ERROR("I/O error when seeking in " << + mSetNumber << " " << mFilename << + " (to " << FilePosition << "), " << + "stripe 1"); + // Attempt to recover + AttemptToRecoverFromIOError(true /* is stripe 1 */); + ASSERT(mStripe1Handle == -1); + // Retry + SetPosition(FilePosition); + return; + } + else + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + if(::lseek(mStripe2Handle, s2p, SEEK_SET) == -1) + { + if(errno == EIO) + { + BOX_ERROR("I/O error when seeking in " << + mSetNumber << " " << mFilename << + " (to " << FilePosition << "), " << + "stripe 2"); + // Attempt to recover + AttemptToRecoverFromIOError(false /* is stripe 2 */); + ASSERT(mStripe2Handle == -1); + // Retry + SetPosition(FilePosition); + return; + } + else + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Store position + mCurrentPosition = FilePosition; + } + else + { + // Simply store, and mark the recovery buffer invalid + mCurrentPosition = FilePosition; + mRecoveryBufferStart = -1; + } + + // not EOF any more + mEOF = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Close() +// Purpose: Close the file (automatically done by destructor) +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::Close() +{ + if(mStripe1Handle != -1) + { + ::close(mStripe1Handle); + mStripe1Handle = -1; + } + if(mStripe2Handle != -1) + { + ::close(mStripe2Handle); + mStripe2Handle = -1; + } + if(mParityHandle != -1) + { + ::close(mParityHandle); + mParityHandle = -1; + } + + mEOF = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::StreamDataLeft() +// Purpose: Any data left? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead_Raid::StreamDataLeft() +{ + return !mEOF; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::GetFileSize() +// Purpose: Returns file size. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_Raid::GetFileSize() const +{ + return mFileSize; +} + + +// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::RaidFileRead(int, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::RaidFileRead(int SetNumber, const std::string &Filename) + : mSetNumber(SetNumber), + mFilename(Filename) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::~RaidFileRead() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::~RaidFileRead() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::Open(int, const std::string &, int) +// Purpose: Opens a RaidFile for reading. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +std::auto_ptr RaidFileRead::Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID, int BufferSizeHint) +{ + // See what's available... + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size() && 1 != rdiscSet.size()) // allow non-RAID configurations + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + + // See if the file exists + int startDisc = 0, existingFiles = 0; + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, Filename, &startDisc, &existingFiles, pRevisionID); + if(existance == RaidFileUtil::NoFile) + { + BOX_ERROR("Expected raidfile " << Filename << " does not exist"); + THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist) + } + else if(existance == RaidFileUtil::NonRaid) + { + // Simple non-RAID file so far... + + // Get the filename for the write file + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, Filename)); + + // Attempt to open + int osFileHandle = ::open(writeFilename.c_str(), + O_RDONLY | O_BINARY, 0); + if(osFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead) + } + + // Return a read object for this file + try + { + return std::auto_ptr(new RaidFileRead_NonRaid(SetNumber, Filename, osFileHandle)); + } + catch(...) + { + ::close(osFileHandle); + throw; + } + } + else if(existance == RaidFileUtil::AsRaid + || ((existingFiles & RaidFileUtil::Stripe1Exists) && (existingFiles & RaidFileUtil::Stripe2Exists))) + { + if(existance != RaidFileUtil::AsRaid) + { + BOX_ERROR("Opening " << SetNumber << " " << + Filename << " in normal mode, but " + "parity file doesn't exist"); + // TODO: Alert recovery daemon + } + + // Open the two stripe files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + int stripe1 = -1; + int stripe1errno = 0; + int stripe2 = -1; + int stripe2errno = 0; + + try + { + // Open stripe1 + stripe1 = ::open(stripe1Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe1 == -1) + { + stripe1errno = errno; + } + // Open stripe2 + stripe2 = ::open(stripe2Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe2 == -1) + { + stripe2errno = errno; + } + if(stripe1errno != 0 || stripe2errno != 0) + { + THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead) + } + + // stat stripe 1 to find ('half' of) length... + struct stat st; + if(::fstat(stripe1, &st) != 0) + { + stripe1errno = errno; + } + pos_type length = st.st_size; + + // stat stripe2 to find (other 'half' of) length... + if(::fstat(stripe2, &st) != 0) + { + stripe2errno = errno; + } + length += st.st_size; + + // Handle errors + if(stripe1errno != 0 || stripe2errno != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Make a nice object to represent this file + return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, -1, length, rdiscSet.GetBlockSize(), false /* actually we don't know */)); + } + catch(...) + { + // Close open files + if(stripe1 != -1) + { + ::close(stripe1); + stripe1 = -1; + } + if(stripe2 != -1) + { + ::close(stripe2); + stripe2 = -1; + } + + // Now... maybe we can try again with one less file? + bool oktotryagain = true; + if(stripe1errno == EIO) + { + BOX_ERROR("I/O error on opening " << + SetNumber << " " << Filename << + " stripe 1, trying recovery mode"); + RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, true /* is stripe 1 */); + + existingFiles = existingFiles & ~RaidFileUtil::Stripe1Exists; + existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) + ?RaidFileUtil::AsRaidWithMissingNotRecoverable + :RaidFileUtil::AsRaidWithMissingReadable; + } + else if(stripe1errno != 0) + { + oktotryagain = false; + } + + if(stripe2errno == EIO) + { + BOX_ERROR("I/O error on opening " << + SetNumber << " " << Filename << + " stripe 2, trying recovery mode"); + RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, false /* is stripe 2 */); + + existingFiles = existingFiles & ~RaidFileUtil::Stripe2Exists; + existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) + ?RaidFileUtil::AsRaidWithMissingNotRecoverable + :RaidFileUtil::AsRaidWithMissingReadable; + } + else if(stripe2errno != 0) + { + oktotryagain = false; + } + + if(!oktotryagain) + { + throw; + } + } + } + + if(existance == RaidFileUtil::AsRaidWithMissingReadable) + { + BOX_ERROR("Attempting to open RAID file " << SetNumber << + " " << Filename << " in recovery mode (stripe " << + ((existingFiles & RaidFileUtil::Stripe1Exists)?1:2) << + " present)"); + + // Generate the filenames of all the lovely files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + + int stripe1 = -1; + int stripe2 = -1; + int parity = -1; + + try + { + // Open stripe1? + if(existingFiles & RaidFileUtil::Stripe1Exists) + { + stripe1 = ::open(stripe1Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe1 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + // Open stripe2? + if(existingFiles & RaidFileUtil::Stripe2Exists) + { + stripe2 = ::open(stripe2Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe2 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + // Open parity + parity = ::open(parityFilename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(parity == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Find the length. This is slightly complex. + unsigned int blockSize = rdiscSet.GetBlockSize(); + pos_type length = 0; + + // The easy one... if the parity file is of an integral block size + sizeof(FileSizeType) + // then it's stored at the end of the parity file + struct stat st; + if(::fstat(parity, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + pos_type paritySize = st.st_size; + FileSizeType parityLastData = 0; + bool parityIntegralPlusOffT = ((paritySize % blockSize) == sizeof(FileSizeType)); + if(paritySize >= static_cast(sizeof(parityLastData)) && (parityIntegralPlusOffT || stripe1 != -1)) + { + // Seek to near the end + ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... + if(::lseek(parity, -8 /*(0 - sizeof(FileSizeType))*/, SEEK_END) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Read it in + if(::read(parity, &parityLastData, sizeof(parityLastData)) != sizeof(parityLastData)) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Set back to beginning of file + if(::lseek(parity, 0, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + bool lastBlockHasSize = false; + if(parityIntegralPlusOffT) + { + // Wonderful! Have the value + length = box_ntoh64(parityLastData); + } + else + { + // Have to resort to more devious means. + if(existingFiles & RaidFileUtil::Stripe1Exists) + { + // Procedure for stripe 1 existence... + // Get size of stripe1. + // If this is not an integral block size, then size can use this + // to work out the size of the file. + // Otherwise, read in the end of the last block, and use a bit of XORing + // to get the size from the FileSizeType value at end of the file. + if(::fstat(stripe1, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + pos_type stripe1Size = st.st_size; + // Is size integral? + if((stripe1Size % ((pos_type)blockSize)) != 0) + { + // No, so know the size. + length = stripe1Size + ((stripe1Size / blockSize) * blockSize); + } + else + { + // Must read the last bit of data from the block and XOR. + FileSizeType stripe1LastData = 0; // initialise to zero, as we may not read everything from it + + // Work out how many bytes to read + int btr = 0; // bytes to read off end + unsigned int lbs = stripe1Size % blockSize; + if(lbs == 0 && stripe1Size > 0) + { + // integral size, need the entire bit + btr = sizeof(FileSizeType); + } + else if(lbs > (blockSize - sizeof(FileSizeType))) + { + btr = lbs - (blockSize - sizeof(FileSizeType)); + } + + // Seek to near the end + if(btr > 0) + { + ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... + ASSERT(btr <= (int)sizeof(FileSizeType)); + if(::lseek(stripe1, 0 - btr, SEEK_END) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Read it in + if(::read(stripe1, &stripe1LastData, btr) != btr) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Set back to beginning of file + if(::lseek(stripe1, 0, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + // Lovely! + length = stripe1LastData ^ parityLastData; + // Convert to host byte order + length = box_ntoh64(length); + ASSERT(length <= (paritySize + stripe1Size)); + // Mark is as having this to aid code later + lastBlockHasSize = true; + } + } + else + { + ASSERT(existingFiles & RaidFileUtil::Stripe2Exists); + } + + if(existingFiles & RaidFileUtil::Stripe2Exists) + { + // Get size of stripe2 file + if(::fstat(stripe2, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + pos_type stripe2Size = st.st_size; + + // Is it an integral size? + if(stripe2Size % blockSize != 0) + { + // No. Working out the size is easy. + length = stripe2Size + (((stripe2Size / blockSize)+1) * blockSize); + // Got last block size in there? + if((stripe2Size % blockSize) <= static_cast((blockSize - sizeof(pos_type)))) + { + // Yes... + lastBlockHasSize = true; + } + } + else + { + // Yes. So we need to compare with the parity file to get a clue... + pos_type stripe2Blocks = stripe2Size / blockSize; + pos_type parityBlocks = paritySize / blockSize; + if(stripe2Blocks == parityBlocks) + { + // Same size, so stripe1 must be the same size + length = (stripe2Blocks * 2) * blockSize; + } + else + { + // Different size, so stripe1 must be one block bigger + ASSERT(stripe2Blocks < parityBlocks); + length = ((stripe2Blocks * 2)+1) * blockSize; + } + + // Then... add in the extra bit of the parity length + unsigned int lastBlockSize = paritySize % blockSize; + length += lastBlockSize; + } + } + else + { + ASSERT(existingFiles & RaidFileUtil::Stripe1Exists); + } + } + + // Create a lovely object to return + return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, parity, length, blockSize, lastBlockHasSize)); + } + catch(...) + { + // Close open files + if(stripe1 != -1) + { + ::close(stripe1); + stripe1 = -1; + } + if(stripe2 != -1) + { + ::close(stripe2); + stripe2 = -1; + } + if(parity != -1) + { + ::close(parity); + parity = -1; + } + throw; + } + } + + THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable) + + // Avoid compiler warning -- it'll never get here... + return std::auto_ptr(); +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::DirectoryExists(int, const std::string &) +// Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::DirectoryExists(int SetNumber, const std::string &rDirName) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + return DirectoryExists(rdiscSet, rDirName); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::DirectoryExists(const RaidFileDiscSet &, const std::string &) +// Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName) +{ + // For each directory, test to see if it exists + unsigned int nexist = 0; + for(unsigned int l = 0; l < rSet.size(); ++l) + { + // build name + std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // check for existence + struct stat st; + if(::stat(dn.c_str(), &st) == 0) + { + // Directory? + if(st.st_mode & S_IFDIR) + { + // yes + nexist++; + } + else + { + // No. It's a file. Bad! + THROW_EXCEPTION(RaidFileException, UnexpectedFileInDirPlace) + } + } + else + { + // Was it a non-exist error? + if(errno != ENOENT) + { + // No. Bad things. + THROW_EXCEPTION(RaidFileException, OSError) + } + } + } + + // Were all of them found? + if(nexist == 0) + { + // None. + return false; + } + else if(nexist == rSet.size()) + { + // All + return true; + } + + // Some exist. We don't like this -- it shows something bad happened before + // TODO: notify recovery daemon + THROW_EXCEPTION(RaidFileException, DirectoryIncomplete) + return false; // avoid compiler warning +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::FileExists(int, const std::string &, int64_t *) +// Purpose: Does a Raid file exist? Optionally return a revision number, which is +// unique to this saving of the file. (revision number may change +// after transformation to RAID -- so only use for cache control, +// not detecting changes to content). +// Created: 2003/09/02 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + return RaidFileUtil::RaidFileExists(rdiscSet, rFilename, 0, 0, pRevisionID) != RaidFileUtil::NoFile; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadDirectoryContents(int, const std::string &, int, std::vector &) +// Purpose: Read directory contents, returning whether or not all entries are likely to be readable or not +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput) +{ + // Remove anything in the vector to begin with. + rOutput.clear(); + + // Controller and set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + // Collect the directory listings + std::map counts; + + unsigned int numDiscs = rdiscSet.size(); + + for(unsigned int l = 0; l < numDiscs; ++l) + { + // build name + std::string dn(rdiscSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // read the contents... + DIR *dirHandle = 0; + try + { + dirHandle = ::opendir(dn.c_str()); + if(dirHandle == 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + struct dirent *en = 0; + while((en = ::readdir(dirHandle)) != 0) + { + if(en->d_name[0] == '.' && + (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) + { + // ignore, it's . or .. + continue; + } + + // Entry... + std::string name; + unsigned int countToAdd = 1; + + // stat the file to find out what type it is +#ifdef HAVE_VALID_DIRENT_D_TYPE + if(DirReadType == DirReadType_FilesOnly && en->d_type == DT_REG) +#else + struct stat st; + std::string fullName(dn + DIRECTORY_SEPARATOR + en->d_name); + if(::lstat(fullName.c_str(), &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + if(DirReadType == DirReadType_FilesOnly && (st.st_mode & S_IFDIR) == 0) +#endif + { + // File. Complex, need to check the extension + int dot = -1; + int p = 0; + while(en->d_name[p] != '\0') + { + if(en->d_name[p] == '.') + { + // store location of dot + dot = p; + } + ++p; + } + // p is length of string + if(dot != -1 && ((p - dot) == 3 || (p - dot) == 4) + && en->d_name[dot+1] == 'r' && en->d_name[dot+2] == 'f' + && (en->d_name[dot+3] == 'w' || en->d_name[dot+3] == '\0')) + { + // so has right extension + name.assign(en->d_name, dot); /* get name up to last . */ + // Was it a write file (which counts as everything) + if(en->d_name[dot+3] == 'w') + { + countToAdd = numDiscs; + } + } + } +#ifdef HAVE_VALID_DIRENT_D_TYPE + if(DirReadType == DirReadType_DirsOnly && en->d_type == DT_DIR) +#else + if(DirReadType == DirReadType_DirsOnly && (st.st_mode & S_IFDIR)) +#endif + { + // Directory, and we want directories + name = en->d_name; + } + // Eligable for entry? + if(!name.empty()) + { + // add to map... + std::map::iterator i = counts.find(name); + if(i != counts.end()) + { + // add to count + i->second += countToAdd; + } + else + { + // insert into map + counts[name] = countToAdd; + } + } + } + + if(::closedir(dirHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + dirHandle = 0; + } + catch(...) + { + if(dirHandle != 0) + { + ::closedir(dirHandle); + } + throw; + } + } + + // Now go through the map, adding in entries + bool everythingReadable = true; + + for(std::map::const_iterator i = counts.begin(); i != counts.end(); ++i) + { + if(i->second < (numDiscs - 1)) + { + // Too few discs to be confident of reading everything + everythingReadable = false; + } + + // Add name to vector + rOutput.push_back(i->first); + } + + return everythingReadable; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::Write(const void *, int) +// Purpose: Not support, throws exception +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void RaidFileRead::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::StreamClosed() +// Purpose: Never any data to write +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::StreamClosed() +{ + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::BytesLeftToRead() +// Purpose: Can tell how many bytes there are to go +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead::BytesLeftToRead() +{ + return GetFileSize() - GetPosition(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::GetDiscUsageInBlocks() +// Purpose: Return how many blocks are used. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead::GetDiscUsageInBlocks() +{ + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + return RaidFileUtil::DiscUsageInBlocks(GetFileSize(), rdiscSet); +} + + + + diff --git a/lib/raidfile/RaidFileRead.h b/lib/raidfile/RaidFileRead.h new file mode 100644 index 00000000..8a04409d --- /dev/null +++ b/lib/raidfile/RaidFileRead.h @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileRead.h +// Purpose: Read Raid like Files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEREAD__H +#define RAIDFILEREAD__H + +#include +#include +#include +#include + +#include "IOStream.h" + +class RaidFileDiscSet; + + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead +// Purpose: Read RAID like files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead : public IOStream +{ +protected: + RaidFileRead(int SetNumber, const std::string &Filename); +public: + virtual ~RaidFileRead(); +private: + RaidFileRead(const RaidFileRead &rToCopy); + +public: + // Open a raid file + static std::auto_ptr Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID = 0, int BufferSizeHint = 4096); + + // Extra info + virtual pos_type GetFileSize() const = 0; + + // Utility functions + static bool FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID = 0); + static bool DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName); + static bool DirectoryExists(int SetNumber, const std::string &rDirName); + enum + { + DirReadType_FilesOnly = 0, + DirReadType_DirsOnly = 1 + }; + static bool ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput); + + // Common IOStream interface implementation + virtual void Write(const void *pBuffer, int NBytes); + virtual bool StreamClosed(); + virtual pos_type BytesLeftToRead(); + + pos_type GetDiscUsageInBlocks(); + + typedef int64_t FileSizeType; + +protected: + int mSetNumber; + std::string mFilename; +}; + +#endif // RAIDFILEREAD__H + diff --git a/lib/raidfile/RaidFileUtil.cpp b/lib/raidfile/RaidFileUtil.cpp new file mode 100644 index 00000000..7c6299ec --- /dev/null +++ b/lib/raidfile/RaidFileUtil.cpp @@ -0,0 +1,210 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileUtil.cpp +// Purpose: Utilities for raid files +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "RaidFileUtil.h" +#include "FileModificationTime.h" +#include "RaidFileRead.h" // for type definition + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileUtil::RaidFileExists(RaidFileDiscSet &, +// const std::string &, int *, int *, int64_t *) +// Purpose: Check to see the state of a RaidFile on disc +// (doesn't look at contents, just at existence of +// files) +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +RaidFileUtil::ExistType RaidFileUtil::RaidFileExists(RaidFileDiscSet &rDiscSet, + const std::string &rFilename, int *pStartDisc, int *pExistingFiles, + int64_t *pRevisionID) +{ + if(pExistingFiles) + { + *pExistingFiles = 0; + } + + // For stat call, although the results are not examined + struct stat st; + + // check various files + int startDisc = 0; + { + std::string writeFile(RaidFileUtil::MakeWriteFileName(rDiscSet, rFilename, &startDisc)); + if(pStartDisc) + { + *pStartDisc = startDisc; + } + if(::stat(writeFile.c_str(), &st) == 0) + { + // write file exists, use that + + // Get unique ID + if(pRevisionID != 0) + { + #ifdef WIN32 + *pRevisionID = st.st_mtime; + #else + *pRevisionID = FileModificationTime(st); + #endif + +#ifdef BOX_RELEASE_BUILD + // The resolution of timestamps may be very + // low, e.g. 1 second. So add the size to it + // to give a bit more chance of it changing. + // TODO: Make this better. + // Disabled in debug mode, to simulate + // filesystem with 1-second timestamp + // resolution, e.g. MacOS X HFS, Linux. + (*pRevisionID) += st.st_size; +#endif + } + + // return non-raid file + return NonRaid; + } + } + + // Now see how many of the raid components exist + int64_t revisionID = 0; + int setSize = rDiscSet.size(); + int rfCount = 0; + + // TODO: replace this with better linux revision ID detection + int64_t revisionIDplus = 0; + + for(int f = 0; f < setSize; ++f) + { + std::string componentFile(RaidFileUtil::MakeRaidComponentName(rDiscSet, rFilename, (f + startDisc) % setSize)); + if(::stat(componentFile.c_str(), &st) == 0) + { + // Component file exists, add to count + rfCount++; + // Set flags for existance? + if(pExistingFiles) + { + (*pExistingFiles) |= (1 << f); + } + // Revision ID + if(pRevisionID != 0) + { + #ifdef WIN32 + int64_t rid = st.st_mtime; + #else + int64_t rid = FileModificationTime(st); + #endif + + if(rid > revisionID) revisionID = rid; + revisionIDplus += st.st_size; + } + } + } + if(pRevisionID != 0) + { + (*pRevisionID) = revisionID; +#ifdef BOX_RELEASE_BUILD + // The resolution of timestamps may be very low, e.g. + // 1 second. So add the size to it to give a bit more + // chance of it changing. + // TODO: Make this better. + // Disabled in debug mode, to simulate filesystem with + // 1-second timestamp resolution, e.g. MacOS X HFS, Linux. + (*pRevisionID) += revisionIDplus; +#endif + } + + // Return a status based on how many parts are available + if(rfCount == setSize) + { + return AsRaid; + } + else if((setSize > 1) && rfCount == (setSize - 1)) + { + return AsRaidWithMissingReadable; + } + else if(rfCount > 0) + { + return AsRaidWithMissingNotRecoverable; + } + + return NoFile; // Obviously doesn't exist +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileUtil::DiscUsageInBlocks(int64_t, const RaidFileDiscSet &) +// Purpose: Returns the size of the file in blocks, given the file size and disc set +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +int64_t RaidFileUtil::DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet) +{ + // Get block size + int blockSize = rDiscSet.GetBlockSize(); + + // OK... so as the size of the file is always sizes of stripe1 + stripe2, we can + // do a very simple calculation for the main data. + int64_t blocks = (FileSize + (((int64_t)blockSize) - 1)) / ((int64_t)blockSize); + + // It's just that simple calculation for non-RAID disc sets + if(rDiscSet.IsNonRaidSet()) + { + return blocks; + } + + // It's the parity which is mildly complex. + // First of all, add in size for all but the last two blocks. + int64_t parityblocks = (FileSize / ((int64_t)blockSize)) / 2; + blocks += parityblocks; + + // Work out how many bytes are left + int bytesOver = (int)(FileSize - (parityblocks * ((int64_t)(blockSize*2)))); + + // Then... (let compiler optimise this out) + if(bytesOver == 0) + { + // Extra block for the size info + blocks++; + } + else if(bytesOver == sizeof(RaidFileRead::FileSizeType)) + { + // For last block of parity, plus the size info + blocks += 2; + } + else if(bytesOver < blockSize) + { + // Just want the parity block + blocks += 1; + } + else if(bytesOver == blockSize || bytesOver >= ((blockSize*2)-((int)sizeof(RaidFileRead::FileSizeType)))) + { + // Last block, plus size info + blocks += 2; + } + else + { + // Just want parity block + blocks += 1; + } + + return blocks; +} + + diff --git a/lib/raidfile/RaidFileUtil.h b/lib/raidfile/RaidFileUtil.h new file mode 100644 index 00000000..a581047c --- /dev/null +++ b/lib/raidfile/RaidFileUtil.h @@ -0,0 +1,97 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileUtil.h +// Purpose: Utilities for the raid file classes +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEUTIL__H +#define RAIDFILEUTIL__H + +#include "RaidFileController.h" +#include "RaidFileException.h" + +// note: these are hardcoded into the directory searching code +#define RAIDFILE_EXTENSION ".rf" +#define RAIDFILE_WRITE_EXTENSION ".rfw" + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileUtil +// Purpose: Utility functions for RaidFile classes +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +class RaidFileUtil +{ +public: + typedef enum + { + NoFile = 0, + NonRaid = 1, + AsRaid = 2, + AsRaidWithMissingReadable = 3, + AsRaidWithMissingNotRecoverable = 4 + } ExistType; + + enum + { + Stripe1Exists = 1, + Stripe2Exists = 2, + ParityExists = 4 + }; + + static ExistType RaidFileExists(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pStartDisc = 0, int *pExisitingFiles = 0, int64_t *pRevisionID = 0); + + static int64_t DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet); + + // -------------------------------------------------------------------------- + // + // Function + // Name: std::string MakeRaidComponentName(RaidFileDiscSet &, const std::string &, int) + // Purpose: Returns the OS filename for a file of part of a disc set + // Created: 2003/07/11 + // + // -------------------------------------------------------------------------- + static inline std::string MakeRaidComponentName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int Disc) + { + if(Disc < 0 || Disc >= (int)rDiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) + } + std::string r(rDiscSet[Disc]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + r += RAIDFILE_EXTENSION; + return r; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: std::string MakeWriteFileName(RaidFileDiscSet &, const std::string &) + // Purpose: Returns the OS filename for the temporary write file + // Created: 2003/07/11 + // + // -------------------------------------------------------------------------- + static inline std::string MakeWriteFileName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pOnDiscSet = 0) + { + int livesOnSet = rDiscSet.GetSetNumForWriteFiles(rFilename); + + // does the caller want to know which set it's on? + if(pOnDiscSet) *pOnDiscSet = livesOnSet; + + // Make the string + std::string r(rDiscSet[livesOnSet]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + r += RAIDFILE_WRITE_EXTENSION; + return r; + } +}; + +#endif // RAIDFILEUTIL__H + diff --git a/lib/raidfile/RaidFileWrite.cpp b/lib/raidfile/RaidFileWrite.cpp new file mode 100644 index 00000000..f24c2422 --- /dev/null +++ b/lib/raidfile/RaidFileWrite.cpp @@ -0,0 +1,930 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileWrite.cpp +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "Guards.h" +#include "RaidFileWrite.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileUtil.h" +#include "Utils.h" +// For DirectoryExists fn +#include "RaidFileRead.h" + +#include "MemLeakFindOn.h" + +// should be a multiple of 2 +#define TRANSFORM_BLOCKS_TO_LOAD 4 +// Must have this number of discs in the set +#define TRANSFORM_NUMBER_DISCS_REQUIRED 3 + +// we want to use POSIX fstat() for now, not the emulated one +#undef fstat + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::RaidFileWrite(int, const std::string &) +// Purpose: Simple constructor, just stores required details +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename) + : mSetNumber(SetNumber), + mFilename(Filename), + mOSFileHandle(-1), // not valid file handle + mRefCount(-1) // unknown refcount +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::RaidFileWrite(int, +// const std::string &, int refcount) +// Purpose: Constructor with check for overwriting file +// with multiple references +// Created: 2009/07/05 +// +// -------------------------------------------------------------------------- +RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename, + int refcount) + : mSetNumber(SetNumber), + mFilename(Filename), + mOSFileHandle(-1), // not valid file handle + mRefCount(refcount) +{ + // Can't check for zero refcount here, because it's legal + // to create a RaidFileWrite to delete an object with zero refcount. + // Check in Commit() and Delete() instead. + if (refcount > 1) + { + BOX_ERROR("Attempted to modify object " << mFilename << + ", which has " << refcount << " references"); + THROW_EXCEPTION(RaidFileException, + RequestedModifyMultiplyReferencedFile); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::~RaidFileWrite() +// Purpose: Destructor (will discard written file if not commited) +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +RaidFileWrite::~RaidFileWrite() +{ + if(mOSFileHandle != -1) + { + Discard(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Open() +// Purpose: Opens the file for writing +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Open(bool AllowOverwrite) +{ + if(mOSFileHandle != -1) + { + THROW_EXCEPTION(RaidFileException, AlreadyOpen) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // Check for overwriting? (step 1) + if(!AllowOverwrite) + { + // See if the file exists already -- can't overwrite existing files + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); + if(existance != RaidFileUtil::NoFile) + { + BOX_ERROR("Attempted to overwrite raidfile " << + mSetNumber << " " << mFilename); + THROW_EXCEPTION(RaidFileException, CannotOverwriteExistingFile) + } + } + + // Get the filename for the write file + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + // Add on a temporary extension + writeFilename += 'X'; + + // Attempt to open + mOSFileHandle = ::open(writeFilename.c_str(), + O_WRONLY | O_CREAT | O_BINARY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(mOSFileHandle == -1) + { + BOX_LOG_SYS_ERROR("Failed to open file: " << writeFilename); + THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFile) + } + + // Get a lock on the write file +#ifdef HAVE_FLOCK + int errnoBlock = EWOULDBLOCK; + if(::flock(mOSFileHandle, LOCK_EX | LOCK_NB) != 0) +#elif HAVE_DECL_F_SETLK + int errnoBlock = EAGAIN; + struct flock desc; + desc.l_type = F_WRLCK; + desc.l_whence = SEEK_SET; + desc.l_start = 0; + desc.l_len = 0; + if(::fcntl(mOSFileHandle, F_SETLK, &desc) != 0) +#else + int errnoBlock = ENOSYS; + if (0) +#endif + { + // Lock was not obtained. + bool wasLocked = (errno == errnoBlock); + // Close the file + ::close(mOSFileHandle); + mOSFileHandle = -1; + // Report an exception? + if(wasLocked) + { + THROW_EXCEPTION(RaidFileException, FileIsCurrentlyOpenForWriting) + } + else + { + // Random error occured + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Truncate it to size zero + if(::ftruncate(mOSFileHandle, 0) != 0) + { + THROW_EXCEPTION(RaidFileException, ErrorOpeningWriteFileOnTruncate) + } + + // Done! +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Write(const void *, int) +// Purpose: Writes a block of data +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Write(const void *pBuffer, int Length) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Write data + int written = ::write(mOSFileHandle, pBuffer, Length); + if(written != Length) + { + BOX_LOG_SYS_ERROR("RaidFileWrite failed, Length = " << + Length << ", written = " << written); + THROW_EXCEPTION(RaidFileException, OSError) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetPosition() +// Purpose: Returns current position in file +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetPosition() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Use lseek to find the current file position + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return p; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Seek(RaidFileWrite::pos_type, bool) +// Purpose: Seeks in the file, relative to current position if Relative is true. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Seek(IOStream::pos_type SeekTo, int SeekType) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Seek... + if(::lseek(mOSFileHandle, SeekTo, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Commit(bool) +// Purpose: Closes, and commits the written file +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Commit(bool ConvertToRaidNow) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + if (mRefCount == 0) + { + BOX_ERROR("Attempted to modify object " << mFilename << + ", which has no references"); + THROW_EXCEPTION(RaidFileException, + RequestedModifyUnreferencedFile); + } + + // Rename it into place -- BEFORE it's closed so lock remains + +#ifdef WIN32 + // Except on Win32 which doesn't allow renaming open files + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + mOSFileHandle = -1; +#endif // WIN32 + + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + // Get the filename for the write file + std::string renameTo(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + // And the current name + std::string renameFrom(renameTo + 'X'); + +#ifdef WIN32 + // need to delete the target first + if(::unlink(renameTo.c_str()) != 0 && + GetLastError() != ERROR_FILE_NOT_FOUND) + { + BOX_LOG_WIN_ERROR("Failed to delete file: " << renameTo); + THROW_EXCEPTION(RaidFileException, OSError) + } +#endif + + if(::rename(renameFrom.c_str(), renameTo.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to rename file: " << renameFrom << + " to " << renameTo); + THROW_EXCEPTION(RaidFileException, OSError) + } + +#ifndef WIN32 + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + mOSFileHandle = -1; +#endif // !WIN32 + + // Raid it? + if(ConvertToRaidNow) + { + TransformToRaidStorage(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Discard() +// Purpose: Closes, discarding the data written. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Discard() +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // Get the filename for the write file (temporary) + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + writeFilename += 'X'; + + // Unlink and close it + +#ifdef WIN32 + // On Win32 we must close it first + if (::close(mOSFileHandle) != 0 || + ::unlink(writeFilename.c_str()) != 0) +#else // !WIN32 + if (::unlink(writeFilename.c_str()) != 0 || + ::close(mOSFileHandle) != 0) +#endif // !WIN32 + { + BOX_LOG_SYS_ERROR("Failed to delete file: " << writeFilename); + THROW_EXCEPTION(RaidFileException, OSError) + } + + // reset file handle + mOSFileHandle = -1; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::TransformToRaidStorage() +// Purpose: Turns the file into the RAID storage form +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::TransformToRaidStorage() +{ + // open? + if(mOSFileHandle != -1) + { + THROW_EXCEPTION(RaidFileException, WriteFileOpenOnTransform) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + if(rdiscSet.IsNonRaidSet()) + { + // Not in RAID mode -- do nothing + return; + } + // Otherwise check that it's the right sized set + if(TRANSFORM_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + unsigned int blockSize = rdiscSet.GetBlockSize(); + + // Get the filename for the write file (and get the disc set name for the start disc) + int startDisc = 0; + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename, &startDisc)); + + // Open it + FileHandleGuard<> writeFile(writeFilename.c_str()); + + // Get file information for write file + struct stat writeFileStat; + if(::fstat(writeFile, &writeFileStat) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } +// // DEBUG MODE -- check file system size block size is same as block size for files +// // doesn't really apply, as space benefits of using fragment size are worth efficiency, +// // and anyway, it'll be buffered eventually so it won't matter. +// #ifndef BOX_RELEASE_BUILD +// { +// if(writeFileStat.st_blksize != blockSize) +// { +// TRACE2("TransformToRaidStorage: optimal block size of file = %d, of set = %d, MISMATCH\n", +// writeFileStat.st_blksize, blockSize); +// } +// } +// #endif + + // How many blocks is the file? (rounding up) + int writeFileSizeInBlocks = (writeFileStat.st_size + (blockSize - 1)) / blockSize; + // And how big should the buffer be? (round up to multiple of 2, and no bigger than the preset limit) + int bufferSizeBlocks = (writeFileSizeInBlocks + 1) & ~1; + if(bufferSizeBlocks > TRANSFORM_BLOCKS_TO_LOAD) bufferSizeBlocks = TRANSFORM_BLOCKS_TO_LOAD; + // How big should the buffer be? + int bufferSize = (TRANSFORM_BLOCKS_TO_LOAD * blockSize); + + // Allocate buffer... + MemoryBlockGuard buffer(bufferSize); + + // Allocate buffer for parity file + MemoryBlockGuard parityBuffer(blockSize); + + // Get filenames of eventual files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 0) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 1) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 2) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + // Make write equivalents + std::string stripe1FilenameW(stripe1Filename + 'P'); + std::string stripe2FilenameW(stripe2Filename + 'P'); + std::string parityFilenameW(parityFilename + 'P'); + + + // Then open them all for writing (in strict order) + try + { +#if HAVE_DECL_O_EXLOCK + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe1(stripe1FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe2(stripe2FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> parity(parityFilenameW.c_str()); +#else + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe1(stripe1FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe2(stripe2FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> parity(parityFilenameW.c_str()); +#endif + + // Then... read in data... + int bytesRead = -1; + bool sizeRecordRequired = false; + int blocksDone = 0; + while((bytesRead = ::read(writeFile, buffer, bufferSize)) > 0) + { + // Blocks to do... + int blocksToDo = (bytesRead + (blockSize - 1)) / blockSize; + + // Need to add zeros to end? + int blocksRoundUp = (blocksToDo + 1) & ~1; + int zerosEnd = (blocksRoundUp * blockSize); + if(bytesRead != zerosEnd) + { + // Set the end of the blocks to zero + ::memset(buffer + bytesRead, 0, zerosEnd - bytesRead); + } + + // number of int's to XOR + unsigned int num = blockSize / sizeof(unsigned int); + + // Then... calculate and write parity data + for(int b = 0; b < blocksToDo; b += 2) + { + // Calculate int pointers + unsigned int *pstripe1 = (unsigned int *)(buffer + (b * blockSize)); + unsigned int *pstripe2 = (unsigned int *)(buffer + ((b+1) * blockSize)); + unsigned int *pparity = (unsigned int *)((char*)parityBuffer); + + // Do XOR + for(unsigned int n = 0; n < num; ++n) + { + pparity[n] = pstripe1[n] ^ pstripe2[n]; + } + + // Size of parity to write... + int parityWriteSize = blockSize; + + // Adjust if it's the last block + if((blocksDone + (b + 2)) >= writeFileSizeInBlocks) + { + // Yes... + unsigned int bytesInLastTwoBlocks = bytesRead - (b * blockSize); + + // Some special cases... + // Zero will never happen... but in the (imaginary) case it does, the file size will be appended + // by the test at the end. + if(bytesInLastTwoBlocks == sizeof(RaidFileRead::FileSizeType) + || bytesInLastTwoBlocks == blockSize) + { + // Write the entire block, and put the file size at end + sizeRecordRequired = true; + } + else if(bytesInLastTwoBlocks < blockSize) + { + // write only these bits + parityWriteSize = bytesInLastTwoBlocks; + } + else if(bytesInLastTwoBlocks < ((blockSize * 2) - sizeof(RaidFileRead::FileSizeType))) + { + // XOR in the size at the end of the parity block + ASSERT(sizeof(RaidFileRead::FileSizeType) == (2*sizeof(unsigned int))); + ASSERT(sizeof(RaidFileRead::FileSizeType) >= sizeof(off_t)); + int sizePos = (blockSize/sizeof(unsigned int)) - 2; + union { RaidFileRead::FileSizeType l; unsigned int i[2]; } sw; + + sw.l = box_hton64(writeFileStat.st_size); + pparity[sizePos+0] = pstripe1[sizePos+0] ^ sw.i[0]; + pparity[sizePos+1] = pstripe1[sizePos+1] ^ sw.i[1]; + } + else + { + // Write the entire block, and put the file size at end + sizeRecordRequired = true; + } + } + + // Write block + if(::write(parity, parityBuffer, parityWriteSize) != parityWriteSize) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Write stripes + char *writeFrom = buffer; + for(int l = 0; l < blocksToDo; ++l) + { + // Write the block + int toWrite = (l == (blocksToDo - 1)) + ?(bytesRead - ((blocksToDo-1)*blockSize)) + :blockSize; + if(::write(((l&1)==0)?stripe1:stripe2, writeFrom, toWrite) != toWrite) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Next block + writeFrom += blockSize; + } + + // Count of blocks done + blocksDone += blocksToDo; + } + // Error on read? + if(bytesRead == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Special case for zero length files + if(writeFileStat.st_size == 0) + { + sizeRecordRequired = true; + } + + // Might need to write the file size to the end of the parity file + // if it can't be worked out some other means -- size is required to rebuild the file if one of the stripe files is missing + if(sizeRecordRequired) + { + ASSERT(sizeof(writeFileStat.st_size) <= sizeof(RaidFileRead::FileSizeType)); + RaidFileRead::FileSizeType sw = box_hton64(writeFileStat.st_size); + ASSERT((::lseek(parity, 0, SEEK_CUR) % blockSize) == 0); + if(::write(parity, &sw, sizeof(sw)) != sizeof(sw)) + { + BOX_LOG_SYS_ERROR("Failed to write to file: " << + writeFilename); + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Then close the written files (note in reverse order of opening) + parity.Close(); + stripe2.Close(); + stripe1.Close(); + +#ifdef WIN32 + // Must delete before renaming + #define CHECK_UNLINK(file) \ + { \ + if (::unlink(file) != 0 && errno != ENOENT) \ + { \ + THROW_EXCEPTION(RaidFileException, OSError); \ + } \ + } + CHECK_UNLINK(stripe1Filename.c_str()); + CHECK_UNLINK(stripe2Filename.c_str()); + CHECK_UNLINK(parityFilename.c_str()); + #undef CHECK_UNLINK +#endif + + // Rename them into place + if(::rename(stripe1FilenameW.c_str(), stripe1Filename.c_str()) != 0 + || ::rename(stripe2FilenameW.c_str(), stripe2Filename.c_str()) != 0 + || ::rename(parityFilenameW.c_str(), parityFilename.c_str()) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Close the write file + writeFile.Close(); + + // Finally delete the write file + if(::unlink(writeFilename.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete file: " << + writeFilename); + THROW_EXCEPTION(RaidFileException, OSError) + } + } + catch(...) + { + // Unlink all the dodgy files + ::unlink(stripe1Filename.c_str()); + ::unlink(stripe2Filename.c_str()); + ::unlink(parityFilename.c_str()); + ::unlink(stripe1FilenameW.c_str()); + ::unlink(stripe2FilenameW.c_str()); + ::unlink(parityFilenameW.c_str()); + + // and send the error on its way + throw; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Delete() +// Purpose: Deletes a RAID file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Delete() +{ + if (mRefCount != 0 && mRefCount != -1) + { + BOX_ERROR("Attempted to delete object " << mFilename << + " which has " << mRefCount << " references"); + THROW_EXCEPTION(RaidFileException, + RequestedDeleteReferencedFile); + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // See if the file exists already -- can't delete files which don't exist + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); + if(existance == RaidFileUtil::NoFile) + { + THROW_EXCEPTION(RaidFileException, RaidFileDoesntExist) + } + + // Get the filename for the write file + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + + // Attempt to delete it + bool deletedSomething = false; + if(::unlink(writeFilename.c_str()) == 0) + { + deletedSomething = true; + } + + // If we're not running in RAID mode, stop now + if(rdiscSet.size() == 1) + { + return; + } + + // Now the other files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 0 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 1 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 2 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + if(::unlink(stripe1Filename.c_str()) == 0) + { + deletedSomething = true; + } + if(::unlink(stripe2Filename.c_str()) == 0) + { + deletedSomething = true; + } + if(::unlink(parityFilename.c_str()) == 0) + { + deletedSomething = true; + } + + // Check something happened + if(!deletedSomething) + { + THROW_EXCEPTION(RaidFileException, OSError) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::CreateDirectory(int, const std::string &, bool, int) +// Purpose: Creates a directory within the raid file directories with the given name. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive, int mode) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + // Pass on... + CreateDirectory(rdiscSet, rDirName, Recursive, mode); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::CreateDirectory(const RaidFileDiscSet &, const std::string &, bool, int) +// Purpose: Creates a directory within the raid file directories with the given name. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive, int mode) +{ + if(Recursive) + { + // split up string + std::vector elements; + SplitString(rDirName, DIRECTORY_SEPARATOR_ASCHAR, elements); + + // Do each element in turn + std::string pn; + for(unsigned int e = 0; e < elements.size(); ++e) + { + // Only do this if the element has some text in it + if(elements[e].size() > 0) + { + pn += elements[e]; + if(!RaidFileRead::DirectoryExists(rSet, pn)) + { + CreateDirectory(rSet, pn, false, mode); + } + + // add separator + pn += DIRECTORY_SEPARATOR_ASCHAR; + } + } + + return; + } + + // Create a directory in every disc of the set + for(unsigned int l = 0; l < rSet.size(); ++l) + { + // build name + std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // attempt to create + if(::mkdir(dn.c_str(), mode) != 0) + { + if(errno == EEXIST) + { + // No. Bad things. + THROW_EXCEPTION(RaidFileException, FileExistsInDirectoryCreation) + } + else + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Read(void *, int, int) +// Purpose: Unsupported, will exception +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +int RaidFileWrite::Read(void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Close() +// Purpose: Close, discarding file. +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Close() +{ + if(mOSFileHandle != -1) + { + BOX_WARNING("RaidFileWrite::Close() called, discarding file"); + Discard(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::StreamDataLeft() +// Purpose: Never any data left to read! +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileWrite::StreamDataLeft() +{ + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::StreamClosed() +// Purpose: Is stream closed for writing? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileWrite::StreamClosed() +{ + return mOSFileHandle == -1; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetFileSize() +// Purpose: Returns the size of the file written. +// Can only be used before the file is commited. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetFileSize() +{ + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, CanOnlyGetFileSizeBeforeCommit) + } + + // Stat to get size + struct stat st; + if(fstat(mOSFileHandle, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return st.st_size; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetDiscUsageInBlocks() +// Purpose: Returns the amount of disc space used, in blocks. +// Can only be used before the file is commited. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetDiscUsageInBlocks() +{ + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, CanOnlyGetUsageBeforeCommit) + } + + // Stat to get size + struct stat st; + if(fstat(mOSFileHandle, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Then return calculation + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + return RaidFileUtil::DiscUsageInBlocks(st.st_size, rdiscSet); +} + + diff --git a/lib/raidfile/RaidFileWrite.h b/lib/raidfile/RaidFileWrite.h new file mode 100644 index 00000000..418f90ee --- /dev/null +++ b/lib/raidfile/RaidFileWrite.h @@ -0,0 +1,68 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileWrite.h +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEWRITE__H +#define RAIDFILEWRITE__H + +#include + +#include "IOStream.h" + +class RaidFileDiscSet; + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileWrite +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +class RaidFileWrite : public IOStream +{ +public: + RaidFileWrite(int SetNumber, const std::string &Filename); + RaidFileWrite(int SetNumber, const std::string &Filename, int refcount); + ~RaidFileWrite(); +private: + RaidFileWrite(const RaidFileWrite &rToCopy); + +public: + // IOStream interface + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); // will exception + virtual void Write(const void *pBuffer, int NBytes); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual void Close(); // will discard the file! Use commit instead. + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + // Extra bits + void Open(bool AllowOverwrite = false); + void Commit(bool ConvertToRaidNow = false); + void Discard(); + void TransformToRaidStorage(); + void Delete(); + pos_type GetFileSize(); + pos_type GetDiscUsageInBlocks(); + + static void CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive = false, int mode = 0777); + static void CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive = false, int mode = 0777); + +private: + +private: + int mSetNumber; + std::string mFilename; + int mOSFileHandle; + int mRefCount; +}; + +#endif // RAIDFILEWRITE__H + diff --git a/lib/raidfile/raidfile-config.in b/lib/raidfile/raidfile-config.in new file mode 100755 index 00000000..b8ea73a5 --- /dev/null +++ b/lib/raidfile/raidfile-config.in @@ -0,0 +1,100 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +# check and get command line parameters +if($#ARGV != 4 && $#ARGV != 2) +{ + print <<__E; + +Setup raidfile config utility. + +Bad command line parameters. +Usage: + raidfile-config config-dir block-size dir0 [dir1 dir2] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + block-size must be a power of two, and usually the block or + fragment size of your file system + dir0, dir1, dir2 are the directories used as the root of the raid + file system + +If only one directory is specified, then userland RAID is disabled. +Specifying three directories enables it. + +__E + exit(1); +} + +my ($config_dir,$block_size,@dirs) = @ARGV; + +my $conf = $config_dir . '/raidfile.conf'; + +# check dirs are unique, and exist +my %d; +for(@dirs) +{ + die "$_ is used twice" if exists $d{$_}; + die "$_ is not a directory" unless -d $_; + die "$_ should be an absolute path" unless m/\A\//; + $d{$_} = 1; +} + +# check block size is OK +$block_size = int($block_size); +die "Bad block size" if $block_size <= 0; +my $c = 1; +while(1) +{ + last if $c == $block_size; + die "Block size $block_size is not a power of two" if $c > $block_size; + $c = $c * 2; +} + +# check that it doesn't already exist +if(-f $conf) +{ + die "$conf already exists. Delete and try again"; +} + +# create directory +if(!-d $config_dir) +{ + print "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +# adjust if userland RAID is disabled +if($#dirs == 0) +{ + $dirs[1] = $dirs[0]; + $dirs[2] = $dirs[0]; + print "WARNING: userland RAID is disabled.\n"; +} + +# write the file +open CONFIG,">$conf" or die "Can't open $conf for writing"; + +print CONFIG <<__E; + +disc0 +{ + SetNumber = 0 + BlockSize = $block_size + Dir0 = $dirs[0] + Dir1 = $dirs[1] + Dir2 = $dirs[2] +} + +__E + +close CONFIG; + +print "Config file written.\n"; + diff --git a/lib/server/ConnectionException.txt b/lib/server/ConnectionException.txt new file mode 100644 index 00000000..c3429116 --- /dev/null +++ b/lib/server/ConnectionException.txt @@ -0,0 +1,27 @@ +EXCEPTION Connection 7 + +# for historic reasons not all numbers are used + +SocketWriteError 6 Probably a network issue between client and server. +SocketReadError 7 Probably a network issue between client and server. +SocketNameLookupError 9 Check hostname specified. +SocketShutdownError 12 +SocketConnectError 15 Probably a network issue between client and server, bad hostname, or server not running. +TLSHandshakeFailed 30 +TLSShutdownFailed 32 +TLSWriteFailed 33 Probably a network issue between client and server. +TLSReadFailed 34 Probably a network issue between client and server, or a problem with the server. +TLSNoPeerCertificate 36 +TLSPeerCertificateInvalid 37 Check certification process +TLSClosedWhenWriting 38 +TLSHandshakeTimedOut 39 +Protocol_Timeout 41 Probably a network issue between client and server. +Protocol_ObjTooBig 42 +Protocol_BadCommandRecieved 44 +Protocol_UnknownCommandRecieved 45 +Protocol_TriedToExecuteReplyCommand 46 +Protocol_UnexpectedReply 47 Server probably reported an error. +Protocol_HandshakeFailed 48 +Protocol_StreamWhenObjExpected 49 +Protocol_ObjWhenStreamExpected 50 +Protocol_TimeOutWhenSendingStream 52 Probably a network issue between client and server. diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp new file mode 100644 index 00000000..8b4f1d0c --- /dev/null +++ b/lib/server/Daemon.cpp @@ -0,0 +1,1024 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Daemon.cpp +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_BSD_UNISTD_H + #include +#endif + +#ifdef WIN32 + #include +#endif + +#include + +#include "Daemon.h" +#include "Configuration.h" +#include "ServerException.h" +#include "Guards.h" +#include "UnixUser.h" +#include "FileModificationTime.h" +#include "Logging.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +Daemon *Daemon::spDaemon = 0; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Daemon() +// Purpose: Constructor +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +Daemon::Daemon() + : mReloadConfigWanted(false), + mTerminateWanted(false), + #ifdef WIN32 + mSingleProcess(true), + mRunInForeground(true), + mKeepConsoleOpenAfterFork(true), + #else + mSingleProcess(false), + mRunInForeground(false), + mKeepConsoleOpenAfterFork(false), + #endif + mHaveConfigFile(false), + mAppName(DaemonName()) +{ + // In debug builds, switch on assert failure logging to syslog + ASSERT_FAILS_TO_SYSLOG_ON + // And trace goes to syslog too + TRACE_TO_SYSLOG(true) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::~Daemon() +// Purpose: Destructor +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +Daemon::~Daemon() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetOptionString() +// Purpose: Returns the valid Getopt command-line options. +// This should be overridden by subclasses to add +// their own options, which should override +// ProcessOption, handle their own, and delegate to +// ProcessOption for the standard options. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +std::string Daemon::GetOptionString() +{ + return "c:" + #ifndef WIN32 + "DFK" + #endif + "hkPqQt:TUvVW:"; +} + +void Daemon::Usage() +{ + std::cout << + DaemonBanner() << "\n" + "\n" + "Usage: " << mAppName << " [options] [config file]\n" + "\n" + "Options:\n" + " -c Use the specified configuration file. If -c is omitted, the last\n" + " argument is the configuration file, or else the default \n" + " [" << GetConfigFileName() << "]\n" +#ifndef WIN32 + " -D Debugging mode, do not fork, one process only, one client only\n" + " -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" +#ifndef WIN32 + " -K Stop writing log messages to console while daemon is running\n" + " -P Show process ID (PID) in console output\n" +#endif + " -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 Set verbosity to error/warning/notice/info/trace/everything\n" + " -t Tag console output with specified marker\n" + " -T Timestamp console output\n" + " -U Timestamp console output with microseconds\n"; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::ProcessOption(int option) +// Purpose: Processes the supplied option (equivalent to the +// return code from getopt()). Return zero if the +// option was handled successfully, or nonzero to +// abort the program with that return value. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +int Daemon::ProcessOption(signed int option) +{ + switch(option) + { + case 'c': + { + mConfigFileName = optarg; + mHaveConfigFile = true; + } + break; + +#ifndef WIN32 + case 'D': + { + mSingleProcess = true; + } + break; + + case 'F': + { + mRunInForeground = true; + } + break; +#endif // !WIN32 + + case 'k': + { + mKeepConsoleOpenAfterFork = true; + } + break; + + case 'K': + { + mKeepConsoleOpenAfterFork = false; + } + break; + + case 'h': + { + Usage(); + return 2; + } + break; + + case 'P': + { + Console::SetShowPID(true); + } + break; + + case 'q': + { + if(mLogLevel == Log::NOTHING) + { + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; + } + mLogLevel--; + } + break; + + case 'Q': + { + mLogLevel = Log::NOTHING; + } + break; + + + case 'v': + { + if(mLogLevel == Log::EVERYTHING) + { + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; + } + mLogLevel++; + } + break; + + case 'V': + { + mLogLevel = Log::EVERYTHING; + } + break; + + case 'W': + { + mLogLevel = Logging::GetNamedLevel(optarg); + if (mLogLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } + } + break; + + case 't': + { + Logging::SetProgramName(optarg); + Console::SetShowTag(true); + } + break; + + case 'T': + { + Console::SetShowTime(true); + } + break; + + case 'U': + { + Console::SetShowTime(true); + Console::SetShowTimeMicros(true); + } + break; + + case '?': + { + BOX_FATAL("Unknown option on command line: " + << "'" << (char)optopt << "'"); + return 2; + } + break; + + default: + { + BOX_FATAL("Unknown error in getopt: returned " + << "'" << option << "'"); + return 1; + } + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const char *, int, const char *[]) +// Purpose: Parses command-line options, and then calls +// Main(std::string& configFile, bool singleProcess) +// to start the daemon. +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +int Daemon::Main(const char *DefaultConfigFile, int argc, const char *argv[]) +{ + // Find filename of config file + mConfigFileName = DefaultConfigFile; + mAppName = argv[0]; + + #ifdef BOX_RELEASE_BUILD + mLogLevel = Log::NOTICE; // need an int to do math with + #else + mLogLevel = Log::INFO; // need an int to do math with + #endif + + if (argc == 2 && strcmp(argv[1], "/?") == 0) + { + Usage(); + return 2; + } + + signed int c; + + // reset getopt, just in case anybody used it before. + // unfortunately glibc and BSD differ on this point! + // http://www.ussg.iu.edu/hypermail/linux/kernel/0305.3/0262.html + #if HAVE_DECL_OPTRESET == 1 || defined WIN32 + optind = 1; + optreset = 1; + #elif defined __GLIBC__ + optind = 0; + #else // Solaris, any others? + optind = 1; + #endif + + while((c = getopt(argc, (char * const *)argv, + GetOptionString().c_str())) != -1) + { + int returnCode = ProcessOption(c); + + if (returnCode != 0) + { + return returnCode; + } + } + + if (argc > optind && !mHaveConfigFile) + { + mConfigFileName = argv[optind]; optind++; + mHaveConfigFile = true; + } + + if (argc > optind && ::strcmp(argv[optind], "SINGLEPROCESS") == 0) + { + mSingleProcess = true; optind++; + } + + if (argc > optind) + { + BOX_FATAL("Unknown parameter on command line: " + << "'" << std::string(argv[optind]) << "'"); + return 2; + } + + Logging::FilterConsole((Log::Level)mLogLevel); + Logging::FilterSyslog ((Log::Level)mLogLevel); + + return Main(mConfigFileName); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Configure(const std::string& rConfigFileName) +// Purpose: Loads daemon configuration. Useful when you have +// a local Daemon object and don't intend to fork() +// or call Main(). +// Created: 2008/04/19 +// +// -------------------------------------------------------------------------- + +bool Daemon::Configure(const std::string& rConfigFileName) +{ + // Load the configuration file. + std::string errors; + std::auto_ptr apConfig; + + try + { + if (!FileExists(rConfigFileName.c_str())) + { + BOX_FATAL("The main configuration file for " << + DaemonName() << " was not found: " << + rConfigFileName); + if (!mHaveConfigFile) + { + BOX_WARNING("The default configuration " + "directory has changed from /etc/box " + "to /etc/boxbackup"); + } + return false; + } + + apConfig = Configuration::LoadAndVerify(rConfigFileName, + GetConfigVerify(), errors); + } + catch(BoxException &e) + { + if(e.GetType() == CommonException::ExceptionType && + e.GetSubType() == CommonException::OSFileOpenError) + { + BOX_ERROR("Failed to open configuration file: " << + rConfigFileName); + return false; + } + + throw; + } + + // Got errors? + if(apConfig.get() == 0) + { + BOX_ERROR("Failed to load or verify configuration file"); + return false; + } + + if(!Configure(*apConfig)) + { + BOX_ERROR("Failed to verify configuration file"); + return false; + } + + // Store configuration + mConfigFileName = rConfigFileName; + mLoadedConfigModifiedTime = GetConfigFileModifiedTime(); + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Configure(const Configuration& rConfig) +// Purpose: Loads daemon configuration. Useful when you have +// a local Daemon object and don't intend to fork() +// or call Main(). +// Created: 2008/08/12 +// +// -------------------------------------------------------------------------- + +bool Daemon::Configure(const Configuration& rConfig) +{ + std::string errors; + + // Verify() may modify the configuration, e.g. adding default values + // for required keys, so need to make a copy here + std::auto_ptr apConf(new Configuration(rConfig)); + apConf->Verify(*GetConfigVerify(), errors); + + // Got errors? + if(!errors.empty()) + { + BOX_ERROR("Configuration errors: " << errors); + return false; + } + + // Store configuration + mapConfiguration = apConf; + + // Let the derived class have a go at setting up stuff + // in the initial process + SetupInInitialProcess(); + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const std::string& rConfigFileName) +// Purpose: Starts the daemon off -- equivalent of C main() function +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +int Daemon::Main(const std::string &rConfigFileName) +{ + // Banner (optional) + { + BOX_SYSLOG(Log::NOTICE, DaemonBanner()); + } + + std::string pidFileName; + + bool asDaemon = !mSingleProcess && !mRunInForeground; + + try + { + if (!Configure(rConfigFileName)) + { + BOX_FATAL("Failed to start: failed to load " + "configuration file: " << rConfigFileName); + return 1; + } + + // Server configuration + const Configuration &serverConfig( + mapConfiguration->GetSubConfiguration("Server")); + + if(serverConfig.KeyExists("LogFacility")) + { + std::string facility = + serverConfig.GetKeyValue("LogFacility"); + Logging::SetFacility(Syslog::GetNamedFacility(facility)); + } + + // Open PID file for writing + pidFileName = serverConfig.GetKeyValue("PidFile"); + FileHandleGuard<(O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)> pidFile(pidFileName.c_str()); + +#ifndef WIN32 + // Handle changing to a different user + if(serverConfig.KeyExists("User")) + { + // Config file specifies an user -- look up + UnixUser daemonUser(serverConfig.GetKeyValue("User").c_str()); + + // Change the owner on the PID file, so it can be deleted properly on termination + if(::fchown(pidFile, daemonUser.GetUID(), daemonUser.GetGID()) != 0) + { + THROW_EXCEPTION(ServerException, CouldNotChangePIDFileOwner) + } + + // Change the process ID + daemonUser.ChangeProcessUser(); + } + + if(asDaemon) + { + // Let's go... Daemonise... + switch(::fork()) + { + case -1: + // error + THROW_EXCEPTION(ServerException, DaemoniseFailed) + break; + + default: + // parent + // _exit(0); + return 0; + break; + + case 0: + // child + break; + } + + // In child + + // Set new session + if(::setsid() == -1) + { + BOX_LOG_SYS_ERROR("Failed to setsid()"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } + + // Fork again... + switch(::fork()) + { + case -1: + // error + BOX_LOG_SYS_ERROR("Failed to fork() a child"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + break; + + default: + // parent + _exit(0); + return 0; + break; + + case 0: + // child + break; + } + } +#endif // !WIN32 + + // Must set spDaemon before installing signal handler, + // otherwise the handler will crash if invoked too soon. + if(spDaemon != NULL) + { + THROW_EXCEPTION(ServerException, AlreadyDaemonConstructed) + } + spDaemon = this; + +#ifndef WIN32 + // Set signal handler + // Don't do this in the parent, since it might be anything + // (e.g. test/bbackupd) + + struct sigaction sa; + sa.sa_handler = SignalHandler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); // macro + if(::sigaction(SIGHUP, &sa, NULL) != 0 || + ::sigaction(SIGTERM, &sa, NULL) != 0) + { + BOX_LOG_SYS_ERROR("Failed to set signal handlers"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } +#endif // !WIN32 + + // Write PID to file + char pid[32]; + + int pidsize = sprintf(pid, "%d", (int)getpid()); + + if(::write(pidFile, pid, pidsize) != pidsize) + { + BOX_LOG_SYS_FATAL("Failed to write PID file: " << + pidFileName); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } + + // Set up memory leak reporting + #ifdef BOX_MEMORY_LEAK_TESTING + { + char filename[256]; + sprintf(filename, "%s.memleaks", DaemonName()); + memleakfinder_setup_exit_report(filename, DaemonName()); + } + #endif // BOX_MEMORY_LEAK_TESTING + + if(asDaemon && !mKeepConsoleOpenAfterFork) + { +#ifndef WIN32 + // Close standard streams + ::close(0); + ::close(1); + ::close(2); + + // Open and redirect them into /dev/null + int devnull = ::open(PLATFORM_DEV_NULL, O_RDWR, 0); + if(devnull == -1) + { + BOX_LOG_SYS_ERROR("Failed to open /dev/null"); + THROW_EXCEPTION(CommonException, OSFileError); + } + // Then duplicate them to all three handles + if(devnull != 0) dup2(devnull, 0); + if(devnull != 1) dup2(devnull, 1); + if(devnull != 2) dup2(devnull, 2); + // Close the original handle if it was opened above the std* range + if(devnull > 2) + { + ::close(devnull); + } + + // And definitely don't try and send anything to those file descriptors + // -- this has in the past sent text to something which isn't expecting it. + TRACE_TO_STDOUT(false); +#endif // ! WIN32 + Logging::ToConsole(false); + } + + // Log the start message + BOX_NOTICE("Starting daemon, version: " << BOX_VERSION); + BOX_NOTICE("Using configuration file: " << mConfigFileName); + } + catch(BoxException &e) + { + BOX_FATAL("Failed to start: exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); + return 1; + } + catch(std::exception &e) + { + BOX_FATAL("Failed to start: exception " << e.what()); + return 1; + } + catch(...) + { + BOX_FATAL("Failed to start: unknown error"); + return 1; + } + +#ifdef WIN32 + // Under win32 we must initialise the Winsock library + // before using sockets + + WSADATA info; + + if (WSAStartup(0x0101, &info) == SOCKET_ERROR) + { + // will not run without sockets + BOX_FATAL("Failed to initialise Windows Sockets"); + THROW_EXCEPTION(CommonException, Internal) + } +#endif + + int retcode = 0; + + // Main Daemon running + try + { + while(!mTerminateWanted) + { + Run(); + + if(mReloadConfigWanted && !mTerminateWanted) + { + // Need to reload that config file... + BOX_NOTICE("Reloading configuration file: " + << mConfigFileName); + std::string errors; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + mConfigFileName.c_str(), + GetConfigVerify(), errors)); + + // Got errors? + if(pconfig.get() == 0 || !errors.empty()) + { + // Tell user about errors + BOX_FATAL("Error in configuration " + << "file: " << mConfigFileName + << ": " << errors); + // And give up + retcode = 1; + break; + } + + // Store configuration + mapConfiguration = pconfig; + mLoadedConfigModifiedTime = + GetConfigFileModifiedTime(); + + // Stop being marked for loading config again + mReloadConfigWanted = false; + } + } + + // Delete the PID file + ::unlink(pidFileName.c_str()); + + // Log + BOX_NOTICE("Terminating daemon"); + } + catch(BoxException &e) + { + BOX_FATAL("Terminating due to exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); + retcode = 1; + } + catch(std::exception &e) + { + BOX_FATAL("Terminating due to exception " << e.what()); + retcode = 1; + } + catch(...) + { + BOX_FATAL("Terminating due to unknown exception"); + retcode = 1; + } + +#ifdef WIN32 + WSACleanup(); +#else + // Should clean up here, but it breaks memory leak tests. + /* + if(asDaemon) + { + // we are running in the child by now, and should not return + mapConfiguration.reset(); + exit(0); + } + */ +#endif + + ASSERT(spDaemon == this); + spDaemon = NULL; + + return retcode; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::EnterChild() +// Purpose: Sets up for a child task of the main server. Call +// just after fork(). +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void Daemon::EnterChild() +{ +#ifndef WIN32 + // Unset signal handlers + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); // macro + ::sigaction(SIGHUP, &sa, NULL); + ::sigaction(SIGTERM, &sa, NULL); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::SignalHandler(int) +// Purpose: Signal handler +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +void Daemon::SignalHandler(int sigraised) +{ +#ifndef WIN32 + if(spDaemon != 0) + { + switch(sigraised) + { + case SIGHUP: + spDaemon->mReloadConfigWanted = true; + break; + + case SIGTERM: + spDaemon->mTerminateWanted = true; + break; + + default: + break; + } + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::DaemonName() +// Purpose: Returns name of the daemon +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const char *Daemon::DaemonName() const +{ + return "generic-daemon"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::DaemonBanner() +// Purpose: Returns the text banner for this daemon's startup +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string Daemon::DaemonBanner() const +{ + return "Generic daemon using the Box Application Framework"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Run() +// Purpose: Main run function after basic Daemon initialisation +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +void Daemon::Run() +{ + while(!StopRun()) + { + ::sleep(10); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfigVerify() +// Purpose: Returns the configuration file verification structure for this daemon +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *Daemon::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + DAEMON_VERIFY_SERVER_KEYS + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfiguration() +// Purpose: Returns the daemon configuration object +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const Configuration &Daemon::GetConfiguration() const +{ + if(mapConfiguration.get() == 0) + { + // Shouldn't get anywhere near this if a configuration file can't be loaded + THROW_EXCEPTION(ServerException, Internal) + } + + return *mapConfiguration; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::SetupInInitialProcess() +// Purpose: A chance for the daemon to do something initial +// setting up in the process which initiates +// everything, and after the configuration file has +// been read and verified. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void Daemon::SetupInInitialProcess() +{ + // Base class doesn't do anything. +} + + +void Daemon::SetProcessTitle(const char *format, ...) +{ + // On OpenBSD, setproctitle() sets the process title to imagename: (imagename) + // -- make sure other platforms include the image name somewhere so ps listings give + // useful information. + +#ifdef HAVE_SETPROCTITLE + // optional arguments + va_list args; + va_start(args, format); + + // Make the string + char title[256]; + ::vsnprintf(title, sizeof(title), format, args); + + // Set process title + ::setproctitle("%s", title); + +#endif // HAVE_SETPROCTITLE +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfigFileModifiedTime() +// Purpose: Returns the timestamp when the configuration file +// was last modified +// +// Created: 2006/01/29 +// +// -------------------------------------------------------------------------- + +box_time_t Daemon::GetConfigFileModifiedTime() const +{ + EMU_STRUCT_STAT st; + + if(EMU_STAT(GetConfigFileName().c_str(), &st) != 0) + { + if (errno == ENOENT) + { + return 0; + } + BOX_LOG_SYS_ERROR("Failed to stat configuration file: " << + GetConfigFileName()); + THROW_EXCEPTION(CommonException, OSFileError) + } + + return FileModificationTime(st); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetLoadedConfigModifiedTime() +// Purpose: Returns the timestamp when the configuration file +// had been last modified, at the time when it was +// loaded +// +// Created: 2006/01/29 +// +// -------------------------------------------------------------------------- + +box_time_t Daemon::GetLoadedConfigModifiedTime() const +{ + return mLoadedConfigModifiedTime; +} + diff --git a/lib/server/Daemon.h b/lib/server/Daemon.h new file mode 100644 index 00000000..a3212a00 --- /dev/null +++ b/lib/server/Daemon.h @@ -0,0 +1,112 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Daemon.h +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + +/* NOTE: will log to local6: include a line like + local6.info /var/log/box + in /etc/syslog.conf +*/ + + +#ifndef DAEMON__H +#define DAEMON__H + +#include + +#include "BoxTime.h" +#include "Configuration.h" + +class ConfigurationVerify; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Daemon +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +class Daemon +{ +public: + Daemon(); + virtual ~Daemon(); +private: + Daemon(const Daemon &rToCopy); +public: + + virtual int Main(const char *DefaultConfigFile, int argc, + const char *argv[]); + + /* override this Main() if you want custom option processing: */ + virtual int Main(const std::string &rConfigFile); + + virtual void Run(); + const Configuration &GetConfiguration() const; + const std::string &GetConfigFileName() const {return mConfigFileName;} + + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + virtual const ConfigurationVerify *GetConfigVerify() const; + virtual void Usage(); + + virtual bool Configure(const std::string& rConfigFileName); + virtual bool Configure(const Configuration& rConfig); + + bool StopRun() {return mReloadConfigWanted | mTerminateWanted;} + bool IsReloadConfigWanted() {return mReloadConfigWanted;} + bool IsTerminateWanted() {return mTerminateWanted;} + + // To allow derived classes to get these signals in other ways + void SetReloadConfigWanted() {mReloadConfigWanted = true;} + void SetTerminateWanted() {mTerminateWanted = true;} + + virtual void EnterChild(); + + static void SetProcessTitle(const char *format, ...); + void SetRunInForeground(bool foreground) + { + mRunInForeground = foreground; + } + void SetSingleProcess(bool value) + { + mSingleProcess = value; + } + +protected: + virtual void SetupInInitialProcess(); + box_time_t GetLoadedConfigModifiedTime() const; + bool IsSingleProcess() { return mSingleProcess; } + virtual std::string GetOptionString(); + virtual int ProcessOption(signed int option); + +private: + static void SignalHandler(int sigraised); + box_time_t GetConfigFileModifiedTime() const; + + std::string mConfigFileName; + std::auto_ptr mapConfiguration; + box_time_t mLoadedConfigModifiedTime; + bool mReloadConfigWanted; + bool mTerminateWanted; + bool mSingleProcess; + bool mRunInForeground; + bool mKeepConsoleOpenAfterFork; + bool mHaveConfigFile; + int mLogLevel; // need an int to do math with + static Daemon *spDaemon; + std::string mAppName; +}; + +#define DAEMON_VERIFY_SERVER_KEYS \ + ConfigurationVerifyKey("PidFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("LogFacility", 0), \ + ConfigurationVerifyKey("User", ConfigTest_LastEntry) + +#endif // DAEMON__H + diff --git a/lib/server/LocalProcessStream.cpp b/lib/server/LocalProcessStream.cpp new file mode 100644 index 00000000..c331a135 --- /dev/null +++ b/lib/server/LocalProcessStream.cpp @@ -0,0 +1,180 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: LocalProcessStream.cpp +// Purpose: Opens a process, and presents stdin/stdout as a stream. +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SYS_SOCKET_H + #include +#endif + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include "LocalProcessStream.h" +#include "autogen_ServerException.h" +#include "Utils.h" + +#ifdef WIN32 + #include "FileStream.h" +#else + #include "SocketStream.h" +#endif + +#include "MemLeakFindOn.h" + +#define MAX_ARGUMENTS 64 + +// -------------------------------------------------------------------------- +// +// Function +// Name: LocalProcessStream(const char *, pid_t &) +// Purpose: Run a new process, and return a stream giving access +// to its stdin and stdout (stdout and stderr on +// Win32). Returns the PID of the new process -- this +// must be waited on at some point to avoid zombies +// (except on Win32). +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr LocalProcessStream(const std::string& rCommandLine, + pid_t &rPidOut) +{ +#ifndef WIN32 + + // Split up command + std::vector command; + SplitString(rCommandLine, ' ', command); + + // Build arguments + char *args[MAX_ARGUMENTS + 4]; + { + int a = 0; + std::vector::const_iterator i(command.begin()); + while(a < MAX_ARGUMENTS && i != command.end()) + { + args[a++] = (char*)(*(i++)).c_str(); + } + args[a] = NULL; + } + + // Create a socket pair to communicate over. + int sv[2] = {-1,-1}; + if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) + { + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + + std::auto_ptr stream(new SocketStream(sv[0])); + + // Fork + pid_t pid = 0; + switch(pid = vfork()) + { + case -1: // error + ::close(sv[0]); + ::close(sv[1]); + THROW_EXCEPTION(ServerException, ServerForkError) + break; + + case 0: // child + // Close end of the socket not being used + ::close(sv[0]); + // Duplicate the file handles to stdin and stdout + if(sv[1] != 0) ::dup2(sv[1], 0); + if(sv[1] != 1) ::dup2(sv[1], 1); + // Close the now redundant socket + if(sv[1] != 0 && sv[1] != 1) + { + ::close(sv[1]); + } + // Execute command! + ::execv(args[0], args); + ::_exit(127); // report error + break; + + default: + // just continue... + break; + } + + // Close the file descriptor not being used + ::close(sv[1]); + + // Return the stream object and PID + rPidOut = pid; + return stream; + +#else // WIN32 + + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttr.bInheritHandle = TRUE; + secAttr.lpSecurityDescriptor = NULL; + + HANDLE writeInChild, readFromChild; + if(!CreatePipe(&readFromChild, &writeInChild, &secAttr, 0)) + { + BOX_ERROR("Failed to CreatePipe for child process: " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + SetHandleInformation(readFromChild, HANDLE_FLAG_INHERIT, 0); + + PROCESS_INFORMATION procInfo; + STARTUPINFO startupInfo; + + ZeroMemory(&procInfo, sizeof(procInfo)); + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.hStdError = writeInChild; + startupInfo.hStdOutput = writeInChild; + startupInfo.hStdInput = INVALID_HANDLE_VALUE; + startupInfo.dwFlags |= STARTF_USESTDHANDLES; + + CHAR* commandLineCopy = (CHAR*)malloc(rCommandLine.size() + 1); + strcpy(commandLineCopy, rCommandLine.c_str()); + + BOOL result = CreateProcess(NULL, + commandLineCopy, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &startupInfo, // STARTUPINFO pointer + &procInfo); // receives PROCESS_INFORMATION + + free(commandLineCopy); + + if(!result) + { + BOX_ERROR("Failed to CreateProcess: '" << rCommandLine << + "': " << GetErrorMessage(GetLastError())); + CloseHandle(writeInChild); + CloseHandle(readFromChild); + THROW_EXCEPTION(ServerException, ServerForkError) + } + + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + CloseHandle(writeInChild); + + rPidOut = (int)(procInfo.dwProcessId); + + std::auto_ptr stream(new FileStream(readFromChild)); + return stream; + +#endif // ! WIN32 +} + + + + diff --git a/lib/server/LocalProcessStream.h b/lib/server/LocalProcessStream.h new file mode 100644 index 00000000..51e51f8a --- /dev/null +++ b/lib/server/LocalProcessStream.h @@ -0,0 +1,20 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: LocalProcessStream.h +// Purpose: Opens a process, and presents stdin/stdout as a stream. +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef LOCALPROCESSSTREAM__H +#define LOCALPROCESSSTREAM__H + +#include +#include "IOStream.h" + +std::auto_ptr LocalProcessStream(const std::string& rCommandLine, + pid_t &rPidOut); + +#endif // LOCALPROCESSSTREAM__H + diff --git a/lib/server/Makefile.extra b/lib/server/Makefile.extra new file mode 100644 index 00000000..7fc6baf9 --- /dev/null +++ b/lib/server/Makefile.extra @@ -0,0 +1,11 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_ServerException.h autogen_ServerException.cpp: $(MAKEEXCEPTION) ServerException.txt + $(_PERL) $(MAKEEXCEPTION) ServerException.txt + +# AUTOGEN SEEDING +autogen_ConnectionException.h autogen_ConnectionException.cpp: $(MAKEEXCEPTION) ConnectionException.txt + $(_PERL) $(MAKEEXCEPTION) ConnectionException.txt + diff --git a/lib/server/OverlappedIO.h b/lib/server/OverlappedIO.h new file mode 100644 index 00000000..12495053 --- /dev/null +++ b/lib/server/OverlappedIO.h @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: OverlappedIO.h +// Purpose: Windows overlapped IO handle guard +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- + +#ifndef OVERLAPPEDIO__H +#define OVERLAPPEDIO__H + +class OverlappedIO +{ +public: + OVERLAPPED mOverlapped; + + OverlappedIO() + { + ZeroMemory(&mOverlapped, sizeof(mOverlapped)); + mOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, + NULL); + if (mOverlapped.hEvent == INVALID_HANDLE_VALUE) + { + BOX_LOG_WIN_ERROR("Failed to create event for " + "overlapped I/O"); + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + } + + ~OverlappedIO() + { + if (CloseHandle(mOverlapped.hEvent) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to delete event for " + "overlapped I/O"); + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + } +}; + +#endif // !OVERLAPPEDIO__H diff --git a/lib/server/Protocol.cpp b/lib/server/Protocol.cpp new file mode 100644 index 00000000..5dc5d0b1 --- /dev/null +++ b/lib/server/Protocol.cpp @@ -0,0 +1,1160 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Protocol.cpp +// Purpose: Generic protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include +#include + +#include + +#include "Protocol.h" +#include "ProtocolWire.h" +#include "IOStream.h" +#include "ServerException.h" +#include "PartialReadStream.h" +#include "ProtocolUncertainStream.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#ifdef BOX_RELEASE_BUILD + #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 +#else +// #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 + #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 4 +#endif + +#define UNCERTAIN_STREAM_SIZE_BLOCK (64*1024) + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Protocol(IOStream &rStream) +// Purpose: Constructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +Protocol::Protocol(IOStream &rStream) + : mrStream(rStream), + mHandshakeDone(false), + mMaxObjectSize(PROTOCOL_DEFAULT_MAXOBJSIZE), + mTimeout(PROTOCOL_DEFAULT_TIMEOUT), + mpBuffer(0), + mBufferSize(0), + mReadOffset(-1), + mWriteOffset(-1), + mValidDataSize(-1), + mLastErrorType(NoError), + mLastErrorSubType(NoError) +{ + BOX_TRACE("Send block allocation size is " << + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::~Protocol() +// Purpose: Destructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +Protocol::~Protocol() +{ + // Free buffer? + if(mpBuffer != 0) + { + free(mpBuffer); + mpBuffer = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// 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::GetLastError(int &rTypeOut, int &rSubTypeOut) +{ + if(mLastErrorType == NoError) + { + // no error. + return false; + } + + // Return type and subtype in args + rTypeOut = mLastErrorType; + rSubTypeOut = mLastErrorSubType; + + // and unset them + mLastErrorType = NoError; + mLastErrorSubType = NoError; + + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Handshake() +// Purpose: Handshake with peer (exchange ident strings) +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void Protocol::Handshake() +{ + // Already done? + if(mHandshakeDone) + { + THROW_EXCEPTION(CommonException, Internal) + } + + // Make handshake block + PW_Handshake hsSend; + ::memset(&hsSend, 0, sizeof(hsSend)); + // Copy in ident string + ::strncpy(hsSend.mIdent, GetIdentString(), sizeof(hsSend.mIdent)); + + // Send it + mrStream.Write(&hsSend, sizeof(hsSend)); + mrStream.WriteAllBuffered(); + + // Receive a handshake from the peer + PW_Handshake hsReceive; + ::memset(&hsReceive, 0, sizeof(hsReceive)); + char *readInto = (char*)&hsReceive; + int bytesToRead = sizeof(hsReceive); + while(bytesToRead > 0) + { + // Get some data from the stream + int bytesRead = mrStream.Read(readInto, bytesToRead, mTimeout); + if(bytesRead == 0) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) + } + readInto += bytesRead; + bytesToRead -= bytesRead; + } + ASSERT(bytesToRead == 0); + + // Are they the same? + if(::memcmp(&hsSend, &hsReceive, sizeof(hsSend)) != 0) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_HandshakeFailed) + } + + // Mark as done + mHandshakeDone = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::CheckAndReadHdr(void *) +// Purpose: Check read for recieve call and get object header from stream. +// Don't use type here to avoid dependency in .h file. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::CheckAndReadHdr(void *hdr) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // Get some data into this header + if(!mrStream.ReadFullBuffer(hdr, sizeof(PW_ObjectHeader), 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Recieve() +// Purpose: Recieves an object from the stream, creating it from the factory object type +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +std::auto_ptr Protocol::Receive() +{ + // Get object header + PW_ObjectHeader objHeader; + CheckAndReadHdr(&objHeader); + + // Hope it's not a stream + if(ntohl(objHeader.mObjType) == SPECIAL_STREAM_OBJECT_TYPE) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_StreamWhenObjExpected) + } + + // Check the object size + u_int32_t objSize = ntohl(objHeader.mObjSize); + if(objSize < sizeof(objHeader) || objSize > mMaxObjectSize) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_ObjTooBig) + } + + // Create a blank object + std::auto_ptr obj(MakeProtocolObject(ntohl(objHeader.mObjType))); + + // Make sure memory is allocated to read it into + EnsureBufferAllocated(objSize); + + // Read data + if(!mrStream.ReadFullBuffer(mpBuffer, objSize - sizeof(objHeader), 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_Timeout) + } + + // Setup ready to read out data from the buffer + mValidDataSize = objSize - sizeof(objHeader); + mReadOffset = 0; + + // Get the object to read its properties from the data recieved + try + { + obj->SetPropertiesFromStreamData(*this); + } + catch(...) + { + // Make sure state is reset! + mValidDataSize = -1; + mReadOffset = -1; + throw; + } + + // Any data left over? + bool dataLeftOver = (mValidDataSize != mReadOffset); + + // Unset read state, so future read calls don't fail + mValidDataSize = -1; + mReadOffset = -1; + + // Exception if not all the data was consumed + if(dataLeftOver) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_BadCommandRecieved) + } + + return obj; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Send() +// Purpose: Send an object to the other side of the connection. +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Send(const ProtocolObject &rObject) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // Make sure there's a little bit of space allocated + EnsureBufferAllocated(((sizeof(PW_ObjectHeader) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); + ASSERT(mBufferSize >= (int)sizeof(PW_ObjectHeader)); + + // Setup for write operation + mValidDataSize = 0; // Not used, but must not be -1 + mWriteOffset = sizeof(PW_ObjectHeader); + + try + { + rObject.WritePropertiesToStreamData(*this); + } + catch(...) + { + // Make sure state is reset! + mValidDataSize = -1; + mWriteOffset = -1; + throw; + } + + // How big? + int writtenSize = mWriteOffset; + + // Reset write state + mValidDataSize = -1; + mWriteOffset = -1; + + // Make header in the existing block + PW_ObjectHeader *pobjHeader = (PW_ObjectHeader*)(mpBuffer); + pobjHeader->mObjSize = htonl(writtenSize); + pobjHeader->mObjType = htonl(rObject.GetType()); + + // Write data + mrStream.Write(mpBuffer, writtenSize); + mrStream.WriteAllBuffered(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::EnsureBufferAllocated(int) +// Purpose: Private. Ensures the buffer is at least the size requested. +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::EnsureBufferAllocated(int Size) +{ + if(mpBuffer != 0 && mBufferSize >= Size) + { + // Nothing to do! + return; + } + + // Need to allocate, or reallocate, the block + if(mpBuffer != 0) + { + // Reallocate + void *b = realloc(mpBuffer, Size); + if(b == 0) + { + throw std::bad_alloc(); + } + mpBuffer = (char*)b; + mBufferSize = Size; + } + else + { + // Just allocate + mpBuffer = (char*)malloc(Size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } + mBufferSize = Size; + } +} + + +#define READ_START_CHECK \ + if(mValidDataSize == -1 || mWriteOffset != -1 || mReadOffset == -1) \ + { \ + THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ + } + +#define READ_CHECK_BYTES_AVAILABLE(bytesRequired) \ + if((mReadOffset + (int)(bytesRequired)) > mValidDataSize) \ + { \ + THROW_EXCEPTION(ConnectionException, Conn_Protocol_BadCommandRecieved) \ + } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(void *, int) +// Purpose: Read raw data from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(void *Buffer, int Size) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(Size) + + // Copy data out + ::memmove(Buffer, mpBuffer + mReadOffset, Size); + mReadOffset += Size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(std::string &, int) +// Purpose: Read raw data from the stream (buffered), into a std::string +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::Read(std::string &rOut, int Size) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(Size) + + rOut.assign(mpBuffer + mReadOffset, Size); + mReadOffset += Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int64_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int64_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int64_t)) + +#ifdef HAVE_ALIGNED_ONLY_INT64 + int64_t nvalue; + memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int64_t)); +#else + int64_t nvalue = *((int64_t*)(mpBuffer + mReadOffset)); +#endif + rOut = box_ntoh64(nvalue); + + mReadOffset += sizeof(int64_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int32_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int32_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int32_t)) + +#ifdef HAVE_ALIGNED_ONLY_INT32 + int32_t nvalue; + memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int32_t)); +#else + int32_t nvalue = *((int32_t*)(mpBuffer + mReadOffset)); +#endif + rOut = ntohl(nvalue); + mReadOffset += sizeof(int32_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int16_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int16_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int16_t)) + + rOut = ntohs(*((int16_t*)(mpBuffer + mReadOffset))); + mReadOffset += sizeof(int16_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int8_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int8_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int8_t)) + + rOut = *((int8_t*)(mpBuffer + mReadOffset)); + mReadOffset += sizeof(int8_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(std::string &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(std::string &rOut) +{ + // READ_START_CHECK implied + int32_t size; + Read(size); + + READ_CHECK_BYTES_AVAILABLE(size) + + // initialise string + rOut.assign(mpBuffer + mReadOffset, size); + mReadOffset += size; +} + + + + +#define WRITE_START_CHECK \ + if(mValidDataSize == -1 || mWriteOffset == -1 || mReadOffset != -1) \ + { \ + THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ + } + +#define WRITE_ENSURE_BYTES_AVAILABLE(bytesToWrite) \ + if(mWriteOffset + (int)(bytesToWrite) > mBufferSize) \ + { \ + EnsureBufferAllocated((((mWriteOffset + (int)(bytesToWrite)) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); \ + ASSERT(mWriteOffset + (int)(bytesToWrite) <= mBufferSize); \ + } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(const void *, int) +// Purpose: Writes the contents of a buffer to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(const void *Buffer, int Size) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(Size) + + ::memmove(mpBuffer + mWriteOffset, Buffer, Size); + mWriteOffset += Size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int64_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int64_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int64_t)) + + int64_t nvalue = box_hton64(Value); +#ifdef HAVE_ALIGNED_ONLY_INT64 + memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int64_t)); +#else + *((int64_t*)(mpBuffer + mWriteOffset)) = nvalue; +#endif + mWriteOffset += sizeof(int64_t); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int32_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int32_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int32_t)) + + int32_t nvalue = htonl(Value); +#ifdef HAVE_ALIGNED_ONLY_INT32 + memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int32_t)); +#else + *((int32_t*)(mpBuffer + mWriteOffset)) = nvalue; +#endif + mWriteOffset += sizeof(int32_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int16_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int16_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int16_t)) + + *((int16_t*)(mpBuffer + mWriteOffset)) = htons(Value); + mWriteOffset += sizeof(int16_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int8_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int8_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int8_t)) + + *((int8_t*)(mpBuffer + mWriteOffset)) = Value; + mWriteOffset += sizeof(int8_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(const std::string &) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(const std::string &rValue) +{ + // WRITE_START_CHECK implied + Write((int32_t)(rValue.size())); + + WRITE_ENSURE_BYTES_AVAILABLE(rValue.size()) + Write(rValue.c_str(), rValue.size()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::ReceieveStream() +// Purpose: Receive a stream from the remote side +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +std::auto_ptr Protocol::ReceiveStream() +{ + // Get object header + PW_ObjectHeader objHeader; + CheckAndReadHdr(&objHeader); + + // Hope it's not an object + if(ntohl(objHeader.mObjType) != SPECIAL_STREAM_OBJECT_TYPE) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_ObjWhenStreamExpected) + } + + // Get the stream size + u_int32_t streamSize = ntohl(objHeader.mObjSize); + + // Inform sub class + InformStreamReceiving(streamSize); + + // Return a stream object + if(streamSize == ProtocolStream_SizeUncertain) + { + BOX_TRACE("Receiving stream, size uncertain"); + return std::auto_ptr( + new ProtocolUncertainStream(mrStream)); + } + else + { + BOX_TRACE("Receiving stream, size " << streamSize << " bytes"); + return std::auto_ptr( + new PartialReadStream(mrStream, streamSize)); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::SendStream(IOStream &) +// Purpose: Send a stream to the remote side +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::SendStream(IOStream &rStream) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // How should this be streamed? + bool uncertainSize = false; + IOStream::pos_type streamSize = rStream.BytesLeftToRead(); + if(streamSize == IOStream::SizeOfStreamUnknown + || streamSize > 0x7fffffff) + { + // Can't send this using the fixed size header + uncertainSize = true; + } + + // Inform sub class + InformStreamSending(streamSize); + + // Make header + PW_ObjectHeader objHeader; + objHeader.mObjSize = htonl(uncertainSize?(ProtocolStream_SizeUncertain):streamSize); + objHeader.mObjType = htonl(SPECIAL_STREAM_OBJECT_TYPE); + + // Write header + mrStream.Write(&objHeader, sizeof(objHeader)); + // Could be sent in one of two ways + if(uncertainSize) + { + // Don't know how big this is going to be -- so send it in chunks + + // Allocate memory + uint8_t *blockA = (uint8_t *)malloc(UNCERTAIN_STREAM_SIZE_BLOCK + sizeof(int)); + if(blockA == 0) + { + throw std::bad_alloc(); + } + uint8_t *block = blockA + sizeof(int); // so that everything is word aligned for reading, but can put the one byte header before it + + try + { + int bytesInBlock = 0; + while(rStream.StreamDataLeft()) + { + // Read some of it + bytesInBlock += rStream.Read(block + bytesInBlock, UNCERTAIN_STREAM_SIZE_BLOCK - bytesInBlock); + + // Send as much as we can out + bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); + } + + // Everything recieved from stream, but need to send whatevers left in the block + while(bytesInBlock > 0) + { + bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); + } + + // Send final byte to finish the stream + BOX_TRACE("Sending end of stream byte"); + uint8_t endOfStream = ProtocolStreamHeader_EndOfStream; + mrStream.Write(&endOfStream, 1); + BOX_TRACE("Sent end of stream byte"); + } + catch(...) + { + free(blockA); + throw; + } + + // Clean up + free(blockA); + } + else + { + // Fixed size stream, send it all in one go + if(!rStream.CopyStreamTo(mrStream, mTimeout, 4096 /* slightly larger buffer */)) + { + THROW_EXCEPTION(ConnectionException, Conn_Protocol_TimeOutWhenSendingStream) + } + } + // Make sure everything is written + mrStream.WriteAllBuffered(); + +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::SendStreamSendBlock(uint8_t *, int) +// Purpose: Sends as much of the block as can be sent, moves the remainer down to the beginning, +// and returns the number of bytes sent. WARNING: Will write to Block[-1] +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- +int Protocol::SendStreamSendBlock(uint8_t *Block, int BytesInBlock) +{ + // Quick sanity check + if(BytesInBlock == 0) + { + BOX_TRACE("Zero size block, not sending anything"); + return 0; + } + + // Work out the header byte + uint8_t header = 0; + int writeSize = 0; + if(BytesInBlock >= (64*1024)) + { + header = ProtocolStreamHeader_SizeIs64k; + writeSize = (64*1024); + } + else + { + // Scan the table to find the most that can be written + for(int s = ProtocolStreamHeader_MaxEncodedSizeValue; s > 0; --s) + { + if(sProtocolStreamHeaderLengths[s] <= BytesInBlock) + { + header = s; + writeSize = sProtocolStreamHeaderLengths[s]; + break; + } + } + } + ASSERT(header > 0); + BOX_TRACE("Sending header byte " << (int)header << " plus " << + writeSize << " bytes to stream"); + + // Store the header + Block[-1] = header; + + // Write everything out + mrStream.Write(Block - 1, writeSize + 1); + + BOX_TRACE("Sent " << (writeSize+1) << " bytes to stream"); + // move the remainer to the beginning of the block for the next time round + if(writeSize != BytesInBlock) + { + ::memmove(Block, Block + writeSize, BytesInBlock - writeSize); + } + + return writeSize; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::InformStreamReceiving(u_int32_t) +// Purpose: Informs sub classes about streams being received +// Created: 2003/10/27 +// +// -------------------------------------------------------------------------- +void Protocol::InformStreamReceiving(u_int32_t Size) +{ + // Do nothing +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::InformStreamSending(u_int32_t) +// Purpose: Informs sub classes about streams being sent +// Created: 2003/10/27 +// +// -------------------------------------------------------------------------- +void Protocol::InformStreamSending(u_int32_t Size) +{ + // Do nothing +} + + +/* +perl code to generate the table below + +#!/usr/bin/perl +use strict; +open OUT,">protolengths.txt"; +my $len = 0; +for(0 .. 255) +{ + print OUT "\t$len,\t// $_\n"; + my $inc = 1; + $inc = 8 if $_ >= 64; + $inc = 16 if $_ >= 96; + $inc = 32 if $_ >= 112; + $inc = 64 if $_ >= 128; + $inc = 128 if $_ >= 135; + $inc = 256 if $_ >= 147; + $inc = 512 if $_ >= 159; + $inc = 1024 if $_ >= 231; + $len += $inc; +} +close OUT; + +*/ +const uint16_t Protocol::sProtocolStreamHeaderLengths[256] = +{ + 0, // 0 + 1, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 6, // 6 + 7, // 7 + 8, // 8 + 9, // 9 + 10, // 10 + 11, // 11 + 12, // 12 + 13, // 13 + 14, // 14 + 15, // 15 + 16, // 16 + 17, // 17 + 18, // 18 + 19, // 19 + 20, // 20 + 21, // 21 + 22, // 22 + 23, // 23 + 24, // 24 + 25, // 25 + 26, // 26 + 27, // 27 + 28, // 28 + 29, // 29 + 30, // 30 + 31, // 31 + 32, // 32 + 33, // 33 + 34, // 34 + 35, // 35 + 36, // 36 + 37, // 37 + 38, // 38 + 39, // 39 + 40, // 40 + 41, // 41 + 42, // 42 + 43, // 43 + 44, // 44 + 45, // 45 + 46, // 46 + 47, // 47 + 48, // 48 + 49, // 49 + 50, // 50 + 51, // 51 + 52, // 52 + 53, // 53 + 54, // 54 + 55, // 55 + 56, // 56 + 57, // 57 + 58, // 58 + 59, // 59 + 60, // 60 + 61, // 61 + 62, // 62 + 63, // 63 + 64, // 64 + 72, // 65 + 80, // 66 + 88, // 67 + 96, // 68 + 104, // 69 + 112, // 70 + 120, // 71 + 128, // 72 + 136, // 73 + 144, // 74 + 152, // 75 + 160, // 76 + 168, // 77 + 176, // 78 + 184, // 79 + 192, // 80 + 200, // 81 + 208, // 82 + 216, // 83 + 224, // 84 + 232, // 85 + 240, // 86 + 248, // 87 + 256, // 88 + 264, // 89 + 272, // 90 + 280, // 91 + 288, // 92 + 296, // 93 + 304, // 94 + 312, // 95 + 320, // 96 + 336, // 97 + 352, // 98 + 368, // 99 + 384, // 100 + 400, // 101 + 416, // 102 + 432, // 103 + 448, // 104 + 464, // 105 + 480, // 106 + 496, // 107 + 512, // 108 + 528, // 109 + 544, // 110 + 560, // 111 + 576, // 112 + 608, // 113 + 640, // 114 + 672, // 115 + 704, // 116 + 736, // 117 + 768, // 118 + 800, // 119 + 832, // 120 + 864, // 121 + 896, // 122 + 928, // 123 + 960, // 124 + 992, // 125 + 1024, // 126 + 1056, // 127 + 1088, // 128 + 1152, // 129 + 1216, // 130 + 1280, // 131 + 1344, // 132 + 1408, // 133 + 1472, // 134 + 1536, // 135 + 1664, // 136 + 1792, // 137 + 1920, // 138 + 2048, // 139 + 2176, // 140 + 2304, // 141 + 2432, // 142 + 2560, // 143 + 2688, // 144 + 2816, // 145 + 2944, // 146 + 3072, // 147 + 3328, // 148 + 3584, // 149 + 3840, // 150 + 4096, // 151 + 4352, // 152 + 4608, // 153 + 4864, // 154 + 5120, // 155 + 5376, // 156 + 5632, // 157 + 5888, // 158 + 6144, // 159 + 6656, // 160 + 7168, // 161 + 7680, // 162 + 8192, // 163 + 8704, // 164 + 9216, // 165 + 9728, // 166 + 10240, // 167 + 10752, // 168 + 11264, // 169 + 11776, // 170 + 12288, // 171 + 12800, // 172 + 13312, // 173 + 13824, // 174 + 14336, // 175 + 14848, // 176 + 15360, // 177 + 15872, // 178 + 16384, // 179 + 16896, // 180 + 17408, // 181 + 17920, // 182 + 18432, // 183 + 18944, // 184 + 19456, // 185 + 19968, // 186 + 20480, // 187 + 20992, // 188 + 21504, // 189 + 22016, // 190 + 22528, // 191 + 23040, // 192 + 23552, // 193 + 24064, // 194 + 24576, // 195 + 25088, // 196 + 25600, // 197 + 26112, // 198 + 26624, // 199 + 27136, // 200 + 27648, // 201 + 28160, // 202 + 28672, // 203 + 29184, // 204 + 29696, // 205 + 30208, // 206 + 30720, // 207 + 31232, // 208 + 31744, // 209 + 32256, // 210 + 32768, // 211 + 33280, // 212 + 33792, // 213 + 34304, // 214 + 34816, // 215 + 35328, // 216 + 35840, // 217 + 36352, // 218 + 36864, // 219 + 37376, // 220 + 37888, // 221 + 38400, // 222 + 38912, // 223 + 39424, // 224 + 39936, // 225 + 40448, // 226 + 40960, // 227 + 41472, // 228 + 41984, // 229 + 42496, // 230 + 43008, // 231 + 44032, // 232 + 45056, // 233 + 46080, // 234 + 47104, // 235 + 48128, // 236 + 49152, // 237 + 50176, // 238 + 51200, // 239 + 52224, // 240 + 53248, // 241 + 54272, // 242 + 55296, // 243 + 56320, // 244 + 57344, // 245 + 58368, // 246 + 59392, // 247 + 60416, // 248 + 61440, // 249 + 62464, // 250 + 63488, // 251 + 64512, // 252 + 0, // 253 = 65536 / 64k + 0, // 254 = special (reserved) + 0 // 255 = special (reserved) +}; + + + + diff --git a/lib/server/Protocol.h b/lib/server/Protocol.h new file mode 100644 index 00000000..e037e33c --- /dev/null +++ b/lib/server/Protocol.h @@ -0,0 +1,201 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Protocol.h +// Purpose: Generic protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOL__H +#define PROTOCOL__H + +#include + +class IOStream; +#include "ProtocolObject.h" +#include +#include +#include + +// default timeout is 15 minutes +#define PROTOCOL_DEFAULT_TIMEOUT (15*60*1000) +// 16 default maximum object size -- should be enough +#define PROTOCOL_DEFAULT_MAXOBJSIZE (16*1024) + +// -------------------------------------------------------------------------- +// +// Class +// Name: Protocol +// Purpose: Generic command / response protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +class Protocol +{ +public: + Protocol(IOStream &rStream); + virtual ~Protocol(); + +private: + Protocol(const Protocol &rToCopy); + +public: + void Handshake(); + std::auto_ptr Receive(); + void Send(const ProtocolObject &rObject); + + std::auto_ptr ReceiveStream(); + void SendStream(IOStream &rStream); + + enum + { + NoError = -1, + UnknownError = 0 + }; + + bool GetLastError(int &rTypeOut, int &rSubTypeOut); + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::SetTimeout(int) + // Purpose: Sets the timeout for sending and reciving + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void SetTimeout(int NewTimeout) {mTimeout = NewTimeout;} + + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::GetTimeout() + // Purpose: Get current timeout for sending and receiving + // Created: 2003/09/06 + // + // -------------------------------------------------------------------------- + int GetTimeout() {return mTimeout;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::SetMaxObjectSize(int) + // Purpose: Sets the maximum size of an object which will be accepted + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void SetMaxObjectSize(unsigned int NewMaxObjSize) {mMaxObjectSize = NewMaxObjSize;} + + // For ProtocolObject derived classes + void Read(void *Buffer, int Size); + void Read(std::string &rOut, int Size); + void Read(int64_t &rOut); + void Read(int32_t &rOut); + void Read(int16_t &rOut); + void Read(int8_t &rOut); + void Read(bool &rOut) {int8_t read; Read(read); rOut = (read == true);} + void Read(std::string &rOut); + template + void Read(type &rOut) + { + rOut.ReadFromProtocol(*this); + } + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::ReadVector(std::vector<> &) + // Purpose: Reads a vector/list of items from the stream + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + template + void ReadVector(std::vector &rOut) + { + rOut.clear(); + int16_t num = 0; + Read(num); + for(int16_t n = 0; n < num; ++n) + { + type v; + Read(v); + rOut.push_back(v); + } + } + + void Write(const void *Buffer, int Size); + void Write(int64_t Value); + void Write(int32_t Value); + void Write(int16_t Value); + void Write(int8_t Value); + void Write(bool Value) {int8_t write = Value; Write(write);} + void Write(const std::string &rValue); + template + void Write(const type &rValue) + { + rValue.WriteToProtocol(*this); + } + template + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::WriteVector(const std::vector<> &) + // Purpose: Writes a vector/list of items from the stream + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void WriteVector(const std::vector &rValue) + { + int16_t num = rValue.size(); + Write(num); + for(int16_t n = 0; n < num; ++n) + { + Write(rValue[n]); + } + } + +public: + static const uint16_t sProtocolStreamHeaderLengths[256]; + enum + { + ProtocolStreamHeader_EndOfStream = 0, + ProtocolStreamHeader_MaxEncodedSizeValue = 252, + ProtocolStreamHeader_SizeIs64k = 253, + ProtocolStreamHeader_Reserved1 = 254, + ProtocolStreamHeader_Reserved2 = 255 + }; + enum + { + ProtocolStream_SizeUncertain = 0xffffffff + }; + +protected: + virtual std::auto_ptr MakeProtocolObject(int ObjType) = 0; + virtual const char *GetIdentString() = 0; + void SetError(int Type, int SubType) {mLastErrorType = Type; mLastErrorSubType = SubType;} + void CheckAndReadHdr(void *hdr); // don't use type here to avoid dependency + + // Will be used for logging + virtual void InformStreamReceiving(u_int32_t Size); + virtual void InformStreamSending(u_int32_t Size); + +private: + void EnsureBufferAllocated(int Size); + int SendStreamSendBlock(uint8_t *Block, int BytesInBlock); + +private: + IOStream &mrStream; + bool mHandshakeDone; + unsigned int mMaxObjectSize; + int mTimeout; + char *mpBuffer; + int mBufferSize; + int mReadOffset; + int mWriteOffset; + int mValidDataSize; + int mLastErrorType; + int mLastErrorSubType; +}; + +#endif // PROTOCOL__H + diff --git a/lib/server/ProtocolObject.cpp b/lib/server/ProtocolObject.cpp new file mode 100644 index 00000000..fb09f820 --- /dev/null +++ b/lib/server/ProtocolObject.cpp @@ -0,0 +1,125 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolObject.h +// Purpose: Protocol object base class +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "ProtocolObject.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::ProtocolObject() +// Purpose: Default constructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +ProtocolObject::ProtocolObject() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::ProtocolObject() +// Purpose: Destructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +ProtocolObject::~ProtocolObject() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::ProtocolObject() +// Purpose: Copy constructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +ProtocolObject::ProtocolObject(const ProtocolObject &rToCopy) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::IsError(int &, int &) +// Purpose: Does this represent an error, and if so, what is the type and subtype? +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +bool ProtocolObject::IsError(int &rTypeOut, int &rSubTypeOut) const +{ + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::IsConversationEnd() +// Purpose: Does this command end the conversation? +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +bool ProtocolObject::IsConversationEnd() const +{ + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::GetType() +// Purpose: Return type of the object +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +int ProtocolObject::GetType() const +{ + // This isn't implemented in the base class! + THROW_EXCEPTION(CommonException, Internal) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::SetPropertiesFromStreamData(Protocol &) +// Purpose: Set the properties of the object from the stream data ready in the Protocol object +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void ProtocolObject::SetPropertiesFromStreamData(Protocol &rProtocol) +{ + // This isn't implemented in the base class! + THROW_EXCEPTION(CommonException, Internal) +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolObject::WritePropertiesToStreamData(Protocol &) +// Purpose: Write the properties of the object into the stream data in the Protocol object +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void ProtocolObject::WritePropertiesToStreamData(Protocol &rProtocol) const +{ + // This isn't implemented in the base class! + THROW_EXCEPTION(CommonException, Internal) +} + + + diff --git a/lib/server/ProtocolObject.h b/lib/server/ProtocolObject.h new file mode 100644 index 00000000..0a127ab5 --- /dev/null +++ b/lib/server/ProtocolObject.h @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolObject.h +// Purpose: Protocol object base class +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLOBJECT__H +#define PROTOCOLOBJECT__H + +class Protocol; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProtocolObject +// Purpose: Basic object representation of objects to pass through a Protocol session +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +class ProtocolObject +{ +public: + ProtocolObject(); + virtual ~ProtocolObject(); + ProtocolObject(const ProtocolObject &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; +}; + +#endif // PROTOCOLOBJECT__H + diff --git a/lib/server/ProtocolUncertainStream.cpp b/lib/server/ProtocolUncertainStream.cpp new file mode 100644 index 00000000..84a213a8 --- /dev/null +++ b/lib/server/ProtocolUncertainStream.cpp @@ -0,0 +1,206 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolUncertainStream.h +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "ProtocolUncertainStream.h" +#include "ServerException.h" +#include "Protocol.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::ProtocolUncertainStream(IOStream &, int) +// Purpose: Constructor, taking another stream. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +ProtocolUncertainStream::ProtocolUncertainStream(IOStream &rSource) + : mrSource(rSource), + mBytesLeftInCurrentBlock(0), + mFinished(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::~ProtocolUncertainStream() +// Purpose: Destructor. Won't absorb any unread bytes. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +ProtocolUncertainStream::~ProtocolUncertainStream() +{ + if(!mFinished) + { + BOX_WARNING("ProtocolUncertainStream destroyed before " + "stream finished"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +int ProtocolUncertainStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Finished? + if(mFinished) + { + return 0; + } + + int read = 0; + while(read < NBytes) + { + // Anything we can get from the current block? + ASSERT(mBytesLeftInCurrentBlock >= 0); + if(mBytesLeftInCurrentBlock > 0) + { + // Yes, let's use some of these up + int toRead = (NBytes - read); + if(toRead > mBytesLeftInCurrentBlock) + { + // Adjust downwards to only read stuff out of the current block + toRead = mBytesLeftInCurrentBlock; + } + + BOX_TRACE("Reading " << toRead << " bytes from stream"); + + // Read it + int r = mrSource.Read(((uint8_t*)pBuffer) + read, toRead, Timeout); + // Give up now if it didn't return anything + if(r == 0) + { + BOX_TRACE("Read " << r << " bytes from " + "stream, returning"); + return read; + } + + // Adjust counts of bytes by the bytes recieved + read += r; + mBytesLeftInCurrentBlock -= r; + + // stop now if the stream returned less than we asked for -- avoid blocking + if(r != toRead) + { + BOX_TRACE("Read " << r << " bytes from " + "stream, returning"); + return read; + } + } + else + { + // Read the header byte to find out how much there is + // in the next block + uint8_t header; + if(mrSource.Read(&header, 1, Timeout) == 0) + { + // Didn't get the byte, return now + BOX_TRACE("Read 0 bytes of block header, " + "returning with " << read << " bytes " + "read this time"); + return read; + } + + // Interpret the byte... + if(header == Protocol::ProtocolStreamHeader_EndOfStream) + { + // All done. + mFinished = true; + BOX_TRACE("Stream finished, returning with " << + read << " bytes read this time"); + return read; + } + else if(header <= Protocol::ProtocolStreamHeader_MaxEncodedSizeValue) + { + // get size of the block from the Protocol's lovely list + mBytesLeftInCurrentBlock = Protocol::sProtocolStreamHeaderLengths[header]; + } + else if(header == Protocol::ProtocolStreamHeader_SizeIs64k) + { + // 64k + mBytesLeftInCurrentBlock = (64*1024); + } + else + { + // Bad. It used the reserved values. + THROW_EXCEPTION(ServerException, ProtocolUncertainStreamBadBlockHeader) + } + + BOX_TRACE("Read header byte " << (int)header << ", " + "next block has " << + mBytesLeftInCurrentBlock << " bytes"); + } + } + + // Return the number read + return read; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::BytesLeftToRead() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ProtocolUncertainStream::BytesLeftToRead() +{ + // Only know how much is left if everything is finished + return mFinished?(0):(IOStream::SizeOfStreamUnknown); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::Write(const void *, int) +// Purpose: As interface. But will exception. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +void ProtocolUncertainStream::Write(const void *pBuffer, int NBytes) +{ + THROW_EXCEPTION(ServerException, CantWriteToProtocolUncertainStream) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::StreamDataLeft() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +bool ProtocolUncertainStream::StreamDataLeft() +{ + return !mFinished; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::StreamClosed() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +bool ProtocolUncertainStream::StreamClosed() +{ + // always closed + return true; +} + diff --git a/lib/server/ProtocolUncertainStream.h b/lib/server/ProtocolUncertainStream.h new file mode 100644 index 00000000..4954cf88 --- /dev/null +++ b/lib/server/ProtocolUncertainStream.h @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolUncertainStream.h +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLUNCERTAINSTREAM__H +#define PROTOCOLUNCERTAINSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProtocolUncertainStream +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +class ProtocolUncertainStream : public IOStream +{ +public: + ProtocolUncertainStream(IOStream &rSource); + ~ProtocolUncertainStream(); +private: + // no copying allowed + ProtocolUncertainStream(const IOStream &); + ProtocolUncertainStream(const ProtocolUncertainStream &); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + IOStream &mrSource; + int mBytesLeftInCurrentBlock; + bool mFinished; +}; + +#endif // PROTOCOLUNCERTAINSTREAM__H + diff --git a/lib/server/ProtocolWire.h b/lib/server/ProtocolWire.h new file mode 100644 index 00000000..ff62b66e --- /dev/null +++ b/lib/server/ProtocolWire.h @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolWire.h +// Purpose: On the wire structures for Protocol +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLWIRE__H +#define PROTOCOLWIRE__H + +#include + +// set packing to one byte +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "BeginStructPackForWire.h" +#else +BEGIN_STRUCTURE_PACKING_FOR_WIRE +#endif + +typedef struct +{ + char mIdent[32]; +} PW_Handshake; + +typedef struct +{ + u_int32_t mObjSize; + u_int32_t mObjType; +} PW_ObjectHeader; + +#define SPECIAL_STREAM_OBJECT_TYPE 0xffffffff + +// Use default packing +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "EndStructPackForWire.h" +#else +END_STRUCTURE_PACKING_FOR_WIRE +#endif + +#endif // PROTOCOLWIRE__H + diff --git a/lib/server/SSLLib.cpp b/lib/server/SSLLib.cpp new file mode 100644 index 00000000..de7a941b --- /dev/null +++ b/lib/server/SSLLib.cpp @@ -0,0 +1,111 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SSLLib.cpp +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include +#include +#include + +#ifdef WIN32 + #include +#endif + +#include "SSLLib.h" +#include "ServerException.h" + +#include "MemLeakFindOn.h" + +#ifndef BOX_RELEASE_BUILD + bool SSLLib__TraceErrors = false; +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: SSLLib::Initialise() +// Purpose: Initialise SSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SSLLib::Initialise() +{ + if(!::SSL_library_init()) + { + LogError("initialising OpenSSL"); + THROW_EXCEPTION(ServerException, SSLLibraryInitialisationError) + } + + // More helpful error messages + ::SSL_load_error_strings(); + + // Extra seeding over and above what's already done by the library +#ifdef WIN32 + HCRYPTPROV provider; + if(!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + { + BOX_LOG_WIN_ERROR("Failed to acquire crypto context"); + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); + } + else + { + // must free provider + BYTE buf[1024]; + + if(!CryptGenRandom(provider, sizeof(buf), buf)) + { + BOX_LOG_WIN_ERROR("Failed to get random data"); + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); + } + else + { + RAND_seed(buf, sizeof(buf)); + } + + if(!CryptReleaseContext(provider, 0)) + { + BOX_LOG_WIN_ERROR("Failed to release crypto context"); + } + } +#elif HAVE_RANDOM_DEVICE + if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) + { + THROW_EXCEPTION(ServerException, SSLRandomInitFailed) + } +#else + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SSLLib::LogError(const char *) +// Purpose: Logs an error +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SSLLib::LogError(const std::string& rErrorDuringAction) +{ + unsigned long errcode; + char errname[256]; // SSL docs say at least 120 bytes + while((errcode = ERR_get_error()) != 0) + { + ::ERR_error_string_n(errcode, errname, sizeof(errname)); + BOX_ERROR("SSL error while " << rErrorDuringAction << ": " << + errname); + } +} + diff --git a/lib/server/SSLLib.h b/lib/server/SSLLib.h new file mode 100644 index 00000000..ff4aab19 --- /dev/null +++ b/lib/server/SSLLib.h @@ -0,0 +1,36 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SSLLib.h +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SSLLIB__H +#define SSLLIB__H + +#ifndef BOX_RELEASE_BUILD + extern bool SSLLib__TraceErrors; + #define SET_DEBUG_SSLLIB_TRACE_ERRORS {SSLLib__TraceErrors = true;} +#else + #define SET_DEBUG_SSLLIB_TRACE_ERRORS +#endif + + +// -------------------------------------------------------------------------- +// +// Namespace +// Name: SSLLib +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +namespace SSLLib +{ + void Initialise(); + void LogError(const std::string& rErrorDuringAction); +}; + +#endif // SSLLIB__H + diff --git a/lib/server/ServerControl.cpp b/lib/server/ServerControl.cpp new file mode 100644 index 00000000..b9650cee --- /dev/null +++ b/lib/server/ServerControl.cpp @@ -0,0 +1,228 @@ +#include "Box.h" + +#include +#include + +#ifdef HAVE_SYS_TYPES_H + #include +#endif + +#ifdef HAVE_SYS_WAIT_H + #include +#endif + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#include "ServerControl.h" +#include "Test.h" + +#ifdef WIN32 + +#include "WinNamedPipeStream.h" +#include "IOStreamGetLine.h" +#include "BoxPortsAndFiles.h" + +static std::string sPipeName; + +void SetNamedPipeName(const std::string& rPipeName) +{ + sPipeName = rPipeName; +} + +bool SendCommands(const std::string& rCmd) +{ + WinNamedPipeStream connection; + + try + { + connection.Connect(sPipeName); + } + catch(...) + { + BOX_ERROR("Failed to connect to daemon control socket"); + return false; + } + + // For receiving data + IOStreamGetLine getLine(connection); + + // Wait for the configuration summary + std::string configSummary; + if(!getLine.GetLine(configSummary)) + { + BOX_ERROR("Failed to receive configuration summary from daemon"); + return false; + } + + // Was the connection rejected by the server? + if(getLine.IsEOF()) + { + BOX_ERROR("Server rejected the connection"); + return false; + } + + // Decode it + int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; + if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", + &autoBackup, &updateStoreInterval, + &minimumFileAge, &maxUploadWait) != 4) + { + BOX_ERROR("Config summary didn't decode"); + return false; + } + + std::string cmds; + bool expectResponse; + + if (rCmd != "") + { + cmds = rCmd; + cmds += "\nquit\n"; + expectResponse = true; + } + else + { + cmds = "quit\n"; + expectResponse = false; + } + + connection.Write(cmds.c_str(), cmds.size()); + + // Read the response + std::string line; + bool statusOk = !expectResponse; + + while (expectResponse && !getLine.IsEOF() && getLine.GetLine(line)) + { + // Is this an OK or error line? + if (line == "ok") + { + statusOk = true; + } + else if (line == "error") + { + BOX_ERROR(rCmd); + break; + } + else + { + BOX_WARNING("Unexpected response to command '" << + rCmd << "': " << line) + } + } + + return statusOk; +} + +bool HUPServer(int pid) +{ + return SendCommands("reload"); +} + +bool KillServerInternal(int pid) +{ + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, false, pid); + if (hProcess == NULL) + { + BOX_ERROR("Failed to open process " << pid << ": " << + GetErrorMessage(GetLastError())); + return false; + } + + if (!TerminateProcess(hProcess, 1)) + { + BOX_ERROR("Failed to terminate process " << pid << ": " << + GetErrorMessage(GetLastError())); + CloseHandle(hProcess); + return false; + } + + CloseHandle(hProcess); + return true; +} + +#else // !WIN32 + +bool HUPServer(int pid) +{ + if(pid == 0) return false; + return ::kill(pid, SIGHUP) == 0; +} + +bool KillServerInternal(int pid) +{ + if(pid == 0 || pid == -1) return false; + bool killed = (::kill(pid, SIGTERM) == 0); + if (!killed) + { + BOX_LOG_SYS_ERROR("Failed to kill process " << pid); + } + TEST_THAT(killed); + return killed; +} + +#endif // WIN32 + +bool KillServer(int pid, bool WaitForProcess) +{ + if (!KillServerInternal(pid)) + { + return false; + } + + #ifdef HAVE_WAITPID + if (WaitForProcess) + { + int status, result; + + result = waitpid(pid, &status, 0); + if (result != pid) + { + BOX_LOG_SYS_ERROR("waitpid failed"); + } + TEST_THAT(result == pid); + + TEST_THAT(WIFEXITED(status)); + if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) != 0) + { + BOX_WARNING("process exited with code " << + WEXITSTATUS(status)); + } + TEST_THAT(WEXITSTATUS(status) == 0); + } + } + #endif + + for (int i = 0; i < 30; i++) + { + if (i == 0) + { + printf("Waiting for server to die (pid %d): ", pid); + } + + printf("."); + fflush(stdout); + + if (!ServerIsAlive(pid)) break; + ::sleep(1); + if (!ServerIsAlive(pid)) break; + } + + if (!ServerIsAlive(pid)) + { + printf(" done.\n"); + } + else + { + printf(" failed!\n"); + } + + fflush(stdout); + + return !ServerIsAlive(pid); +} + diff --git a/lib/server/ServerControl.h b/lib/server/ServerControl.h new file mode 100644 index 00000000..b2e51864 --- /dev/null +++ b/lib/server/ServerControl.h @@ -0,0 +1,18 @@ +#ifndef SERVER_CONTROL_H +#define SERVER_CONTROL_H + +#include "Test.h" + +bool HUPServer(int pid); +bool KillServer(int pid, bool WaitForProcess = false); + +#ifdef WIN32 + #include "WinNamedPipeStream.h" + #include "IOStreamGetLine.h" + #include "BoxPortsAndFiles.h" + + void SetNamedPipeName(const std::string& rPipeName); + // bool SendCommands(const std::string& rCmd); +#endif // WIN32 + +#endif // SERVER_CONTROL_H diff --git a/lib/server/ServerException.h b/lib/server/ServerException.h new file mode 100644 index 00000000..8851b90a --- /dev/null +++ b/lib/server/ServerException.h @@ -0,0 +1,46 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ServerException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef SERVEREXCEPTION__H +#define SERVEREXCEPTION__H + +// Compatibility header +#include "autogen_ServerException.h" +#include "autogen_ConnectionException.h" + +// Rename old connection exception names to new names without Conn_ prefix +// This is all because ConnectionException used to be derived from ServerException +// with some funky magic with subtypes. Perhaps a little unreliable, and the +// usefulness of it never really was used. +#define Conn_SocketWriteError SocketWriteError +#define Conn_SocketReadError SocketReadError +#define Conn_SocketNameLookupError SocketNameLookupError +#define Conn_SocketShutdownError SocketShutdownError +#define Conn_SocketConnectError SocketConnectError +#define Conn_TLSHandshakeFailed TLSHandshakeFailed +#define Conn_TLSShutdownFailed TLSShutdownFailed +#define Conn_TLSWriteFailed TLSWriteFailed +#define Conn_TLSReadFailed TLSReadFailed +#define Conn_TLSNoPeerCertificate TLSNoPeerCertificate +#define Conn_TLSPeerCertificateInvalid TLSPeerCertificateInvalid +#define Conn_TLSClosedWhenWriting TLSClosedWhenWriting +#define Conn_TLSHandshakeTimedOut TLSHandshakeTimedOut +#define Conn_Protocol_Timeout Protocol_Timeout +#define Conn_Protocol_ObjTooBig Protocol_ObjTooBig +#define Conn_Protocol_BadCommandRecieved Protocol_BadCommandRecieved +#define Conn_Protocol_UnknownCommandRecieved Protocol_UnknownCommandRecieved +#define Conn_Protocol_TriedToExecuteReplyCommand Protocol_TriedToExecuteReplyCommand +#define Conn_Protocol_UnexpectedReply Protocol_UnexpectedReply +#define Conn_Protocol_HandshakeFailed Protocol_HandshakeFailed +#define Conn_Protocol_StreamWhenObjExpected Protocol_StreamWhenObjExpected +#define Conn_Protocol_ObjWhenStreamExpected Protocol_ObjWhenStreamExpected +#define Conn_Protocol_TimeOutWhenSendingStream Protocol_TimeOutWhenSendingStream + +#endif // SERVEREXCEPTION__H + diff --git a/lib/server/ServerException.txt b/lib/server/ServerException.txt new file mode 100644 index 00000000..ed591b73 --- /dev/null +++ b/lib/server/ServerException.txt @@ -0,0 +1,39 @@ +EXCEPTION Server 3 + +# for historic reasons, some codes are not used + +Internal 0 +FailedToLoadConfiguration 1 +DaemoniseFailed 2 +AlreadyDaemonConstructed 3 +BadSocketHandle 4 +DupError 5 +SocketAlreadyOpen 8 +SocketOpenError 10 +SocketPollError 11 +SocketCloseError 13 +SocketNameUNIXPathTooLong 14 +SocketBindError 16 Check the ListenAddresses directive in your config file -- must refer to local IP addresses only +SocketAcceptError 17 +ServerStreamBadListenAddrs 18 +ServerForkError 19 +ServerWaitOnChildError 20 +TooManySocketsInMultiListen 21 There is a limit on how many addresses you can listen on simulatiously. +ServerStreamTooManyListenAddresses 22 +TLSContextNotInitialised 23 +TLSAllocationFailed 24 +TLSLoadCertificatesFailed 25 +TLSLoadPrivateKeyFailed 26 +TLSLoadTrustedCAsFailed 27 +TLSSetCiphersFailed 28 +SSLLibraryInitialisationError 29 +TLSNoSSLObject 31 +TLSAlreadyHandshaked 35 +SocketSetNonBlockingFailed 40 +Protocol_BadUsage 43 +Protocol_UnsuitableStreamTypeForSending 51 +CantWriteToProtocolUncertainStream 53 +ProtocolUncertainStreamBadBlockHeader 54 +SocketPairFailed 55 +CouldNotChangePIDFileOwner 56 +SSLRandomInitFailed 57 Read from /dev/*random device failed diff --git a/lib/server/ServerStream.h b/lib/server/ServerStream.h new file mode 100644 index 00000000..e49dbcbe --- /dev/null +++ b/lib/server/ServerStream.h @@ -0,0 +1,418 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ServerStream.h +// Purpose: Stream based server daemons +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SERVERSTREAM__H +#define SERVERSTREAM__H + +#include +#include + +#ifndef WIN32 + #include +#endif + +#include "Daemon.h" +#include "SocketListen.h" +#include "Utils.h" +#include "Configuration.h" +#include "WaitForEvent.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ServerStream +// Purpose: Stream based server daemon +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +template +class ServerStream : public Daemon +{ +public: + ServerStream() + { + } + ~ServerStream() + { + DeleteSockets(); + } +private: + ServerStream(const ServerStream &rToCopy) + { + } +public: + + virtual const char *DaemonName() const + { + return "generic-stream-server"; + } + + virtual void OnIdle() { } + + virtual void Run() + { + // Set process title as appropriate + SetProcessTitle(ForkToHandleRequests?"server":"idle"); + + // Handle exceptions and child task quitting gracefully. + bool childExit = false; + try + { + Run2(childExit); + } + catch(BoxException &e) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what() << "(" << e.GetType() << + "/" << e.GetSubType() << ")"); + _exit(1); + } + else throw; + } + catch(std::exception &e) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what()); + _exit(1); + } + else throw; + } + catch(...) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: " + "unknown exception"); + _exit(1); + } + else throw; + } + + // if it's a child fork, exit the process now + if(childExit) + { + // Child task, dump leaks to trace, which we make sure is on + #ifdef BOX_MEMORY_LEAK_TESTING + #ifndef BOX_RELEASE_BUILD + TRACE_TO_SYSLOG(true); + TRACE_TO_STDOUT(true); + #endif + memleakfinder_traceblocksinsection(); + #endif + + // If this is a child quitting, exit now to stop bad things happening + _exit(0); + } + } + +protected: + virtual void NotifyListenerIsReady() { } + +public: + virtual void Run2(bool &rChildExit) + { + try + { + // Wait object with a timeout of 1 second, which + // is a reasonable time to wait before cleaning up + // finished child processes, and allows the daemon + // to terminate reasonably quickly on request. + WaitForEvent connectionWait(1000); + + // BLOCK + { + // Get the address we need to bind to + // this-> in next line required to build under some gcc versions + const Configuration &config(this->GetConfiguration()); + const Configuration &server(config.GetSubConfiguration("Server")); + std::string addrs = server.GetKeyValue("ListenAddresses"); + + // split up the list of addresses + std::vector addrlist; + SplitString(addrs, ',', addrlist); + + for(unsigned int a = 0; a < addrlist.size(); ++a) + { + // split the address up into components + std::vector c; + SplitString(addrlist[a], ':', c); + + // listen! + SocketListen *psocket = new SocketListen; + try + { + if(c[0] == "inet") + { + // Check arguments + if(c.size() != 2 && c.size() != 3) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + // Which port? + int port = Port; + + if(c.size() == 3) + { + // Convert to number + port = ::atol(c[2].c_str()); + if(port <= 0 || port > ((64*1024)-1)) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + } + + // Listen + psocket->Listen(Socket::TypeINET, c[1].c_str(), port); + } + else if(c[0] == "unix") + { + #ifdef WIN32 + BOX_WARNING("Ignoring request to listen on a Unix socket on Windows: " << addrlist[a]); + delete psocket; + psocket = NULL; + #else + // Check arguments size + if(c.size() != 2) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + // unlink anything there + ::unlink(c[1].c_str()); + + psocket->Listen(Socket::TypeUNIX, c[1].c_str()); + #endif // WIN32 + } + else + { + delete psocket; + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + if (psocket != NULL) + { + // Add to list of sockets + mSockets.push_back(psocket); + } + } + catch(...) + { + delete psocket; + throw; + } + + if (psocket != NULL) + { + // Add to the list of things to wait on + connectionWait.Add(psocket); + } + } + } + + NotifyListenerIsReady(); + + while(!StopRun()) + { + // Wait for a connection, or timeout + SocketListen *psocket + = (SocketListen *)connectionWait.Wait(); + + if(psocket) + { + // Get the incoming connection + // (with zero wait time) + std::string logMessage; + std::auto_ptr connection(psocket->Accept(0, &logMessage)); + + // Was there one (there should be...) + if(connection.get()) + { + // Since this is a template parameter, the if() will be optimised out by the compiler + #ifndef WIN32 // no fork on Win32 + if(ForkToHandleRequests && !IsSingleProcess()) + { + pid_t pid = ::fork(); + switch(pid) + { + case -1: + // Error! + THROW_EXCEPTION(ServerException, ServerForkError) + break; + + case 0: + // Child process + rChildExit = true; + // Close listening sockets + DeleteSockets(); + + // Set up daemon + EnterChild(); + SetProcessTitle("transaction"); + + // Memory leak test the forked process + #ifdef BOX_MEMORY_LEAK_TESTING + memleakfinder_startsectionmonitor(); + #endif + + // The derived class does some server magic with the connection + HandleConnection(*connection); + // Since rChildExit == true, the forked process will call _exit() on return from this fn + return; + + default: + // parent daemon process + break; + } + + // Log it + BOX_NOTICE("Message from child process " << pid << ": " << logMessage); + } + else + { + #endif // !WIN32 + // Just handle in this process + SetProcessTitle("handling"); + HandleConnection(*connection); + SetProcessTitle("idle"); + #ifndef WIN32 + } + #endif // !WIN32 + } + } + + OnIdle(); + + #ifndef WIN32 + // Clean up child processes (if forking daemon) + if(ForkToHandleRequests && !IsSingleProcess()) + { + WaitForChildren(); + } + #endif // !WIN32 + } + } + catch(...) + { + DeleteSockets(); + throw; + } + + // Delete the sockets + DeleteSockets(); + } + + #ifndef WIN32 // no waitpid() on Windows + void WaitForChildren() + { + int p = 0; + do + { + int status = 0; + p = ::waitpid(0 /* any child in process group */, + &status, WNOHANG); + + if(p == -1 && errno != ECHILD && errno != EINTR) + { + THROW_EXCEPTION(ServerException, + ServerWaitOnChildError) + } + else if(p == 0) + { + // no children exited, will return from + // function + } + else if(WIFEXITED(status)) + { + BOX_INFO("child process " << p << " " + "terminated normally"); + } + else if(WIFSIGNALED(status)) + { + int sig = WTERMSIG(status); + BOX_ERROR("child process " << p << " " + "terminated abnormally with " + "signal " << sig); + } + else + { + BOX_WARNING("something unknown happened " + "to child process " << p << ": " + "status = " << status); + } + } + while(p > 0); + } + #endif + + virtual void HandleConnection(StreamType &rStream) + { + Connection(rStream); + } + + virtual void Connection(StreamType &rStream) = 0; + +protected: + // For checking code in derived classes -- use if you have an algorithm which + // depends on the forking model in case someone changes it later. + bool WillForkToHandleRequests() + { + #ifdef WIN32 + return false; + #else + return ForkToHandleRequests && !IsSingleProcess(); + #endif // WIN32 + } + +private: + // -------------------------------------------------------------------------- + // + // Function + // Name: ServerStream::DeleteSockets() + // Purpose: Delete sockets + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + void DeleteSockets() + { + for(unsigned int l = 0; l < mSockets.size(); ++l) + { + if(mSockets[l]) + { + mSockets[l]->Close(); + delete mSockets[l]; + } + mSockets[l] = 0; + } + mSockets.clear(); + } + +private: + std::vector *> mSockets; +}; + +#define SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + ConfigurationVerifyKey("ListenAddresses", 0, DEFAULT_ADDRESSES), \ + DAEMON_VERIFY_SERVER_KEYS + +#include "MemLeakFindOff.h" + +#endif // SERVERSTREAM__H + + + diff --git a/lib/server/ServerTLS.h b/lib/server/ServerTLS.h new file mode 100644 index 00000000..a74a671e --- /dev/null +++ b/lib/server/ServerTLS.h @@ -0,0 +1,80 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ServerTLS.h +// Purpose: Implementation of a server using TLS streams +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SERVERTLS__H +#define SERVERTLS__H + +#include "ServerStream.h" +#include "SocketStreamTLS.h" +#include "SSLLib.h" +#include "TLSContext.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ServerTLS +// Purpose: Implementation of a server using TLS streams +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +template +class ServerTLS : public ServerStream +{ +public: + ServerTLS() + { + // Safe to call this here, as the Daemon class makes sure there is only one instance every of a Daemon. + SSLLib::Initialise(); + } + + ~ServerTLS() + { + } +private: + ServerTLS(const ServerTLS &) + { + } +public: + + virtual void Run2(bool &rChildExit) + { + // First, set up the SSL context. + // Get parameters from the configuration + // this-> in next line required to build under some gcc versions + const Configuration &conf(this->GetConfiguration()); + const Configuration &serverconf(conf.GetSubConfiguration("Server")); + std::string certFile(serverconf.GetKeyValue("CertificateFile")); + std::string keyFile(serverconf.GetKeyValue("PrivateKeyFile")); + std::string caFile(serverconf.GetKeyValue("TrustedCAsFile")); + mContext.Initialise(true /* as server */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); + + // Then do normal stream server stuff + ServerStream::Run2(rChildExit); + } + + virtual void HandleConnection(SocketStreamTLS &rStream) + { + rStream.Handshake(mContext, true /* is server */); + // this-> in next line required to build under some gcc versions + this->Connection(rStream); + } + +private: + TLSContext mContext; +}; + +#define SERVERTLS_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists), \ + SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) + +#endif // SERVERTLS__H + diff --git a/lib/server/Socket.cpp b/lib/server/Socket.cpp new file mode 100644 index 00000000..4a83bdb0 --- /dev/null +++ b/lib/server/Socket.cpp @@ -0,0 +1,184 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Socket.cpp +// Purpose: Socket related stuff +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#ifndef WIN32 +#include +#include +#include +#include +#endif + +#include +#include + +#include "Socket.h" +#include "ServerException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::NameLookupToSockAddr(SocketAllAddr &, int, +// char *, int) +// Purpose: Sets up a sockaddr structure given a name and type +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void Socket::NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, + enum Type Type, const std::string& rName, int Port, + int &rSockAddrLenOut) +{ + int sockAddrLen = 0; + + switch(Type) + { + case TypeINET: + sockDomain = AF_INET; + { + // Lookup hostname + struct hostent *phost = ::gethostbyname(rName.c_str()); + if(phost != NULL) + { + if(phost->h_addr_list[0] != 0) + { + sockAddrLen = sizeof(addr.sa_inet); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + addr.sa_inet.sin_len = sizeof(addr.sa_inet); +#endif + addr.sa_inet.sin_family = PF_INET; + addr.sa_inet.sin_port = htons(Port); + addr.sa_inet.sin_addr = *((in_addr*)phost->h_addr_list[0]); + for(unsigned int l = 0; l < sizeof(addr.sa_inet.sin_zero); ++l) + { + addr.sa_inet.sin_zero[l] = 0; + } + } + else + { + THROW_EXCEPTION(ConnectionException, Conn_SocketNameLookupError); + } + } + else + { + THROW_EXCEPTION(ConnectionException, Conn_SocketNameLookupError); + } + } + break; + +#ifndef WIN32 + case TypeUNIX: + sockDomain = AF_UNIX; + { + // Check length of name is OK + unsigned int nameLen = rName.length(); + if(nameLen >= (sizeof(addr.sa_unix.sun_path) - 1)) + { + THROW_EXCEPTION(ServerException, SocketNameUNIXPathTooLong); + } + sockAddrLen = nameLen + (((char*)(&(addr.sa_unix.sun_path[0]))) - ((char*)(&addr.sa_unix))); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + addr.sa_unix.sun_len = sockAddrLen; +#endif + addr.sa_unix.sun_family = PF_UNIX; + ::strncpy(addr.sa_unix.sun_path, rName.c_str(), + sizeof(addr.sa_unix.sun_path) - 1); + addr.sa_unix.sun_path[sizeof(addr.sa_unix.sun_path)-1] = 0; + } + break; +#endif + + default: + THROW_EXCEPTION(CommonException, BadArguments) + break; + } + + // Return size of structure to caller + rSockAddrLenOut = sockAddrLen; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::LogIncomingConnection(const struct sockaddr *, socklen_t) +// Purpose: Writes a message logging the connection to syslog +// Created: 2003/08/01 +// +// -------------------------------------------------------------------------- +void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen) +{ + if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)} + + switch(addr->sa_family) + { + case AF_UNIX: + BOX_INFO("Incoming connection from local (UNIX socket)"); + break; + + case AF_INET: + { + sockaddr_in *a = (sockaddr_in*)addr; + BOX_INFO("Incoming connection from " << + inet_ntoa(a->sin_addr) << " port " << + ntohs(a->sin_port)); + } + break; + + default: + BOX_WARNING("Incoming connection of unknown type"); + break; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::IncomingConnectionLogMessage(const struct sockaddr *, socklen_t) +// Purpose: Returns a string for use in log messages +// Created: 2003/08/01 +// +// -------------------------------------------------------------------------- +std::string Socket::IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen) +{ + if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)} + + switch(addr->sa_family) + { + case AF_UNIX: + return std::string("Incoming connection from local (UNIX socket)"); + break; + + case AF_INET: + { + 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); + } + break; + + default: + return std::string("Incoming connection of unknown type"); + break; + } + + // Dummy. + return std::string(); +} + diff --git a/lib/server/Socket.h b/lib/server/Socket.h new file mode 100644 index 00000000..5034dbd8 --- /dev/null +++ b/lib/server/Socket.h @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Socket.h +// Purpose: Socket related stuff +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKET__H +#define SOCKET__H + +#ifdef WIN32 +#include "emu.h" +typedef int socklen_t; +#else +#include +#include +#include +#endif + +#include + +typedef union { + struct sockaddr sa_generic; + struct sockaddr_in sa_inet; +#ifndef WIN32 + struct sockaddr_un sa_unix; +#endif +} SocketAllAddr; + +// -------------------------------------------------------------------------- +// +// Namespace +// Name: Socket +// Purpose: Socket utilities +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +namespace Socket +{ + enum Type + { + TypeINET = 1, + TypeUNIX = 2 + }; + + void NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, + enum Type type, const std::string& rName, int Port, + int &rSockAddrLenOut); + void LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen); + std::string IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen); +}; + +#endif // SOCKET__H + diff --git a/lib/server/SocketListen.h b/lib/server/SocketListen.h new file mode 100644 index 00000000..586adf22 --- /dev/null +++ b/lib/server/SocketListen.h @@ -0,0 +1,312 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketListen.h +// Purpose: Stream based sockets for servers +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETLISTEN__H +#define SOCKETLISTEN__H + +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#ifdef HAVE_KQUEUE + #include + #include +#endif + +#ifndef WIN32 + #include +#endif + +#include +#include +#include + +#include "Socket.h" +#include "ServerException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: _NoSocketLocking +// Purpose: Default locking class for SocketListen +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class _NoSocketLocking +{ +public: + _NoSocketLocking(int sock) + { + } + + ~_NoSocketLocking() + { + } + + bool HaveLock() + { + return true; + } + +private: + _NoSocketLocking(const _NoSocketLocking &rToCopy) + { + } +}; + + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketListen +// Purpose: +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +template +class SocketListen +{ +public: + // Initialise + SocketListen() + : mSocketHandle(-1) + { + } + // Close socket nicely + ~SocketListen() + { + Close(); + } +private: + SocketListen(const SocketListen &rToCopy) + { + } +public: + + enum + { + MaxMultipleListenSockets = MaxMultiListenSockets + }; + + void Close() + { + if(mSocketHandle != -1) + { +#ifdef WIN32 + if(::closesocket(mSocketHandle) == -1) +#else + if(::close(mSocketHandle) == -1) +#endif + { + BOX_LOG_SYS_ERROR("Failed to close network " + "socket"); + THROW_EXCEPTION(ServerException, + SocketCloseError) + } + } + mSocketHandle = -1; + } + + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::Listen(int, char*, int) + // Purpose: Initialises, starts the socket listening. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + void Listen(Socket::Type Type, const char *Name, int Port = 0) + { + if(mSocketHandle != -1) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen); + } + + // Setup parameters based on type, looking up names if required + int sockDomain = 0; + SocketAllAddr addr; + int addrLen = 0; + Socket::NameLookupToSockAddr(addr, sockDomain, Type, Name, + Port, addrLen); + + // Create the socket + mSocketHandle = ::socket(sockDomain, SOCK_STREAM, + 0 /* let OS choose protocol */); + if(mSocketHandle == -1) + { + BOX_LOG_SYS_ERROR("Failed to create a network socket"); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + // Set an option to allow reuse (useful for -HUP situations!) +#ifdef WIN32 + if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, "", + 0) == -1) +#else + int option = true; + if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, + &option, sizeof(option)) == -1) +#endif + { + BOX_LOG_SYS_ERROR("Failed to set socket options"); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + // Bind it to the right port, and start listening + if(::bind(mSocketHandle, &addr.sa_generic, addrLen) == -1 + || ::listen(mSocketHandle, ListenBacklog) == -1) + { + // Dispose of the socket + ::close(mSocketHandle); + mSocketHandle = -1; + THROW_EXCEPTION(ServerException, SocketBindError) + } + } + + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::Accept(int) + // Purpose: Accepts a connection, returning a pointer to + // a class of the specified type. May return a + // null pointer if a signal happens, or there's + // a timeout. Timeout specified in + // milliseconds, defaults to infinite time. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + std::auto_ptr Accept(int Timeout = INFTIM, + std::string *pLogMsg = 0) + { + if(mSocketHandle == -1) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + // Do the accept, using the supplied locking type + int sock; + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + // BLOCK + { + SocketLockingType socklock(mSocketHandle); + + if(!socklock.HaveLock()) + { + // Didn't get the lock for some reason. + // Wait a while, then return nothing. + BOX_ERROR("Failed to get a lock on incoming " + "connection"); + ::sleep(1); + return std::auto_ptr(); + } + + // poll this socket + struct pollfd p; + p.fd = mSocketHandle; + p.events = POLLIN; + p.revents = 0; + switch(::poll(&p, 1, Timeout)) + { + case -1: + // signal? + if(errno == EINTR) + { + BOX_ERROR("Failed to accept " + "connection: interrupted by " + "signal"); + // return nothing + return std::auto_ptr(); + } + else + { + BOX_LOG_SYS_ERROR("Failed to poll " + "connection"); + THROW_EXCEPTION(ServerException, + SocketPollError) + } + break; + case 0: // timed out + return std::auto_ptr(); + break; + default: // got some thing... + // control flows on... + break; + } + + sock = ::accept(mSocketHandle, &addr, &addrlen); + } + + // Got socket (or error), unlock (implicit in destruction) + if(sock == -1) + { + BOX_LOG_SYS_ERROR("Failed to accept connection"); + THROW_EXCEPTION(ServerException, SocketAcceptError) + } + + // Log it + if(pLogMsg) + { + *pLogMsg = Socket::IncomingConnectionLogMessage(&addr, + addrlen); + } + else + { + // Do logging ourselves + Socket::LogIncomingConnection(&addr, addrlen); + } + + return std::auto_ptr(new SocketType(sock)); + } + + // Functions to allow adding to WaitForEvent class, for efficient waiting + // on multiple sockets. +#ifdef HAVE_KQUEUE + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::FillInKEevent + // Purpose: Fills in a kevent structure for this socket + // Created: 9/3/04 + // + // ------------------------------------------------------------------ + void FillInKEvent(struct kevent &rEvent, int Flags = 0) const + { + EV_SET(&rEvent, mSocketHandle, EVFILT_READ, 0, 0, 0, + (void*)this); + } +#else + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::FillInPoll + // Purpose: Fills in the data necessary for a poll + // operation + // Created: 9/3/04 + // + // ------------------------------------------------------------------ + void FillInPoll(int &fd, short &events, int Flags = 0) const + { + fd = mSocketHandle; + events = POLLIN; + } +#endif + +private: + int mSocketHandle; +}; + +#include "MemLeakFindOff.h" + +#endif // SOCKETLISTEN__H + diff --git a/lib/server/SocketStream.cpp b/lib/server/SocketStream.cpp new file mode 100644 index 00000000..95b4b4f4 --- /dev/null +++ b/lib/server/SocketStream.cpp @@ -0,0 +1,514 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStream.cpp +// Purpose: I/O stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#ifndef WIN32 + #include +#endif + +#ifdef HAVE_UCRED_H + #include +#endif + +#include "SocketStream.h" +#include "ServerException.h" +#include "CommonException.h" +#include "Socket.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream() +// Purpose: Constructor (create stream ready for Open() call) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream() + : mSocketHandle(INVALID_SOCKET_VALUE), + mReadClosed(false), + mWriteClosed(false), + mBytesRead(0), + mBytesWritten(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream(int) +// Purpose: Create stream from existing socket handle +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream(int socket) + : mSocketHandle(socket), + mReadClosed(false), + mWriteClosed(false), + mBytesRead(0), + mBytesWritten(0) +{ + if(socket < 0) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream(const SocketStream &) +// Purpose: Copy constructor (dup()s socket) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream(const SocketStream &rToCopy) + : mSocketHandle(::dup(rToCopy.mSocketHandle)), + mReadClosed(rToCopy.mReadClosed), + mWriteClosed(rToCopy.mWriteClosed), + mBytesRead(rToCopy.mBytesRead), + mBytesWritten(rToCopy.mBytesWritten) + +{ + if(rToCopy.mSocketHandle < 0) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, DupError); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::~SocketStream() +// Purpose: Destructor, closes stream if open +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::~SocketStream() +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Attach(int) +// Purpose: Attach a socket handle to this stream +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void SocketStream::Attach(int socket) +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + ResetCounters(); + + mSocketHandle = socket; + mReadClosed = false; + mWriteClosed = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Open(Socket::Type, char *, int) +// Purpose: Opens a connection to a listening socket (INET or UNIX) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port) +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + // Setup parameters based on type, looking up names if required + int sockDomain = 0; + SocketAllAddr addr; + int addrLen = 0; + Socket::NameLookupToSockAddr(addr, sockDomain, Type, rName, Port, addrLen); + + // Create the socket + mSocketHandle = ::socket(sockDomain, SOCK_STREAM, + 0 /* let OS choose protocol */); + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + BOX_LOG_SYS_ERROR("Failed to create a network socket"); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + // Connect it + if(::connect(mSocketHandle, &addr.sa_generic, addrLen) == -1) + { + // Dispose of the socket +#ifdef WIN32 + DWORD err = WSAGetLastError(); + ::closesocket(mSocketHandle); + BOX_LOG_WIN_ERROR_NUMBER("Failed to connect to socket " + "(type " << Type << ", name " << rName << + ", port " << Port << ")", err); +#else // !WIN32 + BOX_LOG_SYS_ERROR("Failed to connect to socket (type " << + Type << ", name " << rName << ", port " << Port << + ")"); + ::close(mSocketHandle); +#endif // WIN32 + + mSocketHandle = INVALID_SOCKET_VALUE; + THROW_EXCEPTION(ConnectionException, Conn_SocketConnectError) + } + + ResetCounters(); + + mReadClosed = false; + mWriteClosed = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Read(void *pBuffer, int NBytes) +// Purpose: Reads data from stream. Maybe returns less than asked for. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int SocketStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if(Timeout != IOStream::TimeOutInfinite) + { + struct pollfd p; + p.fd = mSocketHandle; + p.events = POLLIN; + p.revents = 0; + switch(::poll(&p, 1, (Timeout == IOStream::TimeOutInfinite)?INFTIM:Timeout)) + { + case -1: + // error + if(errno == EINTR) + { + // Signal. Just return 0 bytes + return 0; + } + else + { + // Bad! + BOX_LOG_SYS_ERROR("Failed to poll socket"); + THROW_EXCEPTION(ServerException, + SocketPollError) + } + break; + + case 0: + // no data + return 0; + break; + + default: + // good to go! + break; + } + } + +#ifdef WIN32 + int r = ::recv(mSocketHandle, (char*)pBuffer, NBytes, 0); +#else + int r = ::read(mSocketHandle, pBuffer, NBytes); +#endif + if(r == -1) + { + if(errno == EINTR) + { + // Nothing could be read + return 0; + } + else + { + // Other error + BOX_LOG_SYS_ERROR("Failed to read from socket"); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError); + } + } + + // Closed for reading? + if(r == 0) + { + mReadClosed = true; + } + + mBytesRead += r; + return r; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Write(void *pBuffer, int NBytes) +// Purpose: Writes data, blocking until it's all done. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Write(const void *pBuffer, int NBytes) +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Buffer in byte sized type. + ASSERT(sizeof(char) == 1); + const char *buffer = (char *)pBuffer; + + // Bytes left to send + int bytesLeft = NBytes; + + while(bytesLeft > 0) + { + // Try to send. +#ifdef WIN32 + int sent = ::send(mSocketHandle, buffer, bytesLeft, 0); +#else + int sent = ::write(mSocketHandle, buffer, bytesLeft); +#endif + if(sent == -1) + { + // Error. + mWriteClosed = true; // assume can't write again + BOX_LOG_SYS_ERROR("Failed to write to socket"); + THROW_EXCEPTION(ConnectionException, + Conn_SocketWriteError); + } + + // Knock off bytes sent + bytesLeft -= sent; + // Move buffer pointer + buffer += sent; + + mBytesWritten += sent; + + // Need to wait until it can send again? + if(bytesLeft > 0) + { + BOX_TRACE("Waiting to send data on socket " << + mSocketHandle << " (" << bytesLeft << + " of " << NBytes << " bytes left)"); + + // Wait for data to send. + struct pollfd p; + p.fd = mSocketHandle; + p.events = POLLOUT; + p.revents = 0; + + if(::poll(&p, 1, 16000 /* 16 seconds */) == -1) + { + // Don't exception if it's just a signal + if(errno != EINTR) + { + BOX_LOG_SYS_ERROR("Failed to poll " + "socket"); + THROW_EXCEPTION(ServerException, + SocketPollError) + } + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Close() +// Purpose: Closes connection to remote socket +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Close() +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } +#ifdef WIN32 + if(::closesocket(mSocketHandle) == -1) +#else + if(::close(mSocketHandle) == -1) +#endif + { + BOX_LOG_SYS_ERROR("Failed to close socket"); + // don't throw an exception here, assume that the socket was + // already closed or closing. + } + mSocketHandle = INVALID_SOCKET_VALUE; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Shutdown(bool, bool) +// Purpose: Shuts down a socket for further reading and/or writing +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Shutdown(bool Read, bool Write) +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Do anything? + if(!Read && !Write) return; + + int how = SHUT_RDWR; + if(Read && !Write) how = SHUT_RD; + if(!Read && Write) how = SHUT_WR; + + // Shut it down! + if(::shutdown(mSocketHandle, how) == -1) + { + BOX_LOG_SYS_ERROR("Failed to shutdown socket"); + THROW_EXCEPTION(ConnectionException, Conn_SocketShutdownError) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::StreamDataLeft() +// Purpose: Still capable of reading data? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool SocketStream::StreamDataLeft() +{ + return !mReadClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::StreamClosed() +// Purpose: Connection been closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool SocketStream::StreamClosed() +{ + return mWriteClosed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::GetSocketHandle() +// Purpose: Returns socket handle for this stream (derived classes only). +// Will exception if there's no valid socket. +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +tOSSocketHandle SocketStream::GetSocketHandle() +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + return mSocketHandle; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::GetPeerCredentials(uid_t &, gid_t &) +// Purpose: Returns true if the peer credientials are available. +// (will work on UNIX domain sockets only) +// Created: 19/2/04 +// +// -------------------------------------------------------------------------- +bool SocketStream::GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut) +{ +#ifdef HAVE_GETPEEREID + uid_t remoteEUID = 0xffff; + gid_t remoteEGID = 0xffff; + + if(::getpeereid(mSocketHandle, &remoteEUID, &remoteEGID) == 0) + { + rUidOut = remoteEUID; + rGidOut = remoteEGID; + return true; + } +#endif + +#if HAVE_DECL_SO_PEERCRED + struct ucred cred; + socklen_t credLen = sizeof(cred); + + if(::getsockopt(mSocketHandle, SOL_SOCKET, SO_PEERCRED, &cred, + &credLen) == 0) + { + rUidOut = cred.uid; + rGidOut = cred.gid; + return true; + } + + BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); +#endif + +#if defined HAVE_UCRED_H && HAVE_GETPEERUCRED + ucred_t *pucred = NULL; + if(::getpeerucred(mSocketHandle, &pucred) == 0) + { + rUidOut = ucred_geteuid(pucred); + rGidOut = ucred_getegid(pucred); + ucred_free(pucred); + if (rUidOut == -1 || rGidOut == -1) + { + BOX_ERROR("Failed to get peer credentials on " + "socket: insufficient information"); + return false; + } + return true; + } + + BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); +#endif + + // Not available + return false; +} + diff --git a/lib/server/SocketStream.h b/lib/server/SocketStream.h new file mode 100644 index 00000000..2b582f21 --- /dev/null +++ b/lib/server/SocketStream.h @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStream.h +// Purpose: I/O stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETSTREAM__H +#define SOCKETSTREAM__H + +#include "IOStream.h" +#include "Socket.h" + +#ifdef WIN32 + typedef SOCKET tOSSocketHandle; + #define INVALID_SOCKET_VALUE (tOSSocketHandle)(-1) +#else + typedef int tOSSocketHandle; + #define INVALID_SOCKET_VALUE -1 +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketStream +// Purpose: Stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class SocketStream : public IOStream +{ +public: + SocketStream(); + SocketStream(int socket); + SocketStream(const SocketStream &rToCopy); + ~SocketStream(); + + void Open(Socket::Type Type, const std::string& rName, int Port = 0); + void Attach(int socket); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + virtual void Shutdown(bool Read = true, bool Write = true); + + virtual bool GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut); + +protected: + tOSSocketHandle GetSocketHandle(); + void MarkAsReadClosed() {mReadClosed = true;} + void MarkAsWriteClosed() {mWriteClosed = true;} + +private: + tOSSocketHandle mSocketHandle; + bool mReadClosed; + bool mWriteClosed; + +protected: + off_t mBytesRead; + off_t mBytesWritten; + +public: + off_t GetBytesRead() const {return mBytesRead;} + off_t GetBytesWritten() const {return mBytesWritten;} + void ResetCounters() {mBytesRead = mBytesWritten = 0;} + bool IsOpened() { return mSocketHandle != INVALID_SOCKET_VALUE; } +}; + +#endif // SOCKETSTREAM__H + diff --git a/lib/server/SocketStreamTLS.cpp b/lib/server/SocketStreamTLS.cpp new file mode 100644 index 00000000..19fdadd4 --- /dev/null +++ b/lib/server/SocketStreamTLS.cpp @@ -0,0 +1,492 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStreamTLS.cpp +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#include "SocketStreamTLS.h" +#include "SSLLib.h" +#include "ServerException.h" +#include "TLSContext.h" +#include "BoxTime.h" + +#include "MemLeakFindOn.h" + +// Allow 5 minutes to handshake (in milliseconds) +#define TLS_HANDSHAKE_TIMEOUT (5*60*1000) + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::SocketStreamTLS() +// Purpose: Constructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::SocketStreamTLS() + : mpSSL(0), mpBIO(0) +{ + ResetCounters(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::SocketStreamTLS(int) +// Purpose: Constructor, taking previously connected socket +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::SocketStreamTLS(int socket) + : SocketStream(socket), + mpSSL(0), mpBIO(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::~SocketStreamTLS() +// Purpose: Destructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::~SocketStreamTLS() +{ + if(mpSSL) + { + // Attempt to close to avoid problems + Close(); + + // And if that didn't work... + if(mpSSL) + { + ::SSL_free(mpSSL); + mpSSL = 0; + mpBIO = 0; // implicity freed by the SSL_free call + } + } + + // If we only got to creating that BIO. + if(mpBIO) + { + ::BIO_free(mpBIO); + mpBIO = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Open(const TLSContext &, int, const char *, int) +// Purpose: Open connection, and perform TLS handshake +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Open(const TLSContext &rContext, Socket::Type Type, + const std::string& rName, int Port) +{ + SocketStream::Open(Type, rName, Port); + Handshake(rContext); + ResetCounters(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Handshake(const TLSContext &, bool) +// Purpose: Perform TLS handshake +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer) +{ + if(mpBIO || mpSSL) {THROW_EXCEPTION(ServerException, TLSAlreadyHandshaked)} + + // Create a BIO for this socket + mpBIO = ::BIO_new(::BIO_s_socket()); + if(mpBIO == 0) + { + SSLLib::LogError("creating socket bio"); + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + tOSSocketHandle socket = GetSocketHandle(); + BIO_set_fd(mpBIO, socket, BIO_NOCLOSE); + + // Then the SSL object + mpSSL = ::SSL_new(rContext.GetRawContext()); + if(mpSSL == 0) + { + SSLLib::LogError("creating SSL object"); + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + // Make the socket non-blocking so timeouts on Read work + +#ifdef WIN32 + u_long nonblocking = 1; + ioctlsocket(socket, FIONBIO, &nonblocking); +#else // !WIN32 + // This is more portable than using ioctl with FIONBIO + int statusFlags = 0; + if(::fcntl(socket, F_GETFL, &statusFlags) < 0 + || ::fcntl(socket, F_SETFL, statusFlags | O_NONBLOCK) == -1) + { + THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) + } +#endif + + // FIXME: This is less portable than the above. However, it MAY be needed + // for cygwin, which has/had bugs with fcntl + // + // int nonblocking = true; + // if(::ioctl(socket, FIONBIO, &nonblocking) == -1) + // { + // THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) + // } + + // Set the two to know about each other + ::SSL_set_bio(mpSSL, mpBIO, mpBIO); + + bool waitingForHandshake = true; + while(waitingForHandshake) + { + // Attempt to do the handshake + int r = 0; + if(IsServer) + { + r = ::SSL_accept(mpSSL); + } + else + { + r = ::SSL_connect(mpSSL); + } + + // check return code + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, handshake succeeded + waitingForHandshake = false; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the requried data + if(WaitWhenRetryRequired(se, TLS_HANDSHAKE_TIMEOUT) == false) + { + // timed out + THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeTimedOut) + } + break; + + default: // (and SSL_ERROR_ZERO_RETURN) + // Error occured + if(IsServer) + { + SSLLib::LogError("accepting connection"); + THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed) + } + else + { + SSLLib::LogError("connecting"); + THROW_EXCEPTION(ConnectionException, Conn_TLSHandshakeFailed) + } + } + } + + // And that's it +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitWhenRetryRequired(int, int) +// Purpose: Waits until the condition required by the TLS layer is met. +// Returns true if the condition is met, false if timed out. +// Created: 2003/08/15 +// +// -------------------------------------------------------------------------- +bool SocketStreamTLS::WaitWhenRetryRequired(int SSLErrorCode, int Timeout) +{ + struct pollfd p; + p.fd = GetSocketHandle(); + switch(SSLErrorCode) + { + case SSL_ERROR_WANT_READ: + p.events = POLLIN; + break; + + case SSL_ERROR_WANT_WRITE: + p.events = POLLOUT; + break; + + default: + // Not good! + THROW_EXCEPTION(ServerException, Internal) + break; + } + p.revents = 0; + + int64_t start, end; + start = BoxTimeToMilliSeconds(GetCurrentBoxTime()); + end = start + Timeout; + int result; + + do + { + int64_t now = BoxTimeToMilliSeconds(GetCurrentBoxTime()); + int poll_timeout = (int)(end - now); + if (poll_timeout < 0) poll_timeout = 0; + if (Timeout == IOStream::TimeOutInfinite) + { + poll_timeout = INFTIM; + } + result = ::poll(&p, 1, poll_timeout); + } + while(result == -1 && errno == EINTR); + + switch(result) + { + case -1: + // error - Bad! + THROW_EXCEPTION(ServerException, SocketPollError) + break; + + case 0: + // Condition not met, timed out + return false; + break; + + default: + // good to go! + return true; + break; + } + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Read(void *, int, int Timeout) +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +int SocketStreamTLS::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Make sure zero byte reads work as expected + if(NBytes == 0) + { + return 0; + } + + while(true) + { + int r = ::SSL_read(mpSSL, pBuffer, NBytes); + + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, return number of bytes read + mBytesRead += r; + return r; + break; + + case SSL_ERROR_ZERO_RETURN: + // Connection closed + MarkAsReadClosed(); + return 0; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the required data + // Will only get once around this loop, so don't need to calculate timeout values + if(WaitWhenRetryRequired(se, Timeout) == false) + { + // timed out + return 0; + } + break; + + default: + SSLLib::LogError("reading"); + THROW_EXCEPTION(ConnectionException, Conn_TLSReadFailed) + break; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Write(const void *, int) +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Write(const void *pBuffer, int NBytes) +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Make sure zero byte writes work as expected + if(NBytes == 0) + { + return; + } + + // from man SSL_write + // + // SSL_write() will only return with success, when the + // complete contents of buf of length num has been written. + // + // So no worries about partial writes and moving the buffer around + + while(true) + { + // try the write + int r = ::SSL_write(mpSSL, pBuffer, NBytes); + + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, data sent, return success + mBytesWritten += r; + return; + break; + + case SSL_ERROR_ZERO_RETURN: + // Connection closed + MarkAsWriteClosed(); + THROW_EXCEPTION(ConnectionException, Conn_TLSClosedWhenWriting) + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the requried data + { + #ifndef BOX_RELEASE_BUILD + bool conditionmet = + #endif + WaitWhenRetryRequired(se, IOStream::TimeOutInfinite); + ASSERT(conditionmet); + } + break; + + default: + SSLLib::LogError("writing"); + THROW_EXCEPTION(ConnectionException, Conn_TLSWriteFailed) + break; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Close() +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Close() +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Base class to close + SocketStream::Close(); + + // Free resources + ::SSL_free(mpSSL); + mpSSL = 0; + mpBIO = 0; // implicitly freed by SSL_free +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Shutdown() +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Shutdown(bool Read, bool Write) +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + if(::SSL_shutdown(mpSSL) < 0) + { + SSLLib::LogError("shutting down"); + THROW_EXCEPTION(ConnectionException, Conn_TLSShutdownFailed) + } + + // Don't ask the base class to shutdown -- BIO does this, apparently. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::GetPeerCommonName() +// Purpose: Returns the common name of the other end of the connection +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +std::string SocketStreamTLS::GetPeerCommonName() +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Get certificate + X509 *cert = ::SSL_get_peer_certificate(mpSSL); + if(cert == 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, Conn_TLSNoPeerCertificate) + } + + // Subject details + X509_NAME *subject = ::X509_get_subject_name(cert); + if(subject == 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, Conn_TLSPeerCertificateInvalid) + } + + // Common name + char commonName[256]; + if(::X509_NAME_get_text_by_NID(subject, NID_commonName, commonName, sizeof(commonName)) <= 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, Conn_TLSPeerCertificateInvalid) + } + // Terminate just in case + commonName[sizeof(commonName)-1] = '\0'; + + // Done. + return std::string(commonName); +} diff --git a/lib/server/SocketStreamTLS.h b/lib/server/SocketStreamTLS.h new file mode 100644 index 00000000..bb40ed10 --- /dev/null +++ b/lib/server/SocketStreamTLS.h @@ -0,0 +1,61 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStreamTLS.h +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETSTREAMTLS__H +#define SOCKETSTREAMTLS__H + +#include + +#include "SocketStream.h" + +class TLSContext; +#ifndef TLS_CLASS_IMPLEMENTATION_CPP + class SSL; + class BIO; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketStreamTLS +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +class SocketStreamTLS : public SocketStream +{ +public: + SocketStreamTLS(); + SocketStreamTLS(int socket); + ~SocketStreamTLS(); +private: + SocketStreamTLS(const SocketStreamTLS &rToCopy); +public: + + void Open(const TLSContext &rContext, Socket::Type Type, + const std::string& rName, int Port = 0); + void Handshake(const TLSContext &rContext, bool IsServer = false); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes); + virtual void Close(); + virtual void Shutdown(bool Read = true, bool Write = true); + + std::string GetPeerCommonName(); + +private: + bool WaitWhenRetryRequired(int SSLErrorCode, int Timeout); + +private: + SSL *mpSSL; + BIO *mpBIO; +}; + +#endif // SOCKETSTREAMTLS__H + diff --git a/lib/server/TLSContext.cpp b/lib/server/TLSContext.cpp new file mode 100644 index 00000000..ebc7384a --- /dev/null +++ b/lib/server/TLSContext.cpp @@ -0,0 +1,131 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: TLSContext.h +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include + +#include "TLSContext.h" +#include "ServerException.h" +#include "SSLLib.h" +#include "TLSContext.h" + +#include "MemLeakFindOn.h" + +#define MAX_VERIFICATION_DEPTH 2 +#define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::TLSContext() +// Purpose: Constructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +TLSContext::TLSContext() + : mpContext(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::~TLSContext() +// Purpose: Destructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +TLSContext::~TLSContext() +{ + if(mpContext != 0) + { + ::SSL_CTX_free(mpContext); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::Initialise(bool, const char *, const char *, const char *) +// Purpose: Initialise the context, loading in the specified certificate and private key files +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile) +{ + if(mpContext != 0) + { + ::SSL_CTX_free(mpContext); + } + + mpContext = ::SSL_CTX_new(AsServer?TLSv1_server_method():TLSv1_client_method()); + if(mpContext == NULL) + { + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + // Setup our identity + if(::SSL_CTX_use_certificate_chain_file(mpContext, CertificatesFile) != 1) + { + std::string msg = "loading certificates from "; + msg += CertificatesFile; + SSLLib::LogError(msg); + 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; + SSLLib::LogError(msg); + THROW_EXCEPTION(ServerException, TLSLoadPrivateKeyFailed) + } + + // Setup the identify of CAs we trust + if(::SSL_CTX_load_verify_locations(mpContext, TrustedCAsFile, NULL) != 1) + { + std::string msg = "loading CA cert from "; + msg += TrustedCAsFile; + SSLLib::LogError(msg); + THROW_EXCEPTION(ServerException, TLSLoadTrustedCAsFailed) + } + + // Setup options to require these certificates + ::SSL_CTX_set_verify(mpContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + // and a sensible maximum depth + ::SSL_CTX_set_verify_depth(mpContext, MAX_VERIFICATION_DEPTH); + + // Setup allowed ciphers + if(::SSL_CTX_set_cipher_list(mpContext, CIPHER_LIST) != 1) + { + SSLLib::LogError("setting cipher list to " CIPHER_LIST); + THROW_EXCEPTION(ServerException, TLSSetCiphersFailed) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::GetRawContext() +// Purpose: Get the raw context for OpenSSL API +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SSL_CTX *TLSContext::GetRawContext() const +{ + if(mpContext == 0) + { + THROW_EXCEPTION(ServerException, TLSContextNotInitialised) + } + return mpContext; +} + + + diff --git a/lib/server/TLSContext.h b/lib/server/TLSContext.h new file mode 100644 index 00000000..f52f5457 --- /dev/null +++ b/lib/server/TLSContext.h @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: TLSContext.h +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef TLSCONTEXT__H +#define TLSCONTEXT__H + +#ifndef TLS_CLASS_IMPLEMENTATION_CPP + class SSL_CTX; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: TLSContext +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +class TLSContext +{ +public: + TLSContext(); + ~TLSContext(); +private: + TLSContext(const TLSContext &); +public: + void Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile); + SSL_CTX *GetRawContext() const; + +private: + SSL_CTX *mpContext; +}; + +#endif // TLSCONTEXT__H + diff --git a/lib/server/WinNamedPipeListener.h b/lib/server/WinNamedPipeListener.h new file mode 100644 index 00000000..26e76e3d --- /dev/null +++ b/lib/server/WinNamedPipeListener.h @@ -0,0 +1,232 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeListener.h +// Purpose: Windows named pipe socket connection listener +// for server +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- + +#ifndef WINNAMEDPIPELISTENER__H +#define WINNAMEDPIPELISTENER__H + +#include +#include + +#include "ServerException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: WinNamedPipeListener +// Purpose: +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- +template +class WinNamedPipeListener +{ +private: + std::auto_ptr mapPipeName; + std::auto_ptr mapOverlapConnect; + HANDLE mPipeHandle; + +public: + // Initialise + WinNamedPipeListener() + : mPipeHandle(INVALID_HANDLE_VALUE) + { } + +private: + WinNamedPipeListener(const WinNamedPipeListener &rToCopy) + { /* forbidden */ } + + HANDLE CreatePipeHandle(const std::string& rName) + { + std::string socket = WinNamedPipeStream::sPipeNamePrefix + + rName; + + HANDLE handle = CreateNamedPipeA( + socket.c_str(), // pipe name + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, // enabled overlapped I/O + PIPE_TYPE_BYTE | // message type pipe + PIPE_READMODE_BYTE | // message-read mode + PIPE_WAIT, // blocking mode + ListenBacklog + 1, // max. instances + 4096, // output buffer size + 4096, // input buffer size + NMPWAIT_USE_DEFAULT_WAIT, // client time-out + NULL); // default security attribute + + if (handle == INVALID_HANDLE_VALUE) + { + BOX_LOG_WIN_ERROR("Failed to create named pipe " << + socket); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + return handle; + } + +public: + ~WinNamedPipeListener() + { + Close(); + } + + void Close() + { + if (mPipeHandle != INVALID_HANDLE_VALUE) + { + if (mapOverlapConnect.get()) + { + // outstanding connect in progress + if (CancelIo(mPipeHandle) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to cancel " + "outstanding connect request " + "on named pipe"); + } + + mapOverlapConnect.reset(); + } + + if (CloseHandle(mPipeHandle) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to close named pipe " + "handle"); + } + + mPipeHandle = INVALID_HANDLE_VALUE; + } + } + + // ------------------------------------------------------------------ + // + // Function + // Name: WinNamedPipeListener::Listen(std::string name) + // Purpose: Initialises socket name + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + void Listen(const std::string& rName) + { + Close(); + mapPipeName.reset(new std::string(rName)); + mPipeHandle = CreatePipeHandle(rName); + } + + // ------------------------------------------------------------------ + // + // Function + // Name: WinNamedPipeListener::Accept(int) + // Purpose: Accepts a connection, returning a pointer to + // a class of the specified type. May return a + // null pointer if a signal happens, or there's + // a timeout. Timeout specified in + // milliseconds, defaults to infinite time. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + std::auto_ptr Accept(int Timeout = INFTIM, + const char* pLogMsgOut = NULL) + { + if(!mapPipeName.get()) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + BOOL connected = FALSE; + std::auto_ptr mapStream; + + if (!mapOverlapConnect.get()) + { + // start a new connect operation + mapOverlapConnect.reset(new OverlappedIO()); + connected = ConnectNamedPipe(mPipeHandle, + &mapOverlapConnect->mOverlapped); + + if (connected == FALSE) + { + if (GetLastError() == ERROR_PIPE_CONNECTED) + { + connected = TRUE; + } + else if (GetLastError() != ERROR_IO_PENDING) + { + BOX_LOG_WIN_ERROR("Failed to connect " + "named pipe"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + } + } + + if (connected == FALSE) + { + // wait for connection + DWORD result = WaitForSingleObject( + mapOverlapConnect->mOverlapped.hEvent, + (Timeout == INFTIM) ? INFINITE : Timeout); + + if (result == WAIT_OBJECT_0) + { + DWORD dummy; + + if (!GetOverlappedResult(mPipeHandle, + &mapOverlapConnect->mOverlapped, + &dummy, TRUE)) + { + BOX_LOG_WIN_ERROR("Failed to get " + "overlapped connect result"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + + connected = TRUE; + } + else if (result == WAIT_TIMEOUT) + { + return mapStream; // contains NULL + } + else if (result == WAIT_ABANDONED) + { + BOX_ERROR("Wait for named pipe connection " + "was abandoned by the system"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + else if (result == WAIT_FAILED) + { + BOX_LOG_WIN_ERROR("Failed to wait for named " + "pipe connection"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + else + { + BOX_ERROR("Failed to wait for named pipe " + "connection: unknown return code " << + result); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + } + + ASSERT(connected == TRUE); + + mapStream.reset(new WinNamedPipeStream(mPipeHandle)); + mPipeHandle = CreatePipeHandle(*mapPipeName); + mapOverlapConnect.reset(); + + return mapStream; + } +}; + +#include "MemLeakFindOff.h" + +#endif // WINNAMEDPIPELISTENER__H diff --git a/lib/server/WinNamedPipeStream.cpp b/lib/server/WinNamedPipeStream.cpp new file mode 100644 index 00000000..1179516e --- /dev/null +++ b/lib/server/WinNamedPipeStream.cpp @@ -0,0 +1,620 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.cpp +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef WIN32 + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#include "WinNamedPipeStream.h" +#include "ServerException.h" +#include "CommonException.h" +#include "Socket.h" + +#include "MemLeakFindOn.h" + +std::string WinNamedPipeStream::sPipeNamePrefix = "\\\\.\\pipe\\"; + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::WinNamedPipeStream() +// Purpose: Constructor (create stream ready for Open() call) +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::WinNamedPipeStream() + : mSocketHandle(INVALID_HANDLE_VALUE), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(false), + mIsConnected(false) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::WinNamedPipeStream(HANDLE) +// Purpose: Constructor (with already-connected pipe handle) +// Created: 2008/10/01 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) + : mSocketHandle(hNamedPipe), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(true), + mIsConnected(true) +{ + // create the Readable event + mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to create the Readable event: " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(CommonException, Internal) + } + + // initialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + + // start the first overlapped read + if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + + if (err != ERROR_IO_PENDING) + { + BOX_ERROR("Failed to start overlapped read: " << + GetErrorMessage(err)); + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::~WinNamedPipeStream() +// Purpose: Destructor, closes stream if open +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::~WinNamedPipeStream() +{ + if (mSocketHandle != INVALID_HANDLE_VALUE) + { + try + { + Close(); + } + catch (std::exception &e) + { + BOX_ERROR("Caught exception while destroying " + "named pipe, ignored: " << e.what()); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Accept(const std::string& rName) +// Purpose: Creates a new named pipe with the given name, +// and wait for a connection on it +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +/* +void WinNamedPipeStream::Accept() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + if (mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen); + } + + bool connected = ConnectNamedPipe(mSocketHandle, (LPOVERLAPPED) NULL); + + if (!connected) + { + BOX_ERROR("Failed to ConnectNamedPipe(" << socket << "): " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mBytesInBuffer = 0; + mReadClosed = false; + mWriteClosed = false; + mIsServer = true; // must flush and disconnect before closing + mIsConnected = true; + + // create the Readable event + mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to create the Readable event: " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(CommonException, Internal) + } + + // initialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + + // start the first overlapped read + if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + + if (err != ERROR_IO_PENDING) + { + BOX_ERROR("Failed to start overlapped read: " << + GetErrorMessage(err)); + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Connect(const std::string& rName) +// Purpose: Opens a connection to a listening named pipe +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Connect(const std::string& rName) +{ + if (mSocketHandle != INVALID_HANDLE_VALUE || mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + std::string socket = sPipeNamePrefix + rName; + + mSocketHandle = CreateFileA( + socket.c_str(), // pipe name + GENERIC_READ | // read and write access + GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, + 0, // default attributes + NULL); // no template file + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + if (err == ERROR_PIPE_BUSY) + { + BOX_ERROR("Failed to connect to backup daemon: " + "it is busy with another connection"); + } + else + { + BOX_ERROR("Failed to connect to backup daemon: " << + GetErrorMessage(err)); + } + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mReadClosed = false; + mWriteClosed = false; + mIsServer = false; // just close the socket + mIsConnected = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Read(void *pBuffer, int NBytes) +// Purpose: Reads data from stream. Maybe returns less than asked for. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // TODO no support for timeouts yet + if (!mIsServer && Timeout != IOStream::TimeOutInfinite) + { + THROW_EXCEPTION(CommonException, AssertFailed) + } + + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (mReadClosed) + { + THROW_EXCEPTION(ConnectionException, SocketShutdownError) + } + + // ensure safe to cast NBytes to unsigned + if (NBytes < 0) + { + THROW_EXCEPTION(CommonException, AssertFailed) + } + + DWORD NumBytesRead; + + if (mIsServer) + { + // satisfy from buffer if possible, to avoid + // blocking on read. + bool needAnotherRead = false; + if (mBytesInBuffer == 0) + { + // overlapped I/O completed successfully? + // (wait if needed) + DWORD waitResult = WaitForSingleObject( + mReadOverlap.hEvent, Timeout); + + if (waitResult == WAIT_ABANDONED) + { + BOX_ERROR("Wait for command socket read " + "abandoned by system"); + THROW_EXCEPTION(ServerException, + BadSocketHandle); + } + else if (waitResult == WAIT_TIMEOUT) + { + // wait timed out, nothing to read + NumBytesRead = 0; + } + else if (waitResult != WAIT_OBJECT_0) + { + BOX_ERROR("Failed to wait for command " + "socket read: unknown result " << + waitResult); + } + // object is ready to read from + else if (GetOverlappedResult(mSocketHandle, + &mReadOverlap, &NumBytesRead, TRUE)) + { + needAnotherRead = true; + } + else + { + DWORD err = GetLastError(); + + if (err == ERROR_HANDLE_EOF) + { + mReadClosed = true; + } + else + { + if (err == ERROR_BROKEN_PIPE) + { + BOX_NOTICE("Control client " + "disconnected"); + } + else + { + BOX_ERROR("Failed to wait for " + "ReadFile to complete: " + << GetErrorMessage(err)); + } + + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + } + else + { + NumBytesRead = 0; + } + + size_t BytesToCopy = NumBytesRead + mBytesInBuffer; + size_t BytesRemaining = 0; + + if (BytesToCopy > (size_t)NBytes) + { + BytesRemaining = BytesToCopy - NBytes; + BytesToCopy = NBytes; + } + + memcpy(pBuffer, mReadBuffer, BytesToCopy); + memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); + + mBytesInBuffer = BytesRemaining; + NumBytesRead = BytesToCopy; + + if (needAnotherRead) + { + // reinitialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + } + + // start the next overlapped read + if (needAnotherRead && !ReadFile(mSocketHandle, + mReadBuffer + mBytesInBuffer, + sizeof(mReadBuffer) - mBytesInBuffer, + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + // Don't reset yet, there might be data + // in the buffer waiting to be read, + // will check below. + // ResetEvent(mReadableEvent); + } + else if (err == ERROR_HANDLE_EOF) + { + mReadClosed = true; + } + else if (err == ERROR_BROKEN_PIPE) + { + BOX_ERROR("Control client disconnected"); + mReadClosed = true; + } + else + { + BOX_ERROR("Failed to start overlapped read: " + << GetErrorMessage(err)); + Close(); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + } + else + { + if (!ReadFile( + mSocketHandle, // pipe handle + pBuffer, // buffer to receive reply + NBytes, // size of buffer + &NumBytesRead, // number of bytes read + NULL)) // not overlapped + { + DWORD err = GetLastError(); + + Close(); + + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". No exception wanted. + + if (err == ERROR_NO_DATA || + err == ERROR_PIPE_NOT_CONNECTED) + { + NumBytesRead = 0; + } + else + { + BOX_ERROR("Failed to read from control socket: " + << GetErrorMessage(err)); + THROW_EXCEPTION(ConnectionException, + Conn_SocketReadError) + } + } + + // Closed for reading at EOF? + if (NumBytesRead == 0) + { + mReadClosed = true; + } + } + + return NumBytesRead; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Write(void *pBuffer, int NBytes) +// Purpose: Writes data, blocking until it's all done. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Write(const void *pBuffer, int NBytes) +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Buffer in byte sized type. + ASSERT(sizeof(char) == 1); + const char *pByteBuffer = (char *)pBuffer; + + int NumBytesWrittenTotal = 0; + + while (NumBytesWrittenTotal < NBytes) + { + DWORD NumBytesWrittenThisTime = 0; + + bool Success = WriteFile( + mSocketHandle, // pipe handle + pByteBuffer + NumBytesWrittenTotal, // message + NBytes - NumBytesWrittenTotal, // message length + &NumBytesWrittenThisTime, // bytes written this time + NULL); // not overlapped + + if (!Success) + { + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". + + DWORD err = GetLastError(); + + if (err != ERROR_NO_DATA) + { + BOX_ERROR("Failed to write to control " + "socket: " << GetErrorMessage(err)); + } + + Close(); + + THROW_EXCEPTION(ConnectionException, + Conn_SocketWriteError) + } + + NumBytesWrittenTotal += NumBytesWrittenThisTime; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Close() +// Purpose: Closes connection to remote socket +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Close() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE && mIsConnected) + { + BOX_ERROR("Named pipe: inconsistent connected state"); + mIsConnected = false; + } + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (mIsServer) + { + if (!CancelIo(mSocketHandle)) + { + BOX_ERROR("Failed to cancel outstanding I/O: " << + GetErrorMessage(GetLastError())); + } + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to destroy Readable event: " + "invalid handle"); + } + else if (!CloseHandle(mReadableEvent)) + { + BOX_ERROR("Failed to destroy Readable event: " << + GetErrorMessage(GetLastError())); + } + + mReadableEvent = INVALID_HANDLE_VALUE; + + if (!FlushFileBuffers(mSocketHandle)) + { + BOX_ERROR("Failed to FlushFileBuffers: " << + GetErrorMessage(GetLastError())); + } + + if (!DisconnectNamedPipe(mSocketHandle)) + { + DWORD err = GetLastError(); + if (err != ERROR_PIPE_NOT_CONNECTED) + { + BOX_ERROR("Failed to DisconnectNamedPipe: " << + GetErrorMessage(err)); + } + } + + mIsServer = false; + } + + bool result = CloseHandle(mSocketHandle); + + mSocketHandle = INVALID_HANDLE_VALUE; + mIsConnected = false; + mReadClosed = true; + mWriteClosed = true; + + if (!result) + { + BOX_ERROR("Failed to CloseHandle: " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketCloseError) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamDataLeft() +// Purpose: Still capable of reading data? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamDataLeft() +{ + return !mReadClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamClosed() +// Purpose: Connection been closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamClosed() +{ + return mWriteClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::WriteAllBuffered() +// Purpose: Ensures that any data which has been buffered is written to the stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::WriteAllBuffered() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (!FlushFileBuffers(mSocketHandle)) + { + BOX_ERROR("Failed to FlushFileBuffers: " << + GetErrorMessage(GetLastError())); + } +} + + +#endif // WIN32 diff --git a/lib/server/WinNamedPipeStream.h b/lib/server/WinNamedPipeStream.h new file mode 100644 index 00000000..386ff7e3 --- /dev/null +++ b/lib/server/WinNamedPipeStream.h @@ -0,0 +1,67 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.h +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#if ! defined WINNAMEDPIPESTREAM__H && defined WIN32 +#define WINNAMEDPIPESTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: WinNamedPipeStream +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class WinNamedPipeStream : public IOStream +{ +public: + WinNamedPipeStream(); + WinNamedPipeStream(HANDLE hNamedPipe); + ~WinNamedPipeStream(); + + // server side - create the named pipe and listen for connections + // use WinNamedPipeListener to do this instead. + + // client side - connect to a waiting server + void Connect(const std::string& rName); + + // both sides + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes); + virtual void WriteAllBuffered(); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +protected: + void MarkAsReadClosed() {mReadClosed = true;} + void MarkAsWriteClosed() {mWriteClosed = true;} + +private: + WinNamedPipeStream(const WinNamedPipeStream &rToCopy) + { /* do not call */ } + + HANDLE mSocketHandle; + HANDLE mReadableEvent; + OVERLAPPED mReadOverlap; + uint8_t mReadBuffer[4096]; + size_t mBytesInBuffer; + bool mReadClosed; + bool mWriteClosed; + bool mIsServer; + bool mIsConnected; + +public: + static std::string sPipeNamePrefix; +}; + +#endif // WINNAMEDPIPESTREAM__H diff --git a/lib/server/makeprotocol.pl.in b/lib/server/makeprotocol.pl.in new file mode 100755 index 00000000..91ba55b0 --- /dev/null +++ b/lib/server/makeprotocol.pl.in @@ -0,0 +1,1093 @@ +#!@PERL@ +use strict; + +use lib "../../infrastructure"; +use BoxPlatform; + +# Make protocol C++ classes from a protocol description file + +# built in type info (values are is basic type, C++ typename) +# may get stuff added to it later if protocol uses extra types +my %translate_type_info = +( + 'int64' => [1, 'int64_t'], + 'int32' => [1, 'int32_t'], + 'int16' => [1, 'int16_t'], + 'int8' => [1, 'int8_t'], + 'bool' => [1, 'bool'], + 'string' => [0, 'std::string'] +); + +# built in instructions for logging various types +# may be added to +my %log_display_types = +( + 'int64' => ['0x%llx', 'VAR'], + 'int32' => ['0x%x', 'VAR'], + 'int16' => ['0x%x', 'VAR'], + 'int8' => ['0x%x', 'VAR'], + 'bool' => ['%s', '((VAR)?"true":"false")'], + 'string' => ['%s', 'VAR.c_str()'] +); + + + +my ($type, $file) = @ARGV; + +if($type ne 'Server' && $type ne 'Client') +{ + die "Neither Server or Client is specified on command line\n"; +} + +open IN, $file or die "Can't open input file $file\n"; + +print "Making $type protocol classes from $file...\n"; + +my @extra_header_files; + +my $implement_syslog = 0; +my $implement_filelog = 0; + +# read attributes +my %attr; +while() +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\A\s+//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + last if $l eq 'BEGIN_OBJECTS'; + + my ($k,$v) = split /\s+/,$l,2; + + if($k eq 'ClientType') + { + add_type($v) if $type eq 'Client'; + } + elsif($k eq 'ServerType') + { + add_type($v) if $type eq 'Server'; + } + elsif($k eq 'ImplementLog') + { + my ($log_if_type,$log_type) = split /\s+/,$v; + if($type eq $log_if_type) + { + if($log_type eq 'syslog') + { + $implement_syslog = 1; + } + elsif($log_type eq 'file') + { + $implement_filelog = 1; + } + else + { + printf("ERROR: Unknown log type for implementation: $log_type\n"); + exit(1); + } + } + } + elsif($k eq 'LogTypeToText') + { + my ($log_if_type,$type_name,$printf_format,$arg_template) = split /\s+/,$v; + if($type eq $log_if_type) + { + $log_display_types{$type_name} = [$printf_format,$arg_template] + } + } + else + { + $attr{$k} = $v; + } +} + +sub add_type +{ + my ($protocol_name, $cpp_name, $header_file) = split /\s+/,$_[0]; + + $translate_type_info{$protocol_name} = [0, $cpp_name]; + push @extra_header_files, $header_file; +} + +# check attributes +for(qw/Name ServerContextClass IdentString/) +{ + if(!exists $attr{$_}) + { + die "Attribute $_ is required, but not specified\n"; + } +} + +my $protocol_name = $attr{'Name'}; +my ($context_class, $context_class_inc) = split /\s+/,$attr{'ServerContextClass'}; +my $ident_string = $attr{'IdentString'}; + +my $current_cmd = ''; +my %cmd_contents; +my %cmd_attributes; +my %cmd_constants; +my %cmd_id; +my @cmd_list; + +# read in the command definitions +while() +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + # definitions or new command thing? + if($l =~ m/\A\s+/) + { + die "No command defined yet" if $current_cmd eq ''; + + # definition of component + $l =~ s/\A\s+//; + + my ($type,$name,$value) = split /\s+/,$l; + if($type eq 'CONSTANT') + { + push @{$cmd_constants{$current_cmd}},"$name = $value" + } + else + { + push @{$cmd_contents{$current_cmd}},$type,$name; + } + } + else + { + # new command + my ($name,$id,@attributes) = split /\s+/,$l; + $cmd_attributes{$name} = [@attributes]; + $cmd_id{$name} = int($id); + $current_cmd = $name; + push @cmd_list,$name; + } +} + +close IN; + + + +# open files +my $h_filename = 'autogen_'.$protocol_name.'Protocol'.$type.'.h'; +open CPP,'>autogen_'.$protocol_name.'Protocol'.$type.'.cpp'; +open H,">$h_filename"; + +print CPP <<__E; + +// Auto-generated file -- do not edit + +#include "Box.h" + +#include + +#include "$h_filename" +#include "IOStream.h" + +__E + +if($implement_syslog) +{ + print H < +#endif +EOF +} + + +my $guardname = uc 'AUTOGEN_'.$protocol_name.'Protocol'.$type.'_H'; +print H <<__E; + +// Auto-generated file -- do not edit + +#ifndef $guardname +#define $guardname + +#include "Protocol.h" +#include "ProtocolObject.h" +#include "ServerException.h" + +class IOStream; + +__E + +if($implement_filelog) +{ + print H qq~#include \n~; +} + +# extra headers +for(@extra_header_files) +{ + print H qq~#include "$_"\n~ +} +print H "\n"; + +if($type eq 'Server') +{ + # need utils file for the server + print H '#include "Utils.h"',"\n\n" +} + + +my $derive_objects_from = 'ProtocolObject'; +my $objects_extra_h = ''; +my $objects_extra_cpp = ''; +if($type eq 'Server') +{ + # define the context + print H "class $context_class;\n\n"; + print CPP "#include \"$context_class_inc\"\n\n"; + + # change class we derive the objects from + $derive_objects_from = $protocol_name.'ProtocolObject'; + + $objects_extra_h = <<__E; + virtual std::auto_ptr DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); +__E + $objects_extra_cpp = <<__E; +std::auto_ptr ${derive_objects_from}::DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext) +{ + THROW_EXCEPTION(ConnectionException, Conn_Protocol_TriedToExecuteReplyCommand) +} +__E +} + +print CPP qq~#include "MemLeakFindOn.h"\n~; + +if($type eq 'Client' && ($implement_syslog || $implement_filelog)) +{ + # change class we derive the objects from + $derive_objects_from = $protocol_name.'ProtocolObjectCl'; +} +if($implement_syslog) +{ + $objects_extra_h .= <<__E; + virtual void LogSysLog(const char *Action) const = 0; +__E +} +if($implement_filelog) +{ + $objects_extra_h .= <<__E; + virtual void LogFile(const char *Action, FILE *file) const = 0; +__E +} + +if($derive_objects_from ne 'ProtocolObject') +{ + # output a definition for the protocol object derived class + print H <<__E; +class ${protocol_name}ProtocolServer; + +class $derive_objects_from : public ProtocolObject +{ +public: + $derive_objects_from(); + virtual ~$derive_objects_from(); + $derive_objects_from(const $derive_objects_from &rToCopy); + +$objects_extra_h +}; +__E + + # and some cpp definitions + print CPP <<__E; +${derive_objects_from}::${derive_objects_from}() +{ +} +${derive_objects_from}::~${derive_objects_from}() +{ +} +${derive_objects_from}::${derive_objects_from}(const $derive_objects_from &rToCopy) +{ +} +$objects_extra_cpp +__E +} + + + +my $classname_base = $protocol_name.'Protocol'.$type; + +# output the classes +for my $cmd (@cmd_list) +{ + print H <<__E; +class $classname_base$cmd : public $derive_objects_from +{ +public: + $classname_base$cmd(); + $classname_base$cmd(const $classname_base$cmd &rToCopy); + ~$classname_base$cmd(); + int GetType() const; + enum + { + TypeID = $cmd_id{$cmd} + }; +__E + # constants + if(exists $cmd_constants{$cmd}) + { + print H "\tenum\n\t{\n\t\t"; + print H join(",\n\t\t",@{$cmd_constants{$cmd}}); + print H "\n\t};\n"; + } + # flags + if(obj_is_type($cmd,'EndsConversation')) + { + print H "\tbool IsConversationEnd() const;\n"; + } + if(obj_is_type($cmd,'IsError')) + { + print H "\tbool IsError(int &rTypeOut, int &rSubTypeOut) const;\n"; + print H "\tstd::string GetMessage() const;\n"; + } + if($type eq 'Server' && obj_is_type($cmd, 'Command')) + { + print H "\tstd::auto_ptr DoCommand(${protocol_name}ProtocolServer &rProtocol, $context_class &rContext); // IMPLEMENT THIS\n" + } + + # want to be able to read from streams? + my $read_from_streams = (obj_is_type($cmd,'Command') && $type eq 'Server') || (obj_is_type($cmd,'Reply') && $type eq 'Client'); + my $write_to_streams = (obj_is_type($cmd,'Command') && $type eq 'Client') || (obj_is_type($cmd,'Reply') && $type eq 'Server'); + + if($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 = ''; + if($write_to_streams) + { + # 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$classname_base$cmd(".$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"; + } + } + + if($implement_syslog) + { + print H "\tvirtual void LogSysLog(const char *Action) const;\n"; + } + if($implement_filelog) + { + print H "\tvirtual void LogFile(const char *Action, FILE *file) const;\n"; + } + + + # write member variables and setup for cpp file + my @def_constructor_list; + my @copy_constructor_list; + my @param_constructor_list; + + print H "private:\n"; + 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_member_type($ty)." m$nm;\n"; + + my ($basic,$typename) = translate_type($ty); + if($basic) + { + push @def_constructor_list, "m$nm(0)"; + } + push @copy_constructor_list, "m$nm(rToCopy.m$nm)"; + push @param_constructor_list, "m$nm($nm)"; + } + + # finish off + print H "};\n\n"; + + # now the cpp file... + my $def_con_vars = join(",\n\t ",@def_constructor_list); + $def_con_vars = "\n\t: ".$def_con_vars if $def_con_vars ne ''; + my $copy_con_vars = join(",\n\t ",@copy_constructor_list); + $copy_con_vars = "\n\t: ".$copy_con_vars if $copy_con_vars ne ''; + my $param_con_vars = join(",\n\t ",@param_constructor_list); + $param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne ''; + + my $class = "$classname_base$cmd".'::'; + print CPP <<__E; +$class$classname_base$cmd()$def_con_vars +{ +} +$class$classname_base$cmd(const $classname_base$cmd &rToCopy)$copy_con_vars +{ +} +$class~$classname_base$cmd() +{ +} +int ${class}GetType() const +{ + return $cmd_id{$cmd}; +} +__E + if($read_from_streams) + { + print CPP "void ${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"; + } + if($write_to_streams) + { + # implement extra constructor? + if($param_con_vars ne '') + { + print CPP "$class$classname_base$cmd($param_con_args)$param_con_vars\n{\n}\n"; + } + print CPP "void ${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 ${class}IsConversationEnd() const\n{\n\treturn true;\n}\n"; + } + if(obj_is_type($cmd,'IsError')) + { + # get parameters + my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError'); + print CPP <<__E; +bool ${class}IsError(int &rTypeOut, int &rSubTypeOut) const +{ + rTypeOut = m$mem_type; + rSubTypeOut = m$mem_subtype; + return true; +} +std::string ${class}GetMessage() const +{ + switch(m$mem_subtype) + { +__E + foreach my $const (@{$cmd_constants{$cmd}}) + { + next unless $const =~ /^Err_(.*)/; + my $shortname = $1; + $const =~ s/ = .*//; + print CPP <<__E; + case $const: return "$shortname"; +__E + } + print CPP <<__E; + default: + std::ostringstream out; + out << "Unknown subtype " << m$mem_subtype; + return out.str(); + } +} +__E + } + + if($implement_syslog) + { + my ($log) = make_log_strings_framework($cmd); + print CPP <<__E; +void ${class}LogSysLog(const char *Action) const +{ + BOX_TRACE($log); +} +__E + } + if($implement_filelog) + { + my ($log) = make_log_strings_framework($cmd); + print CPP <<__E; +void ${class}LogFile(const char *Action, FILE *File) const +{ + std::ostringstream oss; + oss << $log; + ::fprintf(File, "%s\\n", oss.str().c_str()); + ::fflush(File); +} +__E + } +} + +# finally, the protocol object itself +print H <<__E; +class $classname_base : public Protocol +{ +public: + $classname_base(IOStream &rStream); + virtual ~$classname_base(); + + std::auto_ptr<$derive_objects_from> Receive(); + void Send(const ${derive_objects_from} &rObject); +__E +if($implement_syslog) +{ + print H "\tvoid SetLogToSysLog(bool Log = false) {mLogToSysLog = Log;}\n"; +} +if($implement_filelog) +{ + print H "\tvoid SetLogToFile(FILE *File = 0) {mLogToFile = File;}\n"; +} +if($type eq 'Server') +{ + # need to put in the conversation function + print H "\tvoid DoServer($context_class &rContext);\n\n"; + # and the send vector thing + print H "\tvoid SendStreamAfterCommand(IOStream *pStream);\n\n"; +} +if($type eq 'Client') +{ + # 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?', IOStream &rStream':''; + my $queryextra = $has_stream?', rStream':''; + my $reply = obj_get_type_params($cmd,'Command'); + print H "\tstd::auto_ptr<$classname_base$reply> Query(const $classname_base$cmd &rQuery$argextra);\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 .= "\tinline std::auto_ptr<$classname_base$reply> Query$cmd($ar$argextra)\n\t{\n"; + $with_params .= "\t\t$classname_base$cmd send$nar;\n"; + $with_params .= "\t\treturn Query(send$queryextra);\n"; + $with_params .= "\t}\n"; + } + } + # quick hack to correct bad argument lists for commands with zero paramters but with streams + $with_params =~ s/\(, /(/g; + print H "\n",$with_params,"\n"; +} +print H <<__E; +private: + $classname_base(const $classname_base &rToCopy); +__E +if($type eq 'Server') +{ + # need to put the streams to send vector + print H "\tstd::vector mStreamsToSend;\n\tvoid DeleteStreamsToSend();\n"; +} + +if($implement_filelog || $implement_syslog) +{ + print H <<__E; + virtual void InformStreamReceiving(u_int32_t Size); + virtual void InformStreamSending(u_int32_t Size); +__E +} + +if($implement_syslog) +{ + print H "private:\n\tbool mLogToSysLog;\n"; +} +if($implement_filelog) +{ + print H "private:\n\tFILE *mLogToFile;\n"; +} +print H <<__E; + +protected: + virtual std::auto_ptr MakeProtocolObject(int ObjType); + virtual const char *GetIdentString(); +}; + +__E + +my $constructor_extra = ''; +$constructor_extra .= ', mLogToSysLog(false)' if $implement_syslog; +$constructor_extra .= ', mLogToFile(0)' if $implement_filelog; + +my $destructor_extra = ($type eq 'Server')?"\n\tDeleteStreamsToSend();":''; + +my $prefix = $classname_base.'::'; +print CPP <<__E; +$prefix$classname_base(IOStream &rStream) + : Protocol(rStream)$constructor_extra +{ +} +$prefix~$classname_base() +{$destructor_extra +} +const char *${prefix}GetIdentString() +{ + return "$ident_string"; +} +std::auto_ptr ${prefix}MakeProtocolObject(int ObjType) +{ + switch(ObjType) + { +__E + +# do objects within this +for my $cmd (@cmd_list) +{ + print CPP <<__E; + case $cmd_id{$cmd}: + return std::auto_ptr(new $classname_base$cmd); + break; +__E +} + +print CPP <<__E; + default: + THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnknownCommandRecieved) + } +} +__E +# write receive and send functions +print CPP <<__E; +std::auto_ptr<$derive_objects_from> ${prefix}Receive() +{ + std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(Protocol::Receive().release())); + +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + preply->LogSysLog("Receive"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + preply->LogFile("Receive", mLogToFile); + } +__E + } +print CPP <<__E; + + return preply; +} + +void ${prefix}Send(const ${derive_objects_from} &rObject) +{ +__E + if($implement_syslog) + { + print CPP <<__E; + if(mLogToSysLog) + { + rObject.LogSysLog("Send"); + } +__E + } + if($implement_filelog) + { + print CPP <<__E; + if(mLogToFile != 0) + { + rObject.LogFile("Send", mLogToFile); + } +__E + } + +print CPP <<__E; + Protocol::Send(rObject); +} + +__E +# write server function? +if($type eq 'Server') +{ + print CPP <<__E; +void ${prefix}DoServer($context_class &rContext) +{ + // Handshake with client + Handshake(); + + // Command processing loop + bool inProgress = true; + while(inProgress) + { + // Get an object from the conversation + std::auto_ptr<${derive_objects_from}> pobj(Receive()); + + // Run the command + std::auto_ptr<${derive_objects_from}> preply((${derive_objects_from}*)(pobj->DoCommand(*this, rContext).release())); + + // Send the reply + Send(*(preply.get())); + + // Send any streams + for(unsigned int s = 0; s < mStreamsToSend.size(); s++) + { + // Send the streams + SendStream(*mStreamsToSend[s]); + } + // Delete these streams + DeleteStreamsToSend(); + + // Does this end the conversation? + if(pobj->IsConversationEnd()) + { + inProgress = false; + } + } +} + +void ${prefix}SendStreamAfterCommand(IOStream *pStream) +{ + ASSERT(pStream != NULL); + mStreamsToSend.push_back(pStream); +} + +void ${prefix}DeleteStreamsToSend() +{ + for(std::vector::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i) + { + delete (*i); + } + mStreamsToSend.clear(); +} + +__E +} + +# write logging functions? +if($implement_filelog || $implement_syslog) +{ + my ($fR,$fS); + + if($implement_syslog) + { + $fR .= <<__E; + if(mLogToSysLog) + { + if(Size==Protocol::ProtocolStream_SizeUncertain) + { + BOX_TRACE("Receiving stream, size uncertain"); + } + else + { + BOX_TRACE("Receiving stream, size " << Size); + } + } +__E + + $fS .= <<__E; + if(mLogToSysLog) + { + if(Size==Protocol::ProtocolStream_SizeUncertain) + { + BOX_TRACE("Sending stream, size uncertain"); + } + else + { + BOX_TRACE("Sending stream, size " << Size); + } + } +__E + } + + if($implement_filelog) + { + $fR .= <<__E; + if(mLogToFile) + { + ::fprintf(mLogToFile, + (Size==Protocol::ProtocolStream_SizeUncertain) + ?"Receiving stream, size uncertain\\n" + :"Receiving stream, size %d\\n", Size); + ::fflush(mLogToFile); + } +__E + $fS .= <<__E; + if(mLogToFile) + { + ::fprintf(mLogToFile, + (Size==Protocol::ProtocolStream_SizeUncertain) + ?"Sending stream, size uncertain\\n" + :"Sending stream, size %d\\n", Size); + ::fflush(mLogToFile); + } +__E + } + + print CPP <<__E; + +void ${prefix}InformStreamReceiving(u_int32_t Size) +{ +$fR} + +void ${prefix}InformStreamSending(u_int32_t Size) +{ +$fS} + +__E +} + + +# write client Query functions? +if($type eq 'Client') +{ + for my $cmd (@cmd_list) + { + if(obj_is_type($cmd,'Command')) + { + my $reply = obj_get_type_params($cmd,'Command'); + my $reply_id = $cmd_id{$reply}; + my $has_stream = obj_is_type($cmd,'StreamWithCommand'); + my $argextra = $has_stream?', IOStream &rStream':''; + my $send_stream_extra = ''; + if($has_stream) + { + $send_stream_extra = <<__E; + + // Send stream after the command + SendStream(rStream); +__E + } + print CPP <<__E; +std::auto_ptr<$classname_base$reply> ${classname_base}::Query(const $classname_base$cmd &rQuery$argextra) +{ + // Send query + Send(rQuery); + $send_stream_extra + // Wait for the reply + std::auto_ptr<${derive_objects_from}> preply(Receive().release()); + + if(preply->GetType() == $reply_id) + { + // Correct response + return std::auto_ptr<$classname_base$reply>(($classname_base$reply*)preply.release()); + } + else + { + // Set protocol error + int type, subType; + if(preply->IsError(type, subType)) + { + SetError(type, subType); + BOX_WARNING("$cmd command failed: received error " << + ((${classname_base}Error&)*preply).GetMessage()); + } + else + { + SetError(Protocol::UnknownError, Protocol::UnknownError); + BOX_WARNING("$cmd command failed: received " + "unexpected response type " << + preply->GetType()); + } + + // Throw an exception + THROW_EXCEPTION(ConnectionException, Conn_Protocol_UnexpectedReply) + } +} +__E + } + } +} + + + +print H <<__E; +#endif // $guardname + +__E + +# close files +close H; +close CPP; + + +sub obj_is_type +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return 1 if $_ =~ m/\A$ty/; + } + + return 0; +} + +sub obj_get_type_params +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return $1 if $_ =~ m/\A$ty\((.+?)\)\Z/; + } + die "Can't find attribute $ty\n" +} + +# returns (is basic type, typename) +sub translate_type +{ + my $ty = $_[0]; + + if($ty =~ m/\Avector\<(.+?)\>\Z/) + { + my $v_type = $1; + my (undef,$v_ty) = translate_type($v_type); + return (0, 'std::vector<'.$v_ty.'>') + } + else + { + if(!exists $translate_type_info{$ty}) + { + die "Don't know about type name $ty\n"; + } + return @{$translate_type_info{$ty}} + } +} + +sub translate_type_to_arg_type +{ + my ($basic,$typename) = translate_type(@_); + return $basic?$typename:'const '.$typename.'&' +} + +sub translate_type_to_member_type +{ + my ($basic,$typename) = translate_type(@_); + return $typename +} + +sub make_log_strings +{ + my ($cmd) = @_; + + my @str; + my @arg; + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format eq "0x%llx" and $target_windows) + { + $format = "0x%I64x"; + $arg = "(uint64_t)$arg"; + } + + push @str,$format; + push @arg,$arg; + } + else + { + # is opaque + push @str,'OPAQUE'; + } + } + return ($cmd.'('.join(',',@str).')', join(',','',@arg)); +} + +sub make_log_strings_framework +{ + my ($cmd) = @_; + + my @args; + + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format eq '\\"%s\\"') + { + $arg = "\"\\\"\" << $arg << \"\\\"\""; + } + elsif ($format =~ m'x$') + { + # my $width = 0; + # $ty =~ /^int(\d+)$/ and $width = $1 / 4; + $arg = "($arg == 0 ? \"0x\" : \"\") " . + "<< std::hex " . + "<< std::showbase " . + # "<< std::setw($width) " . + # "<< std::setfill('0') " . + # "<< std::internal " . + "<< $arg " . + "<< std::dec"; + } + + push @args, $arg; + } + else + { + # is opaque + push @args, '"OPAQUE"'; + } + } + + my $log_cmd = "Action << \" $cmd(\" "; + foreach my $arg (@args) + { + $arg = "<< $arg "; + } + $log_cmd .= join('<< "," ',@args); + $log_cmd .= '<< ")"'; + return $log_cmd; +} + + diff --git a/lib/win32/MSG00001.bin b/lib/win32/MSG00001.bin new file mode 100755 index 00000000..b4377b85 Binary files /dev/null and b/lib/win32/MSG00001.bin differ diff --git a/lib/win32/emu.cpp b/lib/win32/emu.cpp new file mode 100644 index 00000000..db9974d2 --- /dev/null +++ b/lib/win32/emu.cpp @@ -0,0 +1,1843 @@ +// Box Backup Win32 native port by Nick Knight + +// Need at least 0x0500 to use GetFileSizeEx on Cygwin/MinGW +#define WINVER 0x0500 + +#include "emu.h" + +#ifdef WIN32 + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +// message resource definitions for syslog() +#include "messages.h" + +DWORD winerrno; +struct passwd gTempPasswd; + +bool EnableBackupRights() +{ + HANDLE hToken; + TOKEN_PRIVILEGES token_priv; + + //open current process to adjust privileges + if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, + &hToken)) + { + ::syslog(LOG_ERR, "Failed to open process token: %s", + GetErrorMessage(GetLastError()).c_str()); + return false; + } + + //let's build the token privilege struct - + //first, look up the LUID for the backup privilege + + if (!LookupPrivilegeValue( + NULL, //this system + SE_BACKUP_NAME, //the name of the privilege + &( token_priv.Privileges[0].Luid ))) //result + { + ::syslog(LOG_ERR, "Failed to lookup backup privilege: %s", + GetErrorMessage(GetLastError()).c_str()); + CloseHandle(hToken); + return false; + } + + token_priv.PrivilegeCount = 1; + token_priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // now set the privilege + // because we're going exit right after dumping the streams, there isn't + // any need to save current state + + if (!AdjustTokenPrivileges( + hToken, //our process token + false, //we're not disabling everything + &token_priv, //address of structure + sizeof(token_priv), //size of structure + NULL, NULL)) //don't save current state + { + //this function is a little tricky - if we were adjusting + //more than one privilege, it could return success but not + //adjust them all - in the general case, you need to trap this + ::syslog(LOG_ERR, "Failed to enable backup privilege: %s", + GetErrorMessage(GetLastError()).c_str()); + CloseHandle(hToken); + return false; + + } + + CloseHandle(hToken); + return true; +} + +// forward declaration +char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage); + +// -------------------------------------------------------------------------- +// +// Function +// Name: GetDefaultConfigFilePath(std::string name) +// Purpose: Calculates the default configuration file name, +// by using the directory location of the currently +// executing program, and appending the provided name. +// In case of fire, returns an empty string. +// Created: 26th May 2007 +// +// -------------------------------------------------------------------------- +std::string GetDefaultConfigFilePath(const std::string& rName) +{ + WCHAR exePathWide[MAX_PATH]; + GetModuleFileNameW(NULL, exePathWide, MAX_PATH-1); + + char* exePathUtf8 = ConvertFromWideString(exePathWide, CP_UTF8); + if (exePathUtf8 == NULL) + { + return ""; + } + + std::string configfile = exePathUtf8; + delete [] exePathUtf8; + + // make the default config file name, + // based on the program path + configfile = configfile.substr(0, + configfile.rfind('\\')); + configfile += "\\"; + configfile += rName; + + return configfile; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertToWideString +// Purpose: Converts a string from specified codepage to +// a wide string (WCHAR*). Returns a buffer which +// MUST be freed by the caller with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +WCHAR* ConvertToWideString(const char* pString, unsigned int codepage, + bool logErrors) +{ + int len = MultiByteToWideChar + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // number of bytes in string - auto detect + NULL, // wide-character buffer + 0 // size of buffer - work out + // how much space we need + ); + + if (len == 0) + { + winerrno = GetLastError(); + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "%s", GetErrorMessage(winerrno).c_str()); + } + errno = EINVAL; + return NULL; + } + + WCHAR* buffer = new WCHAR[len]; + + if (buffer == NULL) + { + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "out of memory"); + } + winerrno = ERROR_OUTOFMEMORY; + errno = ENOMEM; + return NULL; + } + + len = MultiByteToWideChar + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // number of bytes in string - auto detect + buffer, // wide-character buffer + len // size of buffer + ); + + if (len == 0) + { + winerrno = GetLastError(); + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "%s", GetErrorMessage(winerrno).c_str()); + } + errno = EACCES; + delete [] buffer; + return NULL; + } + + return buffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertUtf8ToWideString +// Purpose: Converts a string from UTF-8 to a wide string. +// Returns a buffer which MUST be freed by the caller +// with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +WCHAR* ConvertUtf8ToWideString(const char* pString) +{ + return ConvertToWideString(pString, CP_UTF8, true); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertFromWideString +// Purpose: Converts a wide string to a narrow string in the +// specified code page. Returns a buffer which MUST +// be freed by the caller with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage) +{ + int len = WideCharToMultiByte + ( + codepage, // destination code page + 0, // character-type options + pString, // string to map + -1, // 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: " + "error %d", GetLastError()); + errno = EINVAL; + return NULL; + } + + char* buffer = new char[len]; + + if (buffer == NULL) + { + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "out of memory"); + errno = ENOMEM; + return NULL; + } + + len = WideCharToMultiByte + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // 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: " + "error %i", GetLastError()); + errno = EACCES; + delete [] buffer; + return NULL; + } + + return buffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertEncoding(const std::string&, int, +// std::string&, int) +// Purpose: Converts a string from one code page to another. +// On success, replaces contents of rDest and returns +// true. In case of fire, logs the error and returns +// false. +// Created: 15th October 2006 +// +// -------------------------------------------------------------------------- +bool ConvertEncoding(const std::string& rSource, int sourceCodePage, + std::string& rDest, int destCodePage) +{ + WCHAR* pWide = ConvertToWideString(rSource.c_str(), sourceCodePage, + true); + if (pWide == NULL) + { + ::syslog(LOG_ERR, "Failed to convert string '%s' from " + "current code page %d to wide string: %s", + rSource.c_str(), sourceCodePage, + GetErrorMessage(GetLastError()).c_str()); + return false; + } + + char* pConsole = ConvertFromWideString(pWide, destCodePage); + delete [] pWide; + + if (!pConsole) + { + // Error should have been logged by ConvertFromWideString + return false; + } + + rDest = pConsole; + delete [] pConsole; + + return true; +} + +bool ConvertToUtf8(const std::string& rSource, std::string& rDest, + int sourceCodePage) +{ + return ConvertEncoding(rSource, sourceCodePage, rDest, CP_UTF8); +} + +bool ConvertFromUtf8(const std::string& rSource, std::string& rDest, + int destCodePage) +{ + return ConvertEncoding(rSource, CP_UTF8, rDest, destCodePage); +} + +bool ConvertConsoleToUtf8(const std::string& rSource, std::string& rDest) +{ + return ConvertToUtf8(rSource, rDest, GetConsoleCP()); +} + +bool ConvertUtf8ToConsole(const std::string& rSource, std::string& rDest) +{ + return ConvertFromUtf8(rSource, rDest, GetConsoleOutputCP()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertPathToAbsoluteUnicode +// Purpose: Converts relative paths to absolute (with unicode marker) +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +std::string ConvertPathToAbsoluteUnicode(const char *pFileName) +{ + std::string filename; + for (int i = 0; pFileName[i] != 0; i++) + { + if (pFileName[i] == '/') + { + filename += '\\'; + } + else + { + filename += pFileName[i]; + } + } + + std::string tmpStr("\\\\?\\"); + + // Is the path relative or absolute? + // Absolute paths on Windows are always a drive letter + // followed by ':' + + char wd[PATH_MAX]; + if (::getcwd(wd, PATH_MAX) == 0) + { + ::syslog(LOG_WARNING, + "Failed to open '%s': path too long", + pFileName); + errno = ENAMETOOLONG; + winerrno = ERROR_INVALID_NAME; + tmpStr = ""; + return tmpStr; + } + + if (filename.length() > 2 && filename[0] == '\\' && + filename[1] == '\\') + { + tmpStr += "UNC\\"; + filename.replace(0, 2, ""); + // \\?\UNC\\ + // see http://msdn2.microsoft.com/en-us/library/aa365247.aspx + } + else if (filename.length() >= 1 && filename[0] == '\\') + { + // root directory of current drive. + tmpStr = wd; + tmpStr.resize(2); // drive letter and colon + } + else if (filename.length() >= 2 && filename[1] != ':') + { + // Must be relative. We need to get the + // current directory to make it absolute. + tmpStr += wd; + if (tmpStr[tmpStr.length()] != '\\') + { + tmpStr += '\\'; + } + } + + tmpStr += filename; + + // We are using direct filename access, which does not support .., + // so we need to implement it ourselves. + + for (std::string::size_type i = 1; i < tmpStr.size() - 3; i++) + { + if (tmpStr.substr(i, 3) == "\\..") + { + std::string::size_type lastSlash = + tmpStr.rfind('\\', i - 1); + + if (lastSlash == std::string::npos) + { + // no previous directory, ignore it, + // CreateFile will fail with error 123 + } + else + { + tmpStr.replace(lastSlash, i + 3 - lastSlash, + ""); + } + + i = lastSlash; + } + } + + return tmpStr; +} + +std::string GetErrorMessage(DWORD errorCode) +{ + char* pMsgBuf = NULL; + + DWORD chars = FormatMessage + ( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char *)(&pMsgBuf), + 0, NULL + ); + + if (chars == 0 || pMsgBuf == NULL) + { + return std::string("failed to get error message"); + } + + // remove embedded newline + pMsgBuf[chars - 1] = 0; + pMsgBuf[chars - 2] = 0; + + std::ostringstream line; + line << pMsgBuf << " (" << errorCode << ")"; + LocalFree(pMsgBuf); + + return line.str(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: openfile +// Purpose: replacement for any open calls - handles unicode +// filenames - supplied in utf8 +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +HANDLE openfile(const char *pFileName, int flags, int mode) +{ + winerrno = ERROR_INVALID_FUNCTION; + + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return INVALID_HANDLE_VALUE; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + return INVALID_HANDLE_VALUE; + } + + // flags could be O_WRONLY | O_CREAT | O_RDONLY + DWORD createDisposition = OPEN_EXISTING; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE + | FILE_SHARE_DELETE; + DWORD accessRights = FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY + | FILE_READ_EA; + + if (flags & O_WRONLY) + { + accessRights = FILE_WRITE_DATA; + } + else if (flags & O_RDWR) + { + accessRights |= FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA | FILE_WRITE_EA; + } + + if (flags & O_CREAT) + { + createDisposition = OPEN_ALWAYS; + } + + if (flags & O_TRUNC) + { + createDisposition = CREATE_ALWAYS; + } + + if ((flags & O_CREAT) && (flags & O_EXCL)) + { + createDisposition = CREATE_NEW; + } + + if (flags & O_LOCK) + { + shareMode = 0; + } + + DWORD winFlags = FILE_FLAG_BACKUP_SEMANTICS; + if (flags & O_TEMPORARY) + { + winFlags |= FILE_FLAG_DELETE_ON_CLOSE; + } + + HANDLE hdir = CreateFileW(pBuffer, + accessRights, + shareMode, + NULL, + createDisposition, + winFlags, + NULL); + + delete [] pBuffer; + + if (hdir == INVALID_HANDLE_VALUE) + { + winerrno = GetLastError(); + switch(winerrno) + { + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; + + default: + errno = EINVAL; + } + + ::syslog(LOG_WARNING, "Failed to open file '%s': " + "%s", pFileName, + GetErrorMessage(GetLastError()).c_str()); + + return INVALID_HANDLE_VALUE; + } + + if (flags & O_APPEND) + { + if (SetFilePointer(hdir, 0, NULL, FILE_END) == + INVALID_SET_FILE_POINTER) + { + winerrno = GetLastError(); + errno = EINVAL; + CloseHandle(hdir); + return INVALID_HANDLE_VALUE; + } + } + + winerrno = NO_ERROR; + return hdir; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_fstat +// Purpose: replacement for fstat. Supply a windows handle. +// Returns a struct emu_stat to have room for 64-bit +// file identifier in st_ino (mingw allows only 16!) +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int emu_fstat(HANDLE hdir, struct emu_stat * st) +{ + if (hdir == INVALID_HANDLE_VALUE) + { + ::syslog(LOG_ERR, "Error: invalid file handle in emu_fstat()"); + errno = EBADF; + return -1; + } + + BY_HANDLE_FILE_INFORMATION fi; + if (!GetFileInformationByHandle(hdir, &fi)) + { + ::syslog(LOG_WARNING, "Failed to read file information: " + "%s", GetErrorMessage(GetLastError()).c_str()); + errno = EACCES; + return -1; + } + + if (INVALID_FILE_ATTRIBUTES == fi.dwFileAttributes) + { + ::syslog(LOG_WARNING, "Failed to get file attributes: " + "%s", GetErrorMessage(GetLastError()).c_str()); + errno = EACCES; + return -1; + } + + memset(st, 0, sizeof(*st)); + + // This is how we get our INODE (equivalent) information + ULARGE_INTEGER conv; + conv.HighPart = fi.nFileIndexHigh; + conv.LowPart = fi.nFileIndexLow; + st->st_ino = conv.QuadPart; + + // get the time information + st->st_ctime = ConvertFileTimeToTime_t(&fi.ftCreationTime); + st->st_atime = ConvertFileTimeToTime_t(&fi.ftLastAccessTime); + st->st_mtime = ConvertFileTimeToTime_t(&fi.ftLastWriteTime); + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + st->st_size = 0; + } + else + { + conv.HighPart = fi.nFileSizeHigh; + conv.LowPart = fi.nFileSizeLow; + st->st_size = (_off_t)conv.QuadPart; + } + + // at the mo + st->st_uid = 0; + st->st_gid = 0; + st->st_nlink = 1; + + // the mode of the file + // mode zero will make it impossible to restore on Unix + // (no access to anybody, including the owner). + // we'll fake a sensible mode: + // all objects get user read (0400) + // if it's a directory it gets user execute (0100) + // if it's not read-only it gets user write (0200) + st->st_mode = S_IREAD; + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + st->st_mode |= S_IFDIR | S_IEXEC; + } + else + { + st->st_mode |= S_IFREG; + } + + if (!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + { + st->st_mode |= S_IWRITE; + } + + // st_dev is normally zero, regardless of the drive letter, + // since backup locations can't normally span drives. However, + // a reparse point does allow all kinds of weird stuff to happen. + // We set st_dev to 1 for a reparse point, so that Box will detect + // a change of device number (from 0) and refuse to recurse down + // the reparse point (which could lead to havoc). + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + st->st_dev = 1; + } + else + { + st->st_dev = 0; + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: OpenFileByNameUtf8 +// Purpose: Converts filename to Unicode and returns +// a handle to it. In case of error, sets errno, +// logs the error and returns NULL. +// Created: 10th December 2004 +// +// -------------------------------------------------------------------------- +HANDLE OpenFileByNameUtf8(const char* pFileName, DWORD flags) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return NULL; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + return NULL; + } + + HANDLE handle = CreateFileW(pBuffer, + flags, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + // if our open fails we should always be able to + // open in this mode - to get the inode information + // at least one process must have the file open - + // in this case someone else does. + handle = CreateFileW(pBuffer, + READ_CONTROL, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + } + + delete [] pBuffer; + + if (handle == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + + if (err == ERROR_FILE_NOT_FOUND || + err == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else + { + ::syslog(LOG_WARNING, "Failed to open '%s': " + "%s", pFileName, + GetErrorMessage(err).c_str()); + errno = EACCES; + } + + return NULL; + } + + return handle; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_stat +// Purpose: replacement for the lstat and stat functions. +// Works with unicode filenames supplied in utf8. +// Returns a struct emu_stat to have room for 64-bit +// file identifier in st_ino (mingw allows only 16!) +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int emu_stat(const char * pName, struct emu_stat * st) +{ + HANDLE handle = OpenFileByNameUtf8(pName, + FILE_READ_ATTRIBUTES | FILE_READ_EA); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + int retVal = emu_fstat(handle, st); + if (retVal != 0) + { + // error logged, but without filename + ::syslog(LOG_WARNING, "Failed to get file information " + "for '%s'", pName); + } + + // close the handle + CloseHandle(handle); + + return retVal; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: statfs +// Purpose: returns the mount point of where a file is located - +// in this case the volume serial number +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int statfs(const char * pName, struct statfs * s) +{ + HANDLE handle = OpenFileByNameUtf8(pName, + FILE_READ_ATTRIBUTES | FILE_READ_EA); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + BY_HANDLE_FILE_INFORMATION fi; + if (!GetFileInformationByHandle(handle, &fi)) + { + ::syslog(LOG_WARNING, "Failed to get file information " + "for '%s': %s", pName, + GetErrorMessage(GetLastError()).c_str()); + CloseHandle(handle); + errno = EACCES; + return -1; + } + + // convert volume serial number to a string + _ui64toa(fi.dwVolumeSerialNumber, s->f_mntonname + 1, 16); + + // pseudo unix mount point + s->f_mntonname[0] = '\\'; + + CloseHandle(handle); // close the handle + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_utimes +// Purpose: replacement for the POSIX utimes() function, +// works with unicode filenames supplied in utf8 format, +// sets creation time instead of last access time. +// Created: 25th July 2006 +// +// -------------------------------------------------------------------------- +int emu_utimes(const char * pName, const struct timeval times[]) +{ + FILETIME creationTime; + if (!ConvertTime_tToFileTime(times[0].tv_sec, &creationTime)) + { + errno = EINVAL; + return -1; + } + + FILETIME modificationTime; + if (!ConvertTime_tToFileTime(times[1].tv_sec, &modificationTime)) + { + errno = EINVAL; + return -1; + } + + HANDLE handle = OpenFileByNameUtf8(pName, FILE_WRITE_ATTRIBUTES); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + if (!SetFileTime(handle, &creationTime, NULL, &modificationTime)) + { + ::syslog(LOG_ERR, "Failed to set times on '%s': %s", pName, + GetErrorMessage(GetLastError()).c_str()); + CloseHandle(handle); + return 1; + } + + CloseHandle(handle); + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_chmod +// Purpose: replacement for the POSIX chmod function, +// works with unicode filenames supplied in utf8 format +// Created: 26th July 2006 +// +// -------------------------------------------------------------------------- +int emu_chmod(const char * pName, mode_t mode) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + free(pBuffer); + return -1; + } + + DWORD attribs = GetFileAttributesW(pBuffer); + if (attribs == INVALID_FILE_ATTRIBUTES) + { + ::syslog(LOG_ERR, "Failed to get file attributes of '%s': %s", + pName, GetErrorMessage(GetLastError()).c_str()); + errno = EACCES; + free(pBuffer); + return -1; + } + + if (mode & S_IWRITE) + { + attribs &= ~FILE_ATTRIBUTE_READONLY; + } + else + { + attribs |= FILE_ATTRIBUTE_READONLY; + } + + if (!SetFileAttributesW(pBuffer, attribs)) + { + ::syslog(LOG_ERR, "Failed to set file attributes of '%s': %s", + pName, GetErrorMessage(GetLastError()).c_str()); + errno = EACCES; + free(pBuffer); + return -1; + } + + delete [] pBuffer; + return 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: opendir +// Purpose: replacement for unix function, uses win32 findfirst routines +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +DIR *opendir(const char *name) +{ + if (!name || !name[0]) + { + errno = EINVAL; + return NULL; + } + + std::string dirName(name); + + //append a '\' win32 findfirst is sensitive to this + if ( dirName[dirName.size()] != '\\' || dirName[dirName.size()] != '/' ) + { + dirName += '\\'; + } + + // what is the search string? - everything + dirName += '*'; + + DIR *pDir = new DIR; + if (pDir == NULL) + { + errno = ENOMEM; + return NULL; + } + + pDir->name = ConvertUtf8ToWideString(dirName.c_str()); + // We are responsible for freeing dir->name with delete[] + + if (pDir->name == NULL) + { + delete pDir; + return NULL; + } + + pDir->fd = _wfindfirst((const wchar_t*)pDir->name, &(pDir->info)); + + if (pDir->fd == -1) + { + delete [] pDir->name; + delete pDir; + return NULL; + } + + pDir->result.d_name = 0; + return pDir; +} + +// this kinda makes it not thread friendly! +// but I don't think it needs to be. +char tempbuff[MAX_PATH]; + +// -------------------------------------------------------------------------- +// +// Function +// Name: readdir +// Purpose: as function above +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +struct dirent *readdir(DIR *dp) +{ + try + { + struct dirent *den = NULL; + + if (dp && dp->fd != -1) + { + if (!dp->result.d_name || + _wfindnext(dp->fd, &dp->info) != -1) + { + den = &dp->result; + std::wstring input(dp->info.name); + memset(tempbuff, 0, sizeof(tempbuff)); + WideCharToMultiByte(CP_UTF8, 0, dp->info.name, + -1, &tempbuff[0], sizeof (tempbuff), + NULL, NULL); + //den->d_name = (char *)dp->info.name; + den->d_name = &tempbuff[0]; + if (dp->info.attrib & FILE_ATTRIBUTE_DIRECTORY) + { + den->d_type = S_IFDIR; + } + else + { + den->d_type = S_IFREG; + } + } + } + else + { + errno = EBADF; + } + return den; + } + catch (...) + { + printf("Caught readdir"); + } + return NULL; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: closedir +// Purpose: as function above +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int closedir(DIR *dp) +{ + try + { + int finres = -1; + if (dp) + { + if(dp->fd != -1) + { + finres = _findclose(dp->fd); + } + + delete [] dp->name; + delete dp; + } + + if (finres == -1) // errors go to EBADF + { + errno = EBADF; + } + + return finres; + } + catch (...) + { + printf("Caught closedir"); + } + return -1; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: poll +// Purpose: a weak implimentation (just enough for box) +// of the unix poll for winsock2 +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int poll (struct pollfd *ufds, unsigned long nfds, int timeout) +{ + try + { + fd_set readfd; + fd_set writefd; + + FD_ZERO(&readfd); + FD_ZERO(&writefd); + + // struct pollfd *ufdsTmp = ufds; + + timeval timOut; + timeval *tmpptr; + + if (timeout == INFTIM) + tmpptr = NULL; + else + tmpptr = &timOut; + + timOut.tv_sec = timeout / 1000; + timOut.tv_usec = timeout * 1000; + + for (unsigned long i = 0; i < nfds; i++) + { + struct pollfd* ufd = &(ufds[i]); + + if (ufd->events & POLLIN) + { + FD_SET(ufd->fd, &readfd); + } + + if (ufd->events & POLLOUT) + { + FD_SET(ufd->fd, &writefd); + } + + if (ufd->events & ~(POLLIN | POLLOUT)) + { + printf("Unsupported poll bits %d", + ufd->events); + return -1; + } + } + + int nready = select(0, &readfd, &writefd, 0, tmpptr); + + if (nready == SOCKET_ERROR) + { + // int errval = WSAGetLastError(); + + struct pollfd* pufd = ufds; + for (unsigned long i = 0; i < nfds; i++) + { + pufd->revents = POLLERR; + pufd++; + } + return (-1); + } + else if (nready > 0) + { + for (unsigned long i = 0; i < nfds; i++) + { + struct pollfd *ufd = &(ufds[i]); + + if (FD_ISSET(ufd->fd, &readfd)) + { + ufd->revents |= POLLIN; + } + + if (FD_ISSET(ufd->fd, &writefd)) + { + ufd->revents |= POLLOUT; + } + } + } + + return nready; + } + catch (...) + { + printf("Caught poll"); + } + + return -1; +} + +// copied from MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/eventlog/base/adding_a_source_to_the_registry.asp + +BOOL AddEventSource +( + LPTSTR pszSrcName, // event source name + DWORD dwNum // number of categories +) +{ + // Work out the executable file name, to register ourselves + // as the event source + + WCHAR cmd[MAX_PATH]; + DWORD len = GetModuleFileNameW(NULL, cmd, MAX_PATH); + + if (len == 0) + { + ::syslog(LOG_ERR, "Failed to get the program file name: %s", + GetErrorMessage(GetLastError()).c_str()); + return FALSE; + } + + // Create the event source as a subkey of the log. + + std::string regkey("SYSTEM\\CurrentControlSet\\Services\\EventLog\\" + "Application\\"); + regkey += pszSrcName; + + HKEY hk; + DWORD dwDisp; + + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, regkey.c_str(), + 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_WRITE, NULL, &hk, &dwDisp)) + { + ::syslog(LOG_ERR, "Failed to create the registry key: %s", + GetErrorMessage(GetLastError()).c_str()); + return FALSE; + } + + // Set the name of the message file. + + if (RegSetValueExW(hk, // subkey handle + L"EventMessageFile", // value name + 0, // must be zero + REG_EXPAND_SZ, // value type + (LPBYTE)cmd, // pointer to value data + len*sizeof(WCHAR))) // data size + { + ::syslog(LOG_ERR, "Failed to set the event message file: %s", + GetErrorMessage(GetLastError()).c_str()); + RegCloseKey(hk); + return FALSE; + } + + // Set the supported event types. + + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + if (RegSetValueEx(hk, // subkey handle + "TypesSupported", // value name + 0, // must be zero + REG_DWORD, // value type + (LPBYTE) &dwData, // pointer to value data + sizeof(DWORD))) // length of value data + { + ::syslog(LOG_ERR, "Failed to set the supported types: %s", + GetErrorMessage(GetLastError()).c_str()); + RegCloseKey(hk); + return FALSE; + } + + // Set the category message file and number of categories. + + if (RegSetValueExW(hk, // subkey handle + L"CategoryMessageFile", // value name + 0, // must be zero + REG_EXPAND_SZ, // value type + (LPBYTE)cmd, // pointer to value data + len*sizeof(WCHAR))) // data size + { + ::syslog(LOG_ERR, "Failed to set the category message file: " + "%s", GetErrorMessage(GetLastError()).c_str()); + RegCloseKey(hk); + return FALSE; + } + + if (RegSetValueEx(hk, // subkey handle + "CategoryCount", // value name + 0, // must be zero + REG_DWORD, // value type + (LPBYTE) &dwNum, // pointer to value data + sizeof(DWORD))) // length of value data + { + ::syslog(LOG_ERR, "Failed to set the category count: %s", + GetErrorMessage(GetLastError()).c_str()); + RegCloseKey(hk); + return FALSE; + } + + RegCloseKey(hk); + return TRUE; +} + +static HANDLE gSyslogH = 0; +static bool sHaveWarnedEventLogFull = false; + +void openlog(const char * daemonName, int, int) +{ + std::string nameStr = "Box Backup ("; + nameStr += daemonName; + nameStr += ")"; + + // register a default event source, so that we can + // log errors with the process of adding or registering our own. + gSyslogH = RegisterEventSource( + NULL, // uses local computer + nameStr.c_str()); // source name + if (gSyslogH == NULL) + { + } + + char* name = strdup(nameStr.c_str()); + BOOL success = AddEventSource(name, 0); + free(name); + + if (!success) + { + ::syslog(LOG_ERR, "Failed to add our own event source"); + return; + } + + HANDLE newSyslogH = RegisterEventSource(NULL, nameStr.c_str()); + if (newSyslogH == NULL) + { + ::syslog(LOG_ERR, "Failed to register our own event source: " + "%s", GetErrorMessage(GetLastError()).c_str()); + return; + } + + DeregisterEventSource(gSyslogH); + gSyslogH = newSyslogH; +} + +void closelog(void) +{ + DeregisterEventSource(gSyslogH); +} + +void syslog(int loglevel, const char *frmt, ...) +{ + WORD errinfo; + char buffer[4096]; + std::string sixfour(frmt); + + switch (loglevel) + { + case LOG_INFO: + errinfo = EVENTLOG_INFORMATION_TYPE; + break; + case LOG_ERR: + errinfo = EVENTLOG_ERROR_TYPE; + break; + case LOG_WARNING: + errinfo = EVENTLOG_WARNING_TYPE; + break; + default: + errinfo = EVENTLOG_WARNING_TYPE; + break; + } + + // taken from MSDN + int sixfourpos; + while ( (sixfourpos = (int)sixfour.find("%ll")) != -1 ) + { + // maintain portability - change the 64 bit formater... + std::string temp = sixfour.substr(0,sixfourpos); + temp += "%I64"; + temp += sixfour.substr(sixfourpos+3, sixfour.length()); + sixfour = temp; + } + + // printf("parsed string is:%s\r\n", sixfour.c_str()); + + va_list args; + va_start(args, frmt); + + int len = vsnprintf(buffer, sizeof(buffer)-1, sixfour.c_str(), args); + assert(len >= 0); + if (len < 0) + { + printf("%s\r\n", buffer); + fflush(stdout); + return; + } + + assert((size_t)len < sizeof(buffer)); + buffer[sizeof(buffer)-1] = 0; + + va_end(args); + + if (gSyslogH == 0) + { + printf("%s\r\n", buffer); + fflush(stdout); + return; + } + + WCHAR* pWide = ConvertToWideString(buffer, CP_UTF8, false); + // must delete[] pWide + + DWORD result; + + if (pWide == NULL) + { + std::string buffer2 = buffer; + buffer2 += " (failed to convert string encoding)"; + LPCSTR strings[] = { buffer2.c_str(), NULL }; + + result = ReportEventA(gSyslogH, // event log handle + errinfo, // event type + 0, // category zero + MSG_ERR, // event identifier - + // we will call them all the same + NULL, // no user security identifier + 1, // one substitution string + 0, // no data + strings, // pointer to string array + NULL); // pointer to data + } + else + { + LPCWSTR strings[] = { pWide, NULL }; + result = ReportEventW(gSyslogH, // event log handle + errinfo, // event type + 0, // category zero + MSG_ERR, // event identifier - + // we will call them all the same + NULL, // no user security identifier + 1, // one substitution string + 0, // no data + strings, // pointer to string array + NULL); // pointer to data + delete [] pWide; + } + + if (result == 0) + { + DWORD err = GetLastError(); + if (err == ERROR_LOG_FILE_FULL) + { + if (!sHaveWarnedEventLogFull) + { + printf("Unable to send message to Event Log " + "(Event Log is full):\r\n"); + fflush(stdout); + sHaveWarnedEventLogFull = TRUE; + } + } + else + { + printf("Unable to send message to Event Log: %s:\r\n", + GetErrorMessage(err).c_str()); + fflush(stdout); + } + } + else + { + sHaveWarnedEventLogFull = false; + } +} + +int emu_chdir(const char* pDirName) +{ + /* + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pDirName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + */ + + WCHAR* pBuffer = ConvertUtf8ToWideString(pDirName); + if (!pBuffer) return -1; + + int result = SetCurrentDirectoryW(pBuffer); + delete [] pBuffer; + + if (result != 0) return 0; + + errno = EACCES; + fprintf(stderr, "Failed to change directory to '%s': %s\n", + pDirName, GetErrorMessage(GetLastError()).c_str()); + return -1; +} + +char* emu_getcwd(char* pBuffer, int BufSize) +{ + DWORD len = GetCurrentDirectoryW(0, NULL); + if (len == 0) + { + errno = EINVAL; + return NULL; + } + + if ((int)len > BufSize) + { + errno = ENAMETOOLONG; + return NULL; + } + + WCHAR* pWide = new WCHAR [len]; + if (!pWide) + { + errno = ENOMEM; + return NULL; + } + + DWORD result = GetCurrentDirectoryW(len, pWide); + if (result <= 0 || result >= len) + { + errno = EACCES; + delete [] pWide; + return NULL; + } + + char* pUtf8 = ConvertFromWideString(pWide, CP_UTF8); + delete [] pWide; + + if (!pUtf8) + { + return NULL; + } + + strncpy(pBuffer, pUtf8, BufSize - 1); + pBuffer[BufSize - 1] = 0; + delete [] pUtf8; + + return pBuffer; +} + +int emu_mkdir(const char* pPathName) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pPathName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + if (!pBuffer) + { + return -1; + } + + BOOL result = CreateDirectoryW(pBuffer, NULL); + delete [] pBuffer; + + if (!result) + { + errno = EACCES; + return -1; + } + + return 0; +} + +int emu_unlink(const char* pFileName) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + if (!pBuffer) + { + return -1; + } + + BOOL result = DeleteFileW(pBuffer); + DWORD err = GetLastError(); + delete [] pBuffer; + + if (!result) + { + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else if (err == ERROR_SHARING_VIOLATION) + { + errno = EBUSY; + } + else if (err == ERROR_ACCESS_DENIED) + { + errno = EACCES; + } + else + { + ::syslog(LOG_WARNING, "Failed to delete file " + "'%s': %s", pFileName, + GetErrorMessage(err).c_str()); + errno = ENOSYS; + } + return -1; + } + + return 0; +} + +int emu_rename(const char* pOldFileName, const char* pNewFileName) +{ + std::string OldPathWithUnicode = + ConvertPathToAbsoluteUnicode(pOldFileName); + + if (OldPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pOldBuffer = ConvertUtf8ToWideString(OldPathWithUnicode.c_str()); + if (!pOldBuffer) + { + return -1; + } + + std::string NewPathWithUnicode = + ConvertPathToAbsoluteUnicode(pNewFileName); + + if (NewPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + delete [] pOldBuffer; + return -1; + } + + WCHAR* pNewBuffer = ConvertUtf8ToWideString(NewPathWithUnicode.c_str()); + if (!pNewBuffer) + { + delete [] pOldBuffer; + return -1; + } + + BOOL result = MoveFileW(pOldBuffer, pNewBuffer); + DWORD err = GetLastError(); + delete [] pOldBuffer; + delete [] pNewBuffer; + + if (!result) + { + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else if (err == ERROR_SHARING_VIOLATION) + { + errno = EBUSY; + } + else if (err == ERROR_ACCESS_DENIED) + { + errno = EACCES; + } + else + { + ::syslog(LOG_WARNING, "Failed to rename file " + "'%s' to '%s': %s", pOldFileName, pNewFileName, + GetErrorMessage(err).c_str()); + errno = ENOSYS; + } + return -1; + } + + return 0; +} + +int console_read(char* pBuffer, size_t BufferSize) +{ + HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); + + if (hConsole == INVALID_HANDLE_VALUE) + { + ::fprintf(stderr, "Failed to get a handle on standard input: " + "%s", GetErrorMessage(GetLastError()).c_str()); + return -1; + } + + size_t WideSize = BufferSize / 5; + WCHAR* pWideBuffer = new WCHAR [WideSize + 1]; + + if (!pWideBuffer) + { + ::perror("Failed to allocate wide character buffer"); + return -1; + } + + DWORD numCharsRead = 0; + + if (!ReadConsoleW( + hConsole, + pWideBuffer, + WideSize, // will not be null terminated by ReadConsole + &numCharsRead, + NULL // reserved + )) + { + ::fprintf(stderr, "Failed to read from console: %s\n", + GetErrorMessage(GetLastError()).c_str()); + return -1; + } + + pWideBuffer[numCharsRead] = 0; + + char* pUtf8 = ConvertFromWideString(pWideBuffer, GetConsoleCP()); + delete [] pWideBuffer; + + strncpy(pBuffer, pUtf8, BufferSize); + delete [] pUtf8; + + return strlen(pBuffer); +} + +int readv (int filedes, const struct iovec *vector, size_t count) +{ + int bytes = 0; + + for (size_t i = 0; i < count; i++) + { + int result = read(filedes, vector[i].iov_base, + vector[i].iov_len); + if (result < 0) + { + return result; + } + bytes += result; + } + + return bytes; +} + +int writev(int filedes, const struct iovec *vector, size_t count) +{ + int bytes = 0; + + for (size_t i = 0; i < count; i++) + { + int result = write(filedes, vector[i].iov_base, + vector[i].iov_len); + if (result < 0) + { + return result; + } + bytes += result; + } + + return bytes; +} + +// need this for conversions +time_t ConvertFileTimeToTime_t(FILETIME *fileTime) +{ + SYSTEMTIME stUTC; + struct tm timeinfo; + + // Convert the last-write time to local time. + FileTimeToSystemTime(fileTime, &stUTC); + + memset(&timeinfo, 0, sizeof(timeinfo)); + timeinfo.tm_sec = stUTC.wSecond; + timeinfo.tm_min = stUTC.wMinute; + timeinfo.tm_hour = stUTC.wHour; + timeinfo.tm_mday = stUTC.wDay; + timeinfo.tm_wday = stUTC.wDayOfWeek; + timeinfo.tm_mon = stUTC.wMonth - 1; + // timeinfo.tm_yday = ...; + timeinfo.tm_year = stUTC.wYear - 1900; + + time_t retVal = mktime(&timeinfo) - _timezone; + return retVal; +} + +bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo) +{ + time_t adjusted = from + _timezone; + struct tm *time_breakdown = gmtime(&adjusted); + if (time_breakdown == NULL) + { + ::syslog(LOG_ERR, "Error: failed to convert time format: " + "%d is not a valid time\n", adjusted); + return false; + } + + SYSTEMTIME stUTC; + stUTC.wSecond = time_breakdown->tm_sec; + stUTC.wMinute = time_breakdown->tm_min; + stUTC.wHour = time_breakdown->tm_hour; + stUTC.wDay = time_breakdown->tm_mday; + stUTC.wDayOfWeek = time_breakdown->tm_wday; + stUTC.wMonth = time_breakdown->tm_mon + 1; + stUTC.wYear = time_breakdown->tm_year + 1900; + stUTC.wMilliseconds = 0; + + // Convert the last-write time to local time. + if (!SystemTimeToFileTime(&stUTC, pTo)) + { + syslog(LOG_ERR, "Failed to convert between time formats: %s", + GetErrorMessage(GetLastError()).c_str()); + return false; + } + + return true; +} + +#endif // WIN32 + diff --git a/lib/win32/emu.h b/lib/win32/emu.h new file mode 100644 index 00000000..f3389590 --- /dev/null +++ b/lib/win32/emu.h @@ -0,0 +1,424 @@ +// emulates unix syscalls to win32 functions + +#ifdef WIN32 + #define EMU_STRUCT_STAT struct emu_stat + #define EMU_STAT emu_stat + #define EMU_FSTAT emu_fstat + #define EMU_LSTAT emu_stat +#else + #define EMU_STRUCT_STAT struct stat + #define EMU_STAT ::stat + #define EMU_FSTAT ::fstat + #define EMU_LSTAT ::lstat +#endif + +#if ! defined EMU_INCLUDE && defined WIN32 +#define EMU_INCLUDE + +// basic types, may be required by other headers since we +// don't include sys/types.h + +#ifdef __MINGW32__ + #include +#else // MSVC + typedef unsigned __int64 u_int64_t; + typedef unsigned __int64 uint64_t; + typedef __int64 int64_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int32 u_int32_t; + typedef __int32 int32_t; + typedef unsigned __int16 uint16_t; + typedef __int16 int16_t; + typedef unsigned __int8 uint8_t; + typedef __int8 int8_t; +#endif + +// emulated types, present on MinGW but not MSVC or vice versa + +#ifdef __MINGW32__ + typedef uint32_t u_int32_t; +#else + typedef unsigned int mode_t; + typedef unsigned int pid_t; +#endif + +// set up to include the necessary parts of Windows headers + +#define WIN32_LEAN_AND_MEAN + +#ifndef __MSVCRT_VERSION__ +#define __MSVCRT_VERSION__ 0x0601 +#endif + +// Windows headers + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// emulated functions + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define ITIMER_REAL 0 + +#ifdef _MSC_VER +// Microsoft decided to deprecate the standard POSIX functions. Great! +#define open(file,flags,mode) _open(file,flags,mode) +#define close(fd) _close(fd) +#define dup(fd) _dup(fd) +#define read(fd,buf,count) _read(fd,buf,count) +#define write(fd,buf,count) _write(fd,buf,count) +#define lseek(fd,off,whence) _lseek(fd,off,whence) +#define fileno(struct_file) _fileno(struct_file) +#endif + +struct passwd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + time_t pw_change; + char *pw_class; + char *pw_gecos; + char *pw_dir; + char *pw_shell; + time_t pw_expire; +}; + +extern passwd gTempPasswd; +inline struct passwd * getpwnam(const char * name) +{ + //for the mo pretend to be root + gTempPasswd.pw_uid = 0; + gTempPasswd.pw_gid = 0; + + return &gTempPasswd; +} + +#define S_IRWXG 1 +#define S_IRWXO 2 +#define S_ISUID 4 +#define S_ISGID 8 +#define S_ISVTX 16 + +#ifndef __MINGW32__ + //not sure if these are correct + //S_IWRITE - writing permitted + //_S_IREAD - reading permitted + //_S_IREAD | _S_IWRITE - + #define S_IRUSR S_IWRITE + #define S_IWUSR S_IREAD + #define S_IRWXU (S_IREAD|S_IWRITE|S_IEXEC) + + #define S_ISREG(x) (S_IFREG & x) + #define S_ISDIR(x) (S_IFDIR & x) +#endif + +inline int chown(const char * Filename, u_int32_t uid, u_int32_t gid) +{ + //important - this needs implementing + //If a large restore is required then + //it needs to restore files AND permissions + //reference AdjustTokenPrivileges + //GetAccountSid + //InitializeSecurityDescriptor + //SetSecurityDescriptorOwner + //The next function looks like the guy to use... + //SetFileSecurity + + //indicate success + return 0; +} + +// Windows and Unix owners and groups are pretty fundamentally different. +// Ben prefers that we kludge here rather than litter the code with #ifdefs. +// Pretend to be root, and pretend that set...() operations succeed. +inline int setegid(int) +{ + return true; +} +inline int seteuid(int) +{ + return true; +} +inline int setgid(int) +{ + return true; +} +inline int setuid(int) +{ + return true; +} +inline int getgid(void) +{ + return 0; +} +inline int getuid(void) +{ + return 0; +} +inline int geteuid(void) +{ + return 0; +} + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +// MinGW provides a getopt implementation +#ifndef __MINGW32__ +#include "getopt.h" +#endif // !__MINGW32__ + +#define timespec timeval + +//win32 deals in usec not nsec - so need to ensure this follows through +#define tv_nsec tv_usec + +#ifndef __MINGW32__ + typedef int socklen_t; +#endif + +#define S_IRGRP S_IWRITE +#define S_IWGRP S_IREAD +#define S_IROTH S_IWRITE | S_IREAD +#define S_IWOTH S_IREAD | S_IREAD + +//again need to verify these +#define S_IFLNK 1 +#define S_IFSOCK 0 + +#define S_ISLNK(x) ( false ) + +#define vsnprintf _vsnprintf + +#ifndef __MINGW32__ +inline int strcasecmp(const char *s1, const char *s2) +{ + return _stricmp(s1,s2); +} +#endif + +#ifdef _DIRENT_H_ +#error You must not include the MinGW dirent.h! +#endif + +struct dirent +{ + char *d_name; + unsigned long d_type; +}; + +struct DIR +{ + 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 +}; + +DIR *opendir(const char *name); +struct dirent *readdir(DIR *dp); +int closedir(DIR *dp); + +// local constant to open file exclusively without shared access +#define O_LOCK 0x10000 + +extern DWORD winerrno; /* used to report errors from openfile() */ +HANDLE openfile(const char *filename, int flags, int mode); +inline int closefile(HANDLE handle) +{ + if (CloseHandle(handle) != TRUE) + { + errno = EINVAL; + return -1; + } + return 0; +} + +#define LOG_DEBUG LOG_INFO +#define LOG_INFO 6 +#define LOG_NOTICE LOG_INFO +#define LOG_WARNING 4 +#define LOG_ERR 3 +#define LOG_CRIT LOG_ERR +#define LOG_PID 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 + +void openlog (const char * daemonName, int, int); +void closelog(void); +void syslog (int loglevel, const char *fmt, ...); + +#define LOG_LOCAL0 0 +#define LOG_LOCAL1 0 +#define LOG_LOCAL2 0 +#define LOG_LOCAL3 0 +#define LOG_LOCAL4 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 +#define LOG_DAEMON 0 + +#ifndef __MINGW32__ +#define strtoll _strtoi64 +#endif + +inline unsigned int sleep(unsigned int secs) +{ + Sleep(secs*1000); + return(ERROR_SUCCESS); +} + +#define INFTIM -1 +#define POLLIN 0x1 +#define POLLERR 0x8 +#define POLLOUT 0x4 + +#define SHUT_RDWR SD_BOTH +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND + +struct pollfd +{ + SOCKET fd; + short int events; + short int revents; +}; + +inline int ioctl(SOCKET sock, int flag, int * something) +{ + //indicate success + return 0; +} + +extern "C" inline int getpid() +{ + return (int)GetCurrentProcessId(); +} + +inline int waitpid(pid_t pid, int *status, int) +{ + return 0; +} + +//this shouldn't be needed. +struct statfs +{ + TCHAR f_mntonname[MAX_PATH]; +}; + +struct emu_stat { + int st_dev; + uint64_t st_ino; + DWORD st_mode; + short st_nlink; + short st_uid; + short st_gid; + //_dev_t st_rdev; + uint64_t st_size; + time_t st_atime; + time_t st_mtime; + time_t st_ctime; +}; + +// need this for conversions +time_t ConvertFileTimeToTime_t(FILETIME *fileTime); +bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo); + +int emu_chdir (const char* pDirName); +int emu_mkdir (const char* pPathName); +int emu_unlink (const char* pFileName); +int emu_fstat (HANDLE file, struct emu_stat* st); +int emu_stat (const char* pName, struct emu_stat* st); +int emu_utimes (const char* pName, const struct timeval[]); +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); + +#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 rename(oldname, newname) emu_rename (oldname, newname) + +// Not safe to replace stat/fstat/lstat on mingw at least, as struct stat +// has a 16-bit st_ino and we need a 64-bit one. +// +// #define stat(filename, struct) emu_stat (filename, struct) +// #define lstat(filename, struct) emu_stat (filename, struct) +// #define fstat(handle, struct) emu_fstat (handle, struct) +// +// But lstat doesn't exist on Windows, so we have to provide something: + +#define lstat(filename, struct) stat(filename, struct) + +int statfs(const char * name, struct statfs * s); + +int poll(struct pollfd *ufds, unsigned long nfds, int timeout); + +struct iovec { + void *iov_base; /* Starting address */ + size_t iov_len; /* Number of bytes */ +}; + +int readv (int filedes, const struct iovec *vector, size_t count); +int writev(int filedes, const struct iovec *vector, size_t count); + +// The following functions are not emulations, but utilities for other +// parts of the code where Windows API is used or windows-specific stuff +// is needed, like codepage conversion. + +bool EnableBackupRights( void ); + +bool ConvertEncoding (const std::string& rSource, int sourceCodePage, + std::string& rDest, int destCodePage); +bool ConvertToUtf8 (const std::string& rSource, std::string& rDest, + int sourceCodePage); +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); + +// Utility function which returns a default config file name, +// based on the path of the current executable. +std::string GetDefaultConfigFilePath(const std::string& rName); + +// GetErrorMessage() returns a system error message, like strerror() +// but for Windows error codes. +std::string GetErrorMessage(DWORD errorCode); + +// console_read() is a replacement for _cgetws which requires a +// relatively recent C runtime lib +int console_read(char* pBuffer, size_t BufferSize); + +#ifdef _MSC_VER + /* disable certain compiler warnings to be able to actually see the show-stopper ones */ + #pragma warning(disable:4101) // unreferenced local variable + #pragma warning(disable:4244) // conversion, possible loss of data + #pragma warning(disable:4267) // conversion, possible loss of data + #pragma warning(disable:4311) // pointer truncation + #pragma warning(disable:4700) // uninitialized local variable used (hmmmmm...) + #pragma warning(disable:4805) // unsafe mix of type and type 'bool' in operation + #pragma warning(disable:4800) // forcing value to bool 'true' or 'false' (performance warning) + #pragma warning(disable:4996) // POSIX name for this item is deprecated +#endif // _MSC_VER + +#endif // !EMU_INCLUDE && WIN32 diff --git a/lib/win32/getopt.h b/lib/win32/getopt.h new file mode 100755 index 00000000..7c290343 --- /dev/null +++ b/lib/win32/getopt.h @@ -0,0 +1,98 @@ +/* $OpenBSD: getopt.h,v 1.1 2002/12/03 20:24:29 millert Exp $ */ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +// copied from: http://www.la.utexas.edu/lab/software/devtool/gnu/libtool/C_header_files.html + +/* __BEGIN_DECLS should be used at the beginning of your declarations, + so that C++ compilers don't mangle their names. Use __END_DECLS at + the end of C declarations. */ +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +/* + * GNU-like getopt_long() and 4.4BSD getsubopt()/optreset extensions + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +__BEGIN_DECLS +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DEFINED_ +#define _GETOPT_DEFINED_ +int getopt(int, char * const *, const char *); +int getsubopt(char **, char * const *, char **); + +extern char *optarg; /* getopt(3) external variables */ +extern int opterr; +extern int optind; +extern int optopt; +extern int optreset; +extern char *suboptarg; /* getsubopt(3) external variable */ +#endif +__END_DECLS + +#endif /* !_GETOPT_H_ */ diff --git a/lib/win32/getopt_long.cpp b/lib/win32/getopt_long.cpp new file mode 100755 index 00000000..5d910e1b --- /dev/null +++ b/lib/win32/getopt_long.cpp @@ -0,0 +1,550 @@ +/* $OpenBSD: getopt_long.c,v 1.20 2005/10/25 15:49:37 jmc Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ +// Adapted for Box Backup by Chris Wilson + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +// #include "Box.h" + +#include +#include +#include +#include +#include + +#include "getopt.h" + +#if defined _MSC_VER || defined __MINGW32__ +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ + +#ifdef REPLACE_GETOPT +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static void warnx(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + const char * oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + optchar == (int)'-' && *place != '\0' || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + /* XXX: disable test for :: if PC? (GNU doesn't) */ + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } else if (!(flags & FLAG_PERMUTE)) { + /* + * If permutation is disabled, we can accept an + * optional arg separated by whitespace so long + * as it does not start with a dash (-). + */ + if (optind + 1 < nargc && *nargv[optind + 1] != '-') + optarg = nargv[++optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +#ifdef REPLACE_GETOPT +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} +#endif /* REPLACE_GETOPT */ + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} + +#endif // defined _MSC_VER || defined __MINGW32__ diff --git a/lib/win32/messages.h b/lib/win32/messages.h new file mode 100755 index 00000000..6959591b --- /dev/null +++ b/lib/win32/messages.h @@ -0,0 +1,57 @@ + // Message source file, to be compiled to a resource file with + // Microsoft Message Compiler (MC), to an object file with a Resource + // Compiler, and linked into the application. + + // The main reason for this file is to work around Windows' stupid + // messages in the Event Log, which say: + + // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) + // cannot be found. The local computer may not have the necessary + // registry information or message DLL files to display messages from a + // remote computer. The following information is part of the event: + // Message definitions follow +// +// Values are 32 bit values layed out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: MSG_ERR +// +// MessageText: +// +// %1 +// +#define MSG_ERR ((DWORD)0x40000001L) + diff --git a/lib/win32/messages.mc b/lib/win32/messages.mc new file mode 100644 index 00000000..75f17b0f --- /dev/null +++ b/lib/win32/messages.mc @@ -0,0 +1,22 @@ +; // Message source file, to be compiled to a resource file with +; // Microsoft Message Compiler (MC), to an object file with a Resource +; // Compiler, and linked into the application. +; +; // The main reason for this file is to work around Windows' stupid +; // messages in the Event Log, which say: +; +; // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) +; // cannot be found. The local computer may not have the necessary +; // registry information or message DLL files to display messages from a +; // remote computer. The following information is part of the event: + +MessageIdTypedef = DWORD + +; // Message definitions follow + +MessageId = 0x1 +Severity = Informational +SymbolicName = MSG_ERR +Language = English +%1 +. diff --git a/lib/win32/messages.rc b/lib/win32/messages.rc new file mode 100755 index 00000000..116522b7 --- /dev/null +++ b/lib/win32/messages.rc @@ -0,0 +1,2 @@ +LANGUAGE 0x9,0x1 +1 11 MSG00001.bin diff --git a/modules.txt b/modules.txt new file mode 100644 index 00000000..1be10efa --- /dev/null +++ b/modules.txt @@ -0,0 +1,50 @@ + +# first entry is module name, next entries are dependencies or -l library includes + +# put !, after a module / library to exclude it from a particular platform +# put !+,... to include it only on those platforms + +# -l libaries must be in the order they should appear on the command line. +# Note that order is important on platforms which do not have shared libraries. + +# Generic support code and modules + +lib/raidfile +lib/crypto +lib/server +lib/compress +lib/intercept + +test/common +test/crypto lib/crypto +test/compress lib/compress +test/raidfile lib/raidfile lib/intercept +test/basicserver lib/server + +# IF_DISTRIBUTION(boxbackup) + +# Backup system + +lib/backupclient lib/server lib/crypto lib/compress +lib/backupstore lib/server lib/raidfile lib/backupclient + +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 +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 +test/backupstorepatch bin/bbstored bin/bbstoreaccounts lib/backupstore lib/raidfile +test/backupdiff lib/backupclient +test/bbackupd bin/bbackupd bin/bbstored bin/bbstoreaccounts bin/bbackupquery bin/bbackupctl lib/server lib/backupstore lib/backupclient lib/intercept + +# HTTP server system +lib/httpserver lib/server +test/httpserver lib/httpserver +bin/s3simulator lib/httpserver + +# END_IF_DISTRIBUTION + diff --git a/parcels.txt b/parcels.txt new file mode 100644 index 00000000..718fb995 --- /dev/null +++ b/parcels.txt @@ -0,0 +1,86 @@ + +# this file describes which binaries and scripts go to make +# up a parcel -- a group of files and scripts needed to perform +# a particular task + +backup-client + bin bbackupd + bin bbackupquery + bin bbackupctl + script bin/bbackupd/bbackupd-config + noinstall script COPYING.txt + noinstall script LICENSE-GPL.txt + noinstall script LICENSE-DUAL.txt + + html bbackupd + html bbackupquery + html bbackupctl + html bbackupd-config + html bbackupd.conf + +EXCEPT:mingw32,mingw32msvc + man bbackupd.8 + man bbackupquery.8 + man bbackupctl.8 + man bbackupd-config.8 + man bbackupd.conf.5 +END-EXCEPT + +ONLY:mingw32,mingw32msvc + script bin/bbackupd/win32/installer.iss + script bin/bbackupd/win32/bbackupd.conf + script bin/bbackupd/win32/NotifySysAdmin.vbs +END-ONLY + +ONLY:mingw32 + 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 +END-ONLY + +ONLY:SunOS + script contrib/solaris/bbackupd-manifest.xml lib/svc/manifest + script contrib/solaris/bbackupd-smf-method lib/svc/method +END-ONLY + +ONLY:Darwin + script contrib/mac_osx/org.boxbackup.bbackupd.plist /Library/LaunchDaemons +END-ONLY + +backup-server + bin bbstored + bin bbstoreaccounts + script bin/bbstored/bbstored-certs + script bin/bbstored/bbstored-config + script lib/raidfile/raidfile-config + noinstall script COPYING.txt + noinstall script LICENSE-GPL.txt + noinstall script LICENSE-DUAL.txt + + html bbstored + html bbstoreaccounts + html bbstored-certs + html bbstored-config + html raidfile-config + html bbstored.conf + html raidfile.conf + +EXCEPT:mingw32,mingw32msvc + man bbstored.8 + man bbstoreaccounts.8 + man bbstored-certs.8 + man bbstored-config.8 + man raidfile-config.8 + man bbstored.conf.5 + man raidfile.conf.5 +END-EXCEPT + +ONLY:SunOS + script contrib/solaris/bbstored-manifest.xml lib/svc/manifest + script contrib/solaris/bbstored-smf-method lib/svc/method +END-ONLY + +ONLY:Darwin + script contrib/mac_osx/org.boxbackup.bbstored.plist /Library/LaunchDaemons +END-ONLY diff --git a/runtest.pl.in b/runtest.pl.in new file mode 100755 index 00000000..42407378 --- /dev/null +++ b/runtest.pl.in @@ -0,0 +1,144 @@ +#!@PERL@ + +use strict; +use warnings; + +use lib 'infrastructure'; +use BoxPlatform; + +my ($test_name,$test_mode) = @ARGV; + +$test_mode = 'debug' if not defined $test_mode or $test_mode eq ''; + +if($test_name eq '' || ($test_mode ne 'debug' && $test_mode ne 'release')) +{ + print <<__E; +Run Test utility -- bad usage. + +runtest.pl (test|ALL) [release|debug] + +Mode defaults to debug. + +__E + exit(2); +} + +my @results; +my $exit_code = 0; + +if($test_name ne 'ALL') +{ + # run one or more specified test + if ($test_name =~ m/,/) + { + foreach my $test (split m/,/, $test_name) + { + runtest($test); + } + } + else + { + runtest($test_name); + } +} +else +{ + # run all tests + my @tests; + open MODULES,'modules.txt' or die "Can't open modules file"; + while() + { + # omit bits on some platforms? + next if m/\AEND-OMIT/; + if(m/\AOMIT:(.+)/) + { + if($1 eq $build_os or $1 eq $target_os) + { + while() + { + last if m/\AEND-OMIT/; + } + } + next; + } + push @tests,$1 if m~\Atest/(\w+)\s~; + } + close MODULES; + + runtest($_) for(@tests) +} + +# report results +print "--------\n",join("\n",@results),"\n"; + +if ($exit_code != 0) +{ + print <<__E; + +One or more tests have failed. Please check the following common causes: + +* Check that no instances of bbstored or bbackupd are already running + on this machine. +* Make sure there isn't a firewall blocking incoming or outgoing connections + on port 2201. +* Check that there is sufficient space in the filesystem that the tests + are being run from (at least 1 GB free). +* The backupdiff test fails if it takes too long, so it's sensitive to + the speed of the host and your connection to it. + +After checking all the above, if you still have problems please contact +us on the mailing list, boxbackup\@boxbackup.org. Thanks! +__E +} + +exit $exit_code; + +sub runtest +{ + my ($t) = @_; + + # attempt to make this test + my $flag = ($test_mode eq 'release')?(BoxPlatform::make_flag('RELEASE')):''; + my $make_res = system("cd test/$t ; $make_command $flag"); + if($make_res != 0) + { + push @results,"$t: make failed"; + $exit_code = 2; + return; + } + + my $logfile = "test-$t.log"; + + # run it + my $test_res = system("cd $test_mode/test/$t ; ./t 2>&1 " . + "| tee ../../../$logfile"); + + # open test results + if(open RESULTS, $logfile) + { + my $last; + while() + { + $last = $_ if m/\w/; + } + close RESULTS; + + chomp $last; + $last =~ s/\r//; + push @results, "$t: $last"; + + if ($last ne "PASSED") + { + $exit_code = 1; + } + } + else + { + push @results, + "$t: failed to open test log file: $logfile: $!"; + } + + # delete test results + # unlink $logfile; +} + diff --git a/test/backupdiff/difftestfiles.cpp b/test/backupdiff/difftestfiles.cpp new file mode 100644 index 00000000..33690f6b --- /dev/null +++ b/test/backupdiff/difftestfiles.cpp @@ -0,0 +1,295 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: createtestfiles.cpp +// Purpose: Create the test files for the backupdiff test +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "FileStream.h" +#include "PartialReadStream.h" +#include "Test.h" +#include "RollingChecksum.h" + +#include "MemLeakFindOn.h" + +#define ACT_END 0 +#define ACT_COPY 1 +#define ACT_NEW 2 +#define ACT_SKIP 3 +#define ACT_COPYEND 4 + +typedef struct +{ + int action, length, seed; +} gen_action; + +#define INITIAL_FILE_LENGTH (128*1024 + 342) + + +gen_action file1actions[] = { + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file2actions[] = { + {ACT_COPY, 16*1024, 0}, + // Do blocks on block boundaries, but swapped around a little + {ACT_SKIP, 4*1024, 0}, + {ACT_COPY, 8*1024, 0}, + {ACT_SKIP, -12*1024, 0}, + {ACT_COPY, 4*1024, 0}, + {ACT_SKIP, 8*1024, 0}, + // Get rest of file with some new data inserted + {ACT_COPY, 37*1024 + 12, 0}, + {ACT_NEW, 23*1024 + 129, 23990}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file3actions[] = { + {ACT_COPY, 12*1024 + 983, 0}, + {ACT_SKIP, 37*1024 + 12, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file4actions[] = { + {ACT_COPY, 20*1024 + 2385, 0}, + {ACT_NEW, 12, 2334}, + {ACT_COPY, 16*1024 + 385, 0}, + {ACT_SKIP, 9*1024 + 42, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +// insert 1 byte a block into the file, between two other blocks +gen_action file5actions[] = { + {ACT_COPY, 4*1024, 0}, + {ACT_NEW, 1, 2334}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file6actions[] = { + {ACT_NEW, 6*1024, 12353452}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +// but delete that one byte block, it's annoying +gen_action file7actions[] = { + {ACT_COPY, 10*1024, 0}, + {ACT_SKIP, 1, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_NEW, 7*1024, 1235352}, + {ACT_END, 0, 0} }; + +gen_action file8actions[] = { + {ACT_NEW, 54*1024 + 9, 125352}, + {ACT_END, 0, 0} }; + +gen_action file9actions[] = { + {ACT_END, 0, 0} }; + +gen_action *testfiles[] = {file1actions, file2actions, file3actions, file4actions, + file5actions, file6actions, file7actions, file8actions, file9actions, 0}; + + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +void make_random_data(void *buffer, int size, int seed) +{ + R250 rand(seed); + + int n = size / sizeof(int); + int *b = (int*)buffer; + for(int l = 0; l < n; ++l) + { + b[l] = rand.next(); + } +} + +void write_test_data(IOStream &rstream, int size, int seed) +{ + R250 rand(seed); + + while(size > 0) + { + // make a nice buffer of data + int buffer[2048/sizeof(int)]; + for(unsigned int l = 0; l < (sizeof(buffer) / sizeof(int)); ++l) + { + buffer[l] = rand.next(); + } + + // Write out... + unsigned int w = size; + if(w > sizeof(buffer)) w = sizeof(buffer); + rstream.Write(buffer, w); + + size -= w; + } +} + +void gen_varient(IOStream &out, char *sourcename, gen_action *pact) +{ + // Open source + FileStream source(sourcename); + + while(true) + { + switch(pact->action) + { + case ACT_END: + { + // all done + return; + } + case ACT_COPY: + { + PartialReadStream copy(source, pact->length); + copy.CopyStreamTo(out); + break; + } + case ACT_NEW: + { + write_test_data(out, pact->length, pact->seed); + break; + } + case ACT_SKIP: + { + source.Seek(pact->length, IOStream::SeekType_Relative); + break; + } + case ACT_COPYEND: + { + source.CopyStreamTo(out); + break; + } + } + + ++pact; + } +} + +void create_test_files() +{ + // First, the keys for the crypto + { + FileStream keys("testfiles/backup.keys", O_WRONLY | O_CREAT); + write_test_data(keys, 1024, 237); + } + + // Create the initial file -- needs various special properties... + // 1) Two blocks much be the different, but have the same weak checksum + // 2) A block must exist twice, but at an offset which isn't a multiple of the block size. + { + FileStream f0("testfiles/f0", O_WRONLY | O_CREAT); + // Write first bit. + write_test_data(f0, (16*1024), 20012); + // Now repeated checksum blocks + uint8_t blk[4096]; + make_random_data(blk, sizeof(blk), 12201); + // Three magic numbers which make the checksum work: Use this perl to find them: + /* + for($z = 1; $z < 4096; $z++) + { + for($n = 0; $n <= 255; $n++) + { + for($m = 0; $m <= 255; $m++) + { + if($n != $m && (($n*4096 + $m*(4096-$z)) % (64*1024) == ($n*(4096-$z) + $m*4096) % (64*1024))) + { + print "$z: $n $m\n"; + } + } + } + } + */ + blk[0] = 255; + blk[1024] = 191; + // Checksum to check + RollingChecksum c1(blk, sizeof(blk)); + // Write + f0.Write(blk, sizeof(blk)); + // Adjust block and write again + uint8_t blk2[4096]; + memcpy(blk2, blk, sizeof(blk2)); + blk2[1024] = 255; + blk2[0] = 191; + TEST_THAT(::memcmp(blk2, blk, sizeof(blk)) != 0); + RollingChecksum c2(blk2, sizeof(blk2)); + f0.Write(blk2, sizeof(blk2)); + // Check checksums + TEST_THAT(c1.GetChecksum() == c2.GetChecksum()); + + // Another 4k block + write_test_data(f0, (4*1024), 99209); + // Offset block + make_random_data(blk, 2048, 1234199); + f0.Write(blk, 2048); + f0.Write(blk, 2048); + f0.Write(blk, 2048); + make_random_data(blk, 2048, 1343278); + f0.Write(blk, 2048); + + write_test_data(f0, INITIAL_FILE_LENGTH - (16*1024) - ((4*1024)*2) - (4*1024) - (2048*4), 202); + + } + + // Then... create the varients + for(int l = 0; testfiles[l] != 0; ++l) + { + char n1[256]; + char n2[256]; + sprintf(n1, "testfiles/f%d", l + 1); + sprintf(n2, "testfiles/f%d", l); + + FileStream f1(n1, O_WRONLY | O_CREAT); + gen_varient(f1, n2, testfiles[l]); + } +} + + diff --git a/test/backupdiff/testbackupdiff.cpp b/test/backupdiff/testbackupdiff.cpp new file mode 100644 index 00000000..816f50d1 --- /dev/null +++ b/test/backupdiff/testbackupdiff.cpp @@ -0,0 +1,605 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupdiff.cpp +// Purpose: Test diffing routines for backup store files +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "BackupClientCryptoKeys.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "FileStream.h" +#include "BackupStoreFileWire.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreFileCryptVar.h" +#include "BackupStoreException.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +using namespace BackupStoreFileCryptVar; + + +// from another file +void create_test_files(); + +bool files_identical(const char *file1, const char *file2) +{ + FileStream f1(file1); + FileStream f2(file2); + + if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) + { + return false; + } + + while(f1.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1.Read(buffer1, sizeof(buffer1)); + if(f2.Read(buffer2, s) != s) + { + return false; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + return false; + } + } + + if(f2.StreamDataLeft()) + { + return false; + } + + return true; +} + +bool make_file_of_zeros(const char *filename, size_t size) +{ + #ifdef WIN32 + HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + TEST_THAT(SetFilePointer(handle, size, NULL, FILE_BEGIN) + != INVALID_SET_FILE_POINTER); + TEST_THAT(GetLastError() == NO_ERROR); + BOOL result = SetEndOfFile(handle); + if (result != TRUE) + { + BOX_ERROR("Failed to create large file " << filename << + " (" << (size >> 20) << " MB): " << + GetErrorMessage(GetLastError())); + } + TEST_THAT(result == TRUE); + TEST_THAT(CloseHandle(handle) == TRUE); + #else + int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) perror(filename); + TEST_THAT(fd >= 0); + TEST_THAT(ftruncate(fd, size) == 0); + TEST_THAT(close(fd) == 0); + #endif + + bool correct_size = ((size_t)TestGetFileSize(filename) == size); + TEST_THAT(correct_size); + if (!correct_size) + { + BOX_ERROR("Failed to create large file " << filename << + " (" << (size >> 20) << " MB): " << + "got " << (TestGetFileSize(filename) >> 20) << + " MB instead"); + } + return correct_size; +} + + +void check_encoded_file(const char *filename, int64_t OtherFileID, int new_blocks_expected, int old_blocks_expected) +{ + FileStream enc(filename); + + // Use the interface verify routine + int64_t otherIDFromFile = 0; + TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc, &otherIDFromFile)); + TEST_THAT(otherIDFromFile == OtherFileID); + + // Now do our own reading + enc.Seek(0, IOStream::SeekType_Absolute); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + // Read in header to check magic value is as expected + file_BlockIndexHeader hdr; + TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); + TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); + TEST_THAT((uint64_t)box_ntoh64(hdr.mOtherFileID) == (uint64_t)OtherFileID); + // number of blocks + int64_t nblocks = box_ntoh64(hdr.mNumBlocks); + BOX_TRACE("Reading index from '" << filename << "', has " << + nblocks << " blocks"); + BOX_TRACE("======== ===== ========== ======== ========"); + BOX_TRACE(" Index Where EncSz/Idx Size WChcksm"); + // Read them all in + int64_t nnew = 0, nold = 0; + for(int64_t b = 0; b < nblocks; ++b) + { + file_BlockIndexEntry en; + TEST_THAT(enc.ReadFullBuffer(&en, sizeof(en), 0)); + int64_t s = box_ntoh64(en.mEncodedSize); + + // Decode the rest + uint64_t iv = box_ntoh64(hdr.mEntryIVBase); + iv += b; + sBlowfishDecryptBlockEntry.SetIV(&iv); + file_BlockIndexEntryEnc entryEnc; + sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, + sizeof(entryEnc), en.mEnEnc, sizeof(en.mEnEnc)); + + + if(s > 0) + { + nnew++; + BOX_TRACE(std::setw(8) << b << " this s=" << + std::setw(8) << s << " " << + std::setw(8) << ntohl(entryEnc.mSize) << " " << + std::setw(8) << std::setfill('0') << + std::hex << ntohl(entryEnc.mWeakChecksum)); + } + else + { + nold++; + BOX_TRACE(std::setw(8) << b << " other i=" << + std::setw(8) << (0-s) << " " << + std::setw(8) << ntohl(entryEnc.mSize) << " " << + std::setw(8) << std::setfill('0') << + std::hex << ntohl(entryEnc.mWeakChecksum)); + } + } + BOX_TRACE("======== ===== ========== ======== ========"); + TEST_THAT(new_blocks_expected == nnew); + TEST_THAT(old_blocks_expected == nold); +} + +void test_diff(int from, int to, int new_blocks_expected, int old_blocks_expected, bool expect_completely_different = false) +{ + // First, get the block index of the thing it's comparing against + char from_encoded[256]; + sprintf(from_encoded, "testfiles/f%d.encoded", from); + FileStream blockindex(from_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + // make filenames + char from_orig[256]; + sprintf(from_orig, "testfiles/f%d", from); + char to_encoded[256]; + sprintf(to_encoded, "testfiles/f%d.encoded", to); + char to_diff[256]; + sprintf(to_diff, "testfiles/f%d.diff", to); + char to_orig[256]; + sprintf(to_orig, "testfiles/f%d", to); + char rev_diff[256]; + sprintf(rev_diff, "testfiles/f%d.revdiff", to); + char from_rebuild[256]; + sprintf(from_rebuild, "testfiles/f%d.rebuilt", to); + char from_rebuild_dec[256]; + sprintf(from_rebuild_dec, "testfiles/f%d.rebuilt_dec", to); + + // Then call the encode varient for diffing files + bool completelyDifferent = !expect_completely_different; // oposite of what we want + { + BackupStoreFilenameClear f1name("filename"); + FileStream out(to_diff, O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded( + BackupStoreFile::EncodeFileDiff( + to_orig, + 1 /* dir ID */, + f1name, + 1000 + from /* object ID of the file diffing from */, + blockindex, + IOStream::TimeOutInfinite, + NULL, // DiffTimer interface + 0, + &completelyDifferent)); + encoded->CopyStreamTo(out); + } + TEST_THAT(completelyDifferent == expect_completely_different); + + // Test that the number of blocks in the file match what's expected + check_encoded_file(to_diff, expect_completely_different?(0):(1000 + from), new_blocks_expected, old_blocks_expected); + + // filename + char to_testdec[256]; + sprintf(to_testdec, "testfiles/f%d.testdec", to); + + if(!completelyDifferent) + { + // Then produce a combined file + { + FileStream diff(to_diff); + FileStream diff2(to_diff); + FileStream from(from_encoded); + FileStream out(to_encoded, O_WRONLY | O_CREAT | O_EXCL); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + + // And check it + check_encoded_file(to_encoded, 0, new_blocks_expected + old_blocks_expected, 0); + } + else + { +#ifdef WIN32 + // Emulate the above stage! + char src[256], dst[256]; + sprintf(src, "testfiles\\f%d.diff", to); + sprintf(dst, "testfiles\\f%d.encoded", to); + TEST_THAT(CopyFile(src, dst, FALSE) != 0) +#else + // Emulate the above stage! + char cmd[256]; + sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to); + ::system(cmd); +#endif + } + + // Decode it + { + FileStream enc(to_encoded); + BackupStoreFile::DecodeFile(enc, to_testdec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(to_orig, to_testdec)); + } + + // Then do some comparisons against the block index + { + FileStream index(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(index); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(to_orig, index, IOStream::TimeOutInfinite) == true); + } + { + char from_orig[256]; + sprintf(from_orig, "testfiles/f%d", from); + FileStream index(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(index); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(from_orig, index, IOStream::TimeOutInfinite) == files_identical(from_orig, to_orig)); + } + + // Check that combined index creation works as expected + { + // Load a combined index into memory + FileStream diff(to_diff); + FileStream from(from_encoded); + std::auto_ptr indexCmbStr(BackupStoreFile::CombineFileIndices(diff, from)); + CollectInBufferStream indexCmb; + indexCmbStr->CopyStreamTo(indexCmb); + // Then check that it's as expected! + FileStream result(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(result); + CollectInBufferStream index; + result.CopyStreamTo(index); + TEST_THAT(indexCmb.GetSize() == index.GetSize()); + TEST_THAT(::memcmp(indexCmb.GetBuffer(), index.GetBuffer(), index.GetSize()) == 0); + } + + // Check that reverse delta can be made, and that it decodes OK + { + // Create reverse delta + { + bool reversedCompletelyDifferent = !completelyDifferent; + FileStream diff(to_diff); + FileStream from(from_encoded); + FileStream from2(from_encoded); + FileStream reversed(rev_diff, O_WRONLY | O_CREAT); + BackupStoreFile::ReverseDiffFile(diff, from, from2, reversed, to, &reversedCompletelyDifferent); + TEST_THAT(reversedCompletelyDifferent == completelyDifferent); + } + // Use it to combine a file + { + FileStream diff(rev_diff); + FileStream diff2(rev_diff); + FileStream from(to_encoded); + FileStream out(from_rebuild, O_WRONLY | O_CREAT | O_EXCL); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + // And then confirm that this file is actually the one we want + { + FileStream enc(from_rebuild); + BackupStoreFile::DecodeFile(enc, from_rebuild_dec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(from_orig, from_rebuild_dec)); + } + // Do some extra checking + { + TEST_THAT(files_identical(from_rebuild, from_encoded)); + } + } +} + +void test_combined_diff(int version1, int version2, int serial) +{ + char combined_file[256]; + char last_diff[256]; + sprintf(last_diff, "testfiles/f%d.diff", version1 + 1); // ie from version1 to version1 + 1 + + for(int v = version1 + 2; v <= version2; ++v) + { + FileStream diff1(last_diff); + char next_diff[256]; + sprintf(next_diff, "testfiles/f%d.diff", v); + FileStream diff2(next_diff); + FileStream diff2b(next_diff); + sprintf(combined_file, "testfiles/comb%d_%d.cmbdiff", version1, v); + FileStream out(combined_file, O_WRONLY | O_CREAT); + BackupStoreFile::CombineDiffs(diff1, diff2, diff2b, out); + strcpy(last_diff, combined_file); + } + + // Then do a combine on it, and check that it decodes to the right thing + char orig_enc[256]; + sprintf(orig_enc, "testfiles/f%d.encoded", version1); + char combined_out[256]; + sprintf(combined_out, "testfiles/comb%d_%d.out", version1, version2); + + { + FileStream diff(combined_file); + FileStream diff2(combined_file); + FileStream from(orig_enc); + FileStream out(combined_out, O_WRONLY | O_CREAT); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + + char combined_out_dec[256]; + sprintf(combined_out_dec, "testfiles/comb%d_%d_s%d.dec", version1, version2, serial); + char to_orig[256]; + sprintf(to_orig, "testfiles/f%d", version2); + + { + FileStream enc(combined_out); + BackupStoreFile::DecodeFile(enc, combined_out_dec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(to_orig, combined_out_dec)); + } + +} + +#define MAX_DIFF 9 +void test_combined_diffs() +{ + int serial = 0; + + // Number of items to combine at once + for(int stages = 2; stages <= 4; ++stages) + { + // Offset to get complete coverage + for(int offset = 0; offset < stages; ++offset) + { + // And then actual end file number + for(int f = 0; f <= (MAX_DIFF - stages - offset); ++f) + { + // And finally, do something! + test_combined_diff(offset + f, offset + f + stages, ++serial); + } + } + } +} + +int test(int argc, const char *argv[]) +{ + // Want to trace out all the details + #ifndef BOX_RELEASE_BUILD + #ifndef WIN32 + BackupStoreFile::TraceDetailsOfDiffProcess = true; + #endif + #endif + + // Create all the test files + create_test_files(); + + // Setup the crypto + BackupClientCryptoKeys_Setup("testfiles/backup.keys"); + + // Encode the first file + { + BackupStoreFilenameClear f0name("f0"); + FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + out.Close(); + check_encoded_file("testfiles/f0.encoded", 0, 33, 0); + } + + // Check the "seek to index" code + { + FileStream enc("testfiles/f0.encoded"); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + // Read in header to check magic value is as expected + file_BlockIndexHeader hdr; + TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); + TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); + } + + // Diff some files -- parameters are from number, to number, + // then the number of new blocks expected, and the number of old blocks expected. + + // Diff the original file to a copy of itself, and check that there is no data in the file + // This checks that the hash table is constructed properly, because two of the blocks share + // the same weak checksum. + test_diff(0, 1, 0, 33); + + // Insert some new data + // Blocks from old file moved whole, but put in different order + test_diff(1, 2, 7, 32); + + // Delete some data, but not on block boundaries + test_diff(2, 3, 1, 29); + + // Add a very small amount of data, not on block boundary + // delete a little data + test_diff(3, 4, 3, 25); + + // 1 byte insertion between two blocks + test_diff(4, 5, 1, 28); + + // a file with some new content at the very beginning + // NOTE: You might expect the last numbers to be 2, 29, but the small 1 byte block isn't searched for + test_diff(5, 6, 3, 28); + + // some new content at the very end + // NOTE: 1 byte block deleted, so number aren't what you'd initial expect. + test_diff(6, 7, 2, 30); + + // a completely different file, with no blocks matching. + test_diff(7, 8, 14, 0, true /* completely different expected */); + + // diff to zero sized file + test_diff(8, 9, 0, 0, true /* completely different expected */); + + // Test that combining diffs works + test_combined_diffs(); + + // Check zero sized file works OK to encode on its own, using normal encoding + { + { + // Encode + BackupStoreFilenameClear fn("filename"); + FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn)); + encoded->CopyStreamTo(out); + out.Close(); + check_encoded_file("testfiles/f9.zerotest", 0, 0, 0); + } + { + // Decode + FileStream enc("testfiles/f9.zerotest"); + BackupStoreFile::DecodeFile(enc, "testfiles/f9.testdec.zero", IOStream::TimeOutInfinite); + TEST_THAT(files_identical("testfiles/f9", "testfiles/f9.testdec.zero")); + } + } + +#ifndef WIN32 + // Check that symlinks aren't diffed + TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0) + // And go and diff it against the previous encoded file + { + bool completelyDifferent = false; + { + FileStream blockindex("testfiles/f1.encoded"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("filename"); + FileStream out("testfiles/f2.symlink.diff", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded( + BackupStoreFile::EncodeFileDiff( + "testfiles/f2.symlink", + 1 /* dir ID */, + f1name, + 1001 /* object ID of the file diffing from */, + blockindex, + IOStream::TimeOutInfinite, + NULL, // DiffTimer interface + 0, + &completelyDifferent)); + encoded->CopyStreamTo(out); + } + TEST_THAT(completelyDifferent == true); + check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0); + } +#endif + + // Check that diffing against a file which isn't "complete" and + // references another isn't allowed + { + FileStream blockindex("testfiles/f1.diff"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("filename"); + FileStream out("testfiles/f2.testincomplete", O_WRONLY | O_CREAT | O_EXCL); + TEST_CHECK_THROWS(BackupStoreFile::EncodeFileDiff("testfiles/f2", 1 /* dir ID */, f1name, + 1001 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile); + } + + // Found a nasty case where files of lots of the same thing + // suck up lots of processor time -- because of lots of matches + // found. Check this out! + + #ifdef WIN32 + BOX_WARNING("Testing diffing two large streams, may take a while!"); + ::fflush(stderr); + #endif + + if (!make_file_of_zeros("testfiles/zero.0", 20*1024*1024)) + { + return 1; + } + + if (!make_file_of_zeros("testfiles/zero.1", 200*1024*1024)) + { + remove("testfiles/zero.0"); + return 1; + } + + // Generate a first encoded file + { + BackupStoreFilenameClear f0name("zero.0"); + FileStream out("testfiles/zero.0.enc", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/zero.0", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + } + // Then diff from it -- time how long it takes... + { + int beginTime = time(0); + FileStream blockindex("testfiles/zero.0.enc"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("zero.1"); + FileStream out("testfiles/zero.1.enc", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("testfiles/zero.1", 1 /* dir ID */, f1name, + 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0)); + encoded->CopyStreamTo(out); + + printf("Time taken: %d seconds\n", (int)(time(0) - beginTime)); + + #ifdef WIN32 + TEST_THAT(time(0) < (beginTime + 300)); + #else + TEST_THAT(time(0) < (beginTime + 40)); + #endif + } + // Remove zero-files to save disk space + remove("testfiles/zero.0"); + remove("testfiles/zero.1"); + +#if 0 + // Code for a nasty real world example! (16Mb files, won't include them in the distribution + // for obvious reasons...) + // Generate a first encoded file + { + BackupStoreFilenameClear f0name("0000000000000000.old"); + FileStream out("testfiles/0000000000000000.enc.0", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("/Users/ben/Desktop/0000000000000000.old", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + } + // Then diff from it -- time how long it takes... + { + int beginTime = time(0); + FileStream blockindex("testfiles/0000000000000000.enc.0"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("0000000000000000.new"); + FileStream out("testfiles/0000000000000000.enc.1", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("/Users/ben/Desktop/0000000000000000.new", 1 /* dir ID */, f1name, + 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0)); + encoded->CopyStreamTo(out); + TEST_THAT(time(0) < (beginTime + 20)); + } +#endif // 0 + + return 0; +} + + diff --git a/test/backupdiff/testextra b/test/backupdiff/testextra new file mode 100644 index 00000000..165cacb9 --- /dev/null +++ b/test/backupdiff/testextra @@ -0,0 +1,2 @@ +rm -rf testfiles +mkdir testfiles diff --git a/test/backupstore/Makefile.extra b/test/backupstore/Makefile.extra new file mode 100644 index 00000000..e2e2d27c --- /dev/null +++ b/test/backupstore/Makefile.extra @@ -0,0 +1 @@ +link-extra: ../../bin/bbstored/HousekeepStoreAccount.o diff --git a/test/backupstore/testbackupstore.cpp b/test/backupstore/testbackupstore.cpp new file mode 100644 index 00000000..646e6b45 --- /dev/null +++ b/test/backupstore/testbackupstore.cpp @@ -0,0 +1,2178 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstore.cpp +// Purpose: Test backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "autogen_BackupProtocolClient.h" +#include "SSLLib.h" +#include "TLSContext.h" +#include "SocketStreamTLS.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreConstants.h" +#include "Socket.h" +#include "BackupStoreFilenameClear.h" +#include "CollectInBufferStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "FileStream.h" +#include "RaidFileController.h" +#include "RaidFileWrite.h" +#include "BackupStoreInfo.h" +#include "BackupStoreException.h" +#include "RaidFileException.h" +#include "MemBlockStream.h" +#include "BackupClientFileAttributes.h" +#include "BackupClientCryptoKeys.h" +#include "ServerControl.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreRefCountDatabase.h" +#include "BackupStoreAccounts.h" +#include "HousekeepStoreAccount.h" + +#include "MemLeakFindOn.h" + + +#define ENCFILE_SIZE 2765 + +typedef struct +{ + BackupStoreFilenameClear fn; + box_time_t mod; + int64_t id; + int64_t size; + int16_t flags; + box_time_t attrmod; +} dirtest; + +static dirtest ens[] = +{ + {BackupStoreFilenameClear(), 324324, 3432, 324, BackupStoreDirectory::Entry::Flags_File, 458763243422LL}, + {BackupStoreFilenameClear(), 3432, 32443245645LL, 78, BackupStoreDirectory::Entry::Flags_Dir, 3248972347LL}, + {BackupStoreFilenameClear(), 544435, 234234, 23324, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 2348974782LL}, + {BackupStoreFilenameClear(), 234, 235436, 6523, BackupStoreDirectory::Entry::Flags_File, 32458923175634LL}, + {BackupStoreFilenameClear(), 0x3242343532144LL, 8978979789LL, 21345, BackupStoreDirectory::Entry::Flags_File, 329483243432LL}, + {BackupStoreFilenameClear(), 324265765734LL, 12312312321LL, 324987324329874LL, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 32489747234LL}, + {BackupStoreFilenameClear(), 3452134, 7868578768LL, 324243, BackupStoreDirectory::Entry::Flags_Dir, 34786457432LL}, + {BackupStoreFilenameClear(), 43543543, 324234, 21432, BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, 3489723478327LL}, + {BackupStoreFilenameClear(), 325654765874324LL, 4353543, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 32489734789237LL}, + {BackupStoreFilenameClear(), 32144325, 436547657, 9, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 234897347234LL} +}; +static const char *ens_filenames[] = {"obj1ertewt", "obj2", "obj3", "obj4dfedfg43", "obj5", "obj6dfgs", "obj7", "obj8xcvbcx", "obj9", "obj10fgjhfg"}; +#define DIR_NUM 10 +#define DIR_DIRS 3 +#define DIR_FILES 7 +#define DIR_OLD 2 +#define DIR_DELETED 3 + +typedef struct +{ + const char *fnextra; + BackupStoreFilenameClear name; + int seed; + int size; + box_time_t mod_time; + int64_t allocated_objid; + bool should_be_old_version; + bool delete_file; +} uploadtest; + +#define TEST_FILE_FOR_PATCHING "testfiles/test2" +// a few bytes will be inserted at this point: +#define TEST_FILE_FOR_PATCHING_PATCH_AT ((64*1024)-128) +#define TEST_FILE_FOR_PATCHING_SIZE ((128*1024)+2564) +#define UPLOAD_PATCH_EN 2 + +uploadtest uploads[] = +{ + {"0", BackupStoreFilenameClear(), 324, 455, 0, 0, false, false}, + {"1", BackupStoreFilenameClear(), 3232432, 2674, 0, 0, true, false}, // old ver + {"2", BackupStoreFilenameClear(), 234, TEST_FILE_FOR_PATCHING_SIZE, 0, 0, false, false}, + {"3", BackupStoreFilenameClear(), 324324, 6763, 0, 0, false, false}, + {"4", BackupStoreFilenameClear(), 23456, 124, 0, 0, true, false}, // old ver + {"5", BackupStoreFilenameClear(), 675745, 1, 0, 0, false, false}, // will upload new attrs for this one! + {"6", BackupStoreFilenameClear(), 345213, 0, 0, 0, false, false}, + {"7", BackupStoreFilenameClear(), 12313, 3246, 0, 0, true, true}, // old ver, will get deleted + {"8", BackupStoreFilenameClear(), 457, 3434, 0, 0, false, false}, // overwrites + {"9", BackupStoreFilenameClear(), 12315, 446, 0, 0, false, false}, + {"a", BackupStoreFilenameClear(), 3476, 2466, 0, 0, false, false}, + {"b", BackupStoreFilenameClear(), 124334, 4562, 0, 0, false, false}, + {"c", BackupStoreFilenameClear(), 45778, 234, 0, 0, false, false}, // overwrites + {"d", BackupStoreFilenameClear(), 2423425, 435, 0, 0, false, true} // overwrites, will be deleted +}; +static const char *uploads_filenames[] = {"49587fds", "cvhjhj324", "sdfcscs324", "dsfdsvsdc3214", "XXsfdsdf2342", "dsfdsc232", + "sfdsdce2345", "YYstfbdtrdf76", "cvhjhj324", "fbfd098.ycy", "dfs98732hj", "svd987kjsad", "XXsfdsdf2342", "YYstfbdtrdf76"}; +#define UPLOAD_NUM 14 +#define UPLOAD_LATEST_FILES 12 +// file we'll upload some new attributes for +#define UPLOAD_ATTRS_EN 5 +#define UPLOAD_DELETE_EN 13 +// file which will be moved (as well as it's old version) +#define UPLOAD_FILE_TO_MOVE 8 + + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + + +int SkipEntries(int e, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) +{ + if(e >= DIR_NUM) return e; + + bool skip = false; + do + { + skip = false; + + if(FlagsMustBeSet != BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING) + { + if((ens[e].flags & FlagsMustBeSet) != FlagsMustBeSet) + { + skip = true; + } + } + if((ens[e].flags & FlagsNotToBeSet) != 0) + { + skip = true; + } + + if(skip) + { + ++e; + } + } while(skip && e < DIR_NUM); + + return e; +} + +void CheckEntries(BackupStoreDirectory &rDir, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) +{ + int e = 0; + + BackupStoreDirectory::Iterator i(rDir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + TEST_THAT(e < DIR_NUM); + + // Skip to entry in the ens array which matches + e = SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet); + + // Does it match? + TEST_THAT(en->GetName() == ens[e].fn && en->GetModificationTime() == ens[e].mod && en->GetObjectID() == ens[e].id && en->GetFlags() == ens[e].flags && en->GetSizeInBlocks() == ens[e].size); + + // next + ++e; + } + + // Got them all? + TEST_THAT(en == 0); + TEST_THAT(DIR_NUM == SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet)); +} + +int test1(int argc, const char *argv[]) +{ + // Initialise the raid file controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + + // test some basics -- encoding and decoding filenames + { + // Make some filenames in various ways + BackupStoreFilenameClear fn1; + fn1.SetClearFilename(std::string("filenameXYZ")); + BackupStoreFilenameClear fn2(std::string("filenameXYZ")); + BackupStoreFilenameClear fn3(fn1); + TEST_THAT(fn1 == fn2); + TEST_THAT(fn1 == fn3); + + // Check that it's been encrypted + std::string name(fn2.GetEncodedFilename()); + TEST_THAT(name.find("name") == name.npos); + + // Bung it in a stream, get it out in a Clear filename + { + CollectInBufferStream stream; + fn1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn4; + fn4.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn4.GetClearFilename() == "filenameXYZ"); + TEST_THAT(fn4 == fn1); + } + // Bung it in a stream, get it out in a server non-Clear filename (two of them into the same var) + { + BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); + CollectInBufferStream stream; + fn1.WriteToStream(stream); + fno.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilename fn5; + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5 == fn1); + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5 == fno); + } + // Same again with clear strings + { + BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); + CollectInBufferStream stream; + fn1.WriteToStream(stream); + fno.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn5; + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5.GetClearFilename() == "filenameXYZ"); + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5.GetClearFilename() == "pinglet dksfnsf jksjdf "); + } + // Test a very big filename + { + const char *fnr = "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + BackupStoreFilenameClear fnLong(fnr); + CollectInBufferStream stream; + fnLong.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn9; + fn9.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn9.GetClearFilename() == fnr); + TEST_THAT(fn9 == fnLong); + } + // Test a filename which went wrong once + { + BackupStoreFilenameClear dodgy("content-negotiation.html"); + } + } + return 0; +} + +int test2(int argc, const char *argv[]) +{ + { + // Now play with directories + + // Fill in... + BackupStoreDirectory dir1(12, 98); + for(int e = 0; e < DIR_NUM; ++e) + { + dir1.AddEntry(ens[e].fn, ens[e].mod, ens[e].id, ens[e].size, ens[e].flags, ens[e].attrmod); + } + // Got the right number + TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM); + + // Stick it into a stream and get it out again + { + CollectInBufferStream stream; + dir1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreDirectory dir2; + dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_NUM); + TEST_THAT(dir2.GetObjectID() == 12); + TEST_THAT(dir2.GetContainerID() == 98); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + + // Then do selective writes and reads + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2; + dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2; + dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_DIRS); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_Dir, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); + stream.SetForReading(); + BackupStoreDirectory dir2; + dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - DIR_OLD); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); + } + + // Finally test deleting items + { + dir1.DeleteEntry(12312312321LL); + // Verify + TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM - 1); + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2; + dir2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - 1); + } + + // Check attributes + { + int attrI[4] = {1, 2, 3, 4}; + StreamableMemBlock attr(attrI, sizeof(attrI)); + BackupStoreDirectory d1(16, 546); + d1.SetAttributes(attr, 56234987324232LL); + TEST_THAT(d1.GetAttributes() == attr); + TEST_THAT(d1.GetAttributesModTime() == 56234987324232LL); + CollectInBufferStream stream; + d1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreDirectory d2; + d2.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(d2.GetAttributes() == attr); + TEST_THAT(d2.GetAttributesModTime() == 56234987324232LL); + } + } + return 0; +} + +void write_test_file(int t) +{ + std::string filename("testfiles/test"); + filename += uploads[t].fnextra; + printf("%s\n", filename.c_str()); + + FileStream write(filename.c_str(), O_WRONLY | O_CREAT); + + R250 r(uploads[t].seed); + + unsigned char *data = (unsigned char*)malloc(uploads[t].size); + for(int l = 0; l < uploads[t].size; ++l) + { + data[l] = r.next() & 0xff; + } + write.Write(data, uploads[t].size); + + free(data); +} + +void test_test_file(int t, IOStream &rStream) +{ + // Decode to a file + BackupStoreFile::DecodeFile(rStream, "testfiles/test_download", IOStream::TimeOutInfinite); + + // Compare... + FileStream in("testfiles/test_download"); + TEST_THAT(in.BytesLeftToRead() == uploads[t].size); + + R250 r(uploads[t].seed); + + unsigned char *data = (unsigned char*)malloc(uploads[t].size); + TEST_THAT(in.ReadFullBuffer(data, uploads[t].size, 0 /* not interested in bytes read if this fails */)); + + for(int l = 0; l < uploads[t].size; ++l) + { + TEST_THAT(data[l] == (r.next() & 0xff)); + } + + free(data); + in.Close(); + TEST_THAT(unlink("testfiles/test_download") == 0); +} + +void test_everything_deleted(BackupProtocolClient &protocol, int64_t DirID) +{ + printf("Test for del: %llx\n", DirID); + + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + DirID, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + int files = 0; + int dirs = 0; + while((en = i.Next()) != 0) + { + if(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Dir) + { + dirs++; + // Recurse + test_everything_deleted(protocol, en->GetObjectID()); + } + else + { + files++; + } + // Check it's deleted + TEST_THAT(en->GetFlags() & BackupProtocolClientListDirectory::Flags_Deleted); + } + + // Check there were the right number of files and directories + TEST_THAT(files == 3); + TEST_THAT(dirs == 0 || dirs == 2); +} + +std::vector ExpectedRefCounts; + +void set_refcount(int64_t ObjectID, uint32_t RefCount = 1) +{ + if (ExpectedRefCounts.size() <= ObjectID); + { + ExpectedRefCounts.resize(ObjectID + 1, 0); + } + ExpectedRefCounts[ObjectID] = RefCount; +} + +void create_file_in_dir(std::string name, std::string source, int64_t parentId, + BackupProtocolClient &protocol, BackupStoreRefCountDatabase& rRefCount) +{ + BackupStoreFilenameClear name_encoded("file_One"); + std::auto_ptr upload(BackupStoreFile::EncodeFile( + source.c_str(), parentId, name_encoded)); + std::auto_ptr stored( + protocol.QueryStoreFile( + parentId, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + name_encoded, + *upload)); + int64_t objectId = stored->GetObjectID(); + TEST_EQUAL(objectId, rRefCount.GetLastObjectIDUsed()); + TEST_EQUAL(1, rRefCount.GetRefCount(objectId)) + set_refcount(objectId, 1); +} + +int64_t create_test_data_subdirs(BackupProtocolClient &protocol, int64_t indir, + const char *name, int depth, BackupStoreRefCountDatabase& rRefCount) +{ + // Create a directory + int64_t subdirid = 0; + BackupStoreFilenameClear dirname(name); + { + // Create with dummy attributes + int attrS = 0; + MemBlockStream attr(&attrS, sizeof(attrS)); + std::auto_ptr dirCreate(protocol.QueryCreateDirectory( + indir, + 9837429842987984LL, dirname, attr)); + subdirid = dirCreate->GetObjectID(); + } + + printf("Create subdirs, depth = %d, dirid = %llx\n", depth, subdirid); + + TEST_EQUAL(subdirid, rRefCount.GetLastObjectIDUsed()); + TEST_EQUAL(1, rRefCount.GetRefCount(subdirid)) + set_refcount(subdirid, 1); + + // Put more directories in it, if we haven't gone down too far + if(depth > 0) + { + create_test_data_subdirs(protocol, subdirid, "dir_One", + depth - 1, rRefCount); + create_test_data_subdirs(protocol, subdirid, "dir_Two", + depth - 1, rRefCount); + } + + // Stick some files in it + create_file_in_dir("file_One", "testfiles/file1", subdirid, protocol, + rRefCount); + create_file_in_dir("file_Two", "testfiles/file1", subdirid, protocol, + rRefCount); + create_file_in_dir("file_Three", "testfiles/file1", subdirid, protocol, + rRefCount); + return subdirid; +} + + +void check_dir_after_uploads(BackupProtocolClient &protocol, const StreamableMemBlock &Attributes) +{ + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + TEST_THAT(dirreply->GetObjectID() == BackupProtocolClientListDirectory::RootDirectory); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 1 /* for the first test file */); + TEST_THAT(!dir.HasAttributes()); + + // Check them! + BackupStoreDirectory::Iterator i(dir); + // Discard first + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + + for(int t = 0; t < UPLOAD_NUM; ++t) + { + en = i.Next(); + TEST_THAT(en != 0); + TEST_THAT(en->GetName() == uploads[t].name); + TEST_THAT(en->GetObjectID() == uploads[t].allocated_objid); + TEST_THAT(en->GetModificationTime() == uploads[t].mod_time); + 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; + TEST_THAT(en->GetFlags() == correct_flags); + if(t == UPLOAD_ATTRS_EN) + { + TEST_THAT(en->HasAttributes()); + TEST_THAT(en->GetAttributesHash() == 32498749832475LL); + TEST_THAT(en->GetAttributes() == Attributes); + } + else + { + // No attributes on this one + TEST_THAT(!en->HasAttributes()); + } + } + en = i.Next(); + TEST_THAT(en == 0); +} + + +typedef struct +{ + int objectsNotDel; + int deleted; + int old; +} recursive_count_objects_results; + +void recursive_count_objects_r(BackupProtocolClient &protocol, int64_t id, recursive_count_objects_results &results) +{ + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + id, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + + // Check them! + BackupStoreDirectory::Iterator i(dir); + // Discard first + BackupStoreDirectory::Entry *en = 0; + + while((en = i.Next()) != 0) + { + if((en->GetFlags() & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) == 0) results.objectsNotDel++; + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) results.deleted++; + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) results.old++; + + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) + { + recursive_count_objects_r(protocol, en->GetObjectID(), results); + } + } +} + +void recursive_count_objects(const char *hostname, int64_t id, recursive_count_objects_results &results) +{ + // Context + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + // Get a connection + SocketStreamTLS connReadOnly; + connReadOnly.Open(context, Socket::TypeINET, hostname, + BOX_PORT_BBSTORED_TEST); + BackupProtocolClient protocolReadOnly(connReadOnly); + + { + std::auto_ptr serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + std::auto_ptr loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); + } + + // Count objects + recursive_count_objects_r(protocolReadOnly, id, results); + + // Close it + protocolReadOnly.QueryFinished(); +} + +bool check_block_index(const char *encoded_file, IOStream &rBlockIndex) +{ + // Open file, and move to the right position + FileStream enc(encoded_file); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + + bool same = true; + + // Now compare the two... + while(enc.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = enc.Read(buffer1, sizeof(buffer1)); + if(rBlockIndex.Read(buffer2, s) != s) + { + same = false; + break; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + same = false; + break; + } + } + + if(rBlockIndex.StreamDataLeft()) + { + same = false; + + // Absorb all this excess data so procotol is in the first state + char buffer[2048]; + while(rBlockIndex.StreamDataLeft()) + { + rBlockIndex.Read(buffer, sizeof(buffer)); + } + } + + return same; +} + +bool check_files_same(const char *f1, const char *f2) +{ + // Open file, and move to the right position + FileStream f1s(f1); + FileStream f2s(f2); + + bool same = true; + + // Now compare the two... + while(f1s.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1s.Read(buffer1, sizeof(buffer1)); + if(f2s.Read(buffer2, s) != s) + { + same = false; + break; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + same = false; + break; + } + } + + if(f2s.StreamDataLeft()) + { + same = false; + } + + return same; +} + + +void test_server_1(BackupProtocolClient &protocol, BackupProtocolClient &protocolReadOnly) +{ + int encfile[ENCFILE_SIZE]; + { + for(int l = 0; l < ENCFILE_SIZE; ++l) + { + encfile[l] = l * 173; + } + + // Write this to a file + { + FileStream f("testfiles/file1", O_WRONLY | O_CREAT | O_EXCL); + f.Write(encfile, sizeof(encfile)); + } + + } + + // Read the root directory a few times (as it's cached, so make sure it doesn't hurt anything) + for(int l = 0; l < 3; ++l) + { + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == 0); + } + + // Read the dir from the readonly connection (make sure it gets in the cache) + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == 0); + } + + // Store a file -- first make the encoded file + BackupStoreFilenameClear store1name("testfiles/file1"); + { + FileStream out("testfiles/file1_upload1", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/file1", BackupProtocolClientListDirectory::RootDirectory, store1name)); + encoded->CopyStreamTo(out); + } + +// printf("SKIPPING\n"); +// goto skip; { + // Then send it + int64_t store1objid = 0; + { + FileStream upload("testfiles/file1_upload1"); + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::RootDirectory, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + store1name, + upload)); + store1objid = stored->GetObjectID(); + TEST_THAT(store1objid == 2); + } + set_refcount(store1objid, 1); + // And retrieve it + { + // Retrieve as object + std::auto_ptr getfile(protocol.QueryGetObject(store1objid)); + TEST_THAT(getfile->GetObjectID() == store1objid); + // BLOCK + { + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Need to put it in another stream, because it's not in stream order + CollectInBufferStream f; + filestream->CopyStreamTo(f); + f.SetForReading(); + // Get and decode + BackupStoreFile::DecodeFile(f, "testfiles/file1_upload_retrieved", IOStream::TimeOutInfinite); + } + + // Retrieve as file + std::auto_ptr getobj(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, store1objid)); + TEST_THAT(getobj->GetObjectID() == store1objid); + // BLOCK + { + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Get and decode + BackupStoreFile::DecodeFile(*filestream, "testfiles/file1_upload_retrieved_str", IOStream::TimeOutInfinite); + } + + // Read in rebuilt original, and compare contents + { + FileStream in("testfiles/file1_upload_retrieved"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + { + FileStream in("testfiles/file1_upload_retrieved_str"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + + // Retrieve the block index, by ID + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(store1objid)); + TEST_THAT(getblockindex->GetObjectID() == store1objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + // Check against uploaded file + TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); + } + // and again, by name + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName(BackupProtocolClientListDirectory::RootDirectory, store1name)); + TEST_THAT(getblockindex->GetObjectID() == store1objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + // Check against uploaded file + TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); + } + } + // Get the directory again, and see if the entry is in it + { + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == 1); + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + TEST_THAT(i.Next() == 0); + if(en != 0) + { + TEST_THAT(en->GetName() == store1name); + TEST_THAT(en->GetModificationTime() == 0x123456789abcdefLL); + TEST_THAT(en->GetAttributesHash() == 0x7362383249872dfLL); + TEST_THAT(en->GetObjectID() == store1objid); + TEST_THAT(en->GetSizeInBlocks() < ((ENCFILE_SIZE * 4 * 3) / 2 / 2048)+2); + TEST_THAT(en->GetFlags() == BackupStoreDirectory::Entry::Flags_File); + } + } + + // Try using GetFile on a directory + { + TEST_CHECK_THROWS(std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, BackupProtocolClientListDirectory::RootDirectory)), + ConnectionException, Conn_Protocol_UnexpectedReply); + } +} + +void init_context(TLSContext& rContext) +{ + rContext.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); +} + +std::auto_ptr open_conn(const char *hostname, + TLSContext& rContext) +{ + init_context(rContext); + std::auto_ptr conn(new SocketStreamTLS); + conn->Open(rContext, Socket::TypeINET, hostname, + BOX_PORT_BBSTORED_TEST); + return conn; +} + +std::auto_ptr test_server_login(SocketStreamTLS& rConn) +{ + // Make a protocol + std::auto_ptr protocol(new + BackupProtocolClient(rConn)); + + // Check the version + std::auto_ptr serverVersion( + protocol->QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + std::auto_ptr loginConf( + protocol->QueryLogin(0x01234567, 0)); + + return protocol; +} + +void run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount) +{ + std::string rootDir = BackupStoreAccounts::GetAccountRoot(rAccount); + int discSet = rAccount.GetDiscSet(); + + // Do housekeeping on this account + HousekeepStoreAccount housekeeping(rAccount.GetID(), rootDir, + discSet, NULL); + housekeeping.DoHousekeeping(true /* keep trying forever */); +} + +int test_server(const char *hostname) +{ + TLSContext context; + std::auto_ptr conn = open_conn(hostname, context); + std::auto_ptr apProtocol( + test_server_login(*conn)); + BackupProtocolClient& protocol(*apProtocol); + + // Make some test attributes + #define ATTR1_SIZE 245 + #define ATTR2_SIZE 23 + #define ATTR3_SIZE 122 + int attr1[ATTR1_SIZE]; + int attr2[ATTR2_SIZE]; + int attr3[ATTR3_SIZE]; + { + R250 r(3465657); + for(int l = 0; l < ATTR1_SIZE; ++l) {attr1[l] = r.next();} + for(int l = 0; l < ATTR2_SIZE; ++l) {attr2[l] = r.next();} + for(int l = 0; l < ATTR3_SIZE; ++l) {attr3[l] = r.next();} + } + + // BLOCK + { + // Get it logging + FILE *protocolLog = ::fopen("testfiles/protocol.log", "w"); + TEST_THAT(protocolLog != 0); + protocol.SetLogToFile(protocolLog); + +#ifndef WIN32 + // Check that we can't open a new connection which requests write permissions + { + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, hostname, + BOX_PORT_BBSTORED_TEST); + BackupProtocolClient protocol(conn); + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + TEST_CHECK_THROWS(std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, 0)), + ConnectionException, Conn_Protocol_UnexpectedReply); + protocol.QueryFinished(); + } +#endif + + // Set the client store marker + protocol.QuerySetClientStoreMarker(0x8732523ab23aLL); + +#ifndef WIN32 + // Open a new connection which is read only + SocketStreamTLS connReadOnly; + connReadOnly.Open(context, Socket::TypeINET, hostname, + BOX_PORT_BBSTORED_TEST); + BackupProtocolClient protocolReadOnly(connReadOnly); + + // Get it logging + FILE *protocolReadOnlyLog = ::fopen("testfiles/protocolReadOnly.log", "w"); + TEST_THAT(protocolReadOnlyLog != 0); + protocolReadOnly.SetLogToFile(protocolReadOnlyLog); + + { + std::auto_ptr serverVersion(protocolReadOnly.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + std::auto_ptr loginConf(protocolReadOnly.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); + + // Check client store marker + TEST_THAT(loginConf->GetClientStoreMarker() == 0x8732523ab23aLL); + } +#else // WIN32 + BackupProtocolClient& protocolReadOnly(protocol); +#endif + + test_server_1(protocol, protocolReadOnly); + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // Create and upload some test files + int64_t maxID = 0; + for(int t = 0; t < UPLOAD_NUM; ++t) + { + write_test_file(t); + + std::string filename("testfiles/test"); + filename += uploads[t].fnextra; + int64_t modtime = 0; + + std::auto_ptr upload(BackupStoreFile::EncodeFile(filename.c_str(), BackupProtocolClientListDirectory::RootDirectory, uploads[t].name, &modtime)); + TEST_THAT(modtime != 0); + + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::RootDirectory, + modtime, + modtime, /* use it for attr hash too */ + 0, /* diff from ID */ + uploads[t].name, + *upload)); + uploads[t].allocated_objid = stored->GetObjectID(); + uploads[t].mod_time = modtime; + if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); + set_refcount(stored->GetObjectID(), 1); + BOX_TRACE("wrote file " << filename << " to server " + "as object " << + BOX_FORMAT_OBJECTID(stored->GetObjectID())); + } + + // Add some attributes onto one of them + { + MemBlockStream attrnew(attr3, sizeof(attr3)); + std::auto_ptr set(protocol.QuerySetReplacementFileAttributes( + BackupProtocolClientListDirectory::RootDirectory, + 32498749832475LL, + uploads[UPLOAD_ATTRS_EN].name, + attrnew)); + TEST_THAT(set->GetObjectID() == uploads[UPLOAD_ATTRS_EN].allocated_objid); + } + + // Delete one of them (will implicitly delete an old version) + { + std::auto_ptr del(protocol.QueryDeleteFile( + BackupProtocolClientListDirectory::RootDirectory, + uploads[UPLOAD_DELETE_EN].name)); + TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); + } + // 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); + std::auto_ptr getobj(protocol.QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid)); + std::auto_ptr objstream(protocol.ReceiveStream()); + objstream->CopyStreamTo(out); + } + // query index and test + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName( + BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_DELETE_EN].name)); + TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + TEST_THAT(check_block_index("testfiles/downloaddelobj", *blockIndexStream)); + } + + // Download them all... (even deleted files) + for(int t = 0; t < UPLOAD_NUM; ++t) + { + printf("%d\n", t); + std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, uploads[t].allocated_objid)); + TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid); + std::auto_ptr filestream(protocol.ReceiveStream()); + test_test_file(t, *filestream); + } + + { + StreamableMemBlock attrtest(attr3, sizeof(attr3)); + + // Use the read only connection to verify that the directory is as we expect + printf("\n\n==== Reading directory using read-only connection\n"); + check_dir_after_uploads(protocolReadOnly, attrtest); + printf("done.\n\n"); + // And on the read/write one + check_dir_after_uploads(protocol, attrtest); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // Check diffing and rsync like stuff... + // Build a modified file + { + // Basically just insert a bit in the middle + TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING) == TEST_FILE_FOR_PATCHING_SIZE); + FileStream in(TEST_FILE_FOR_PATCHING); + void *buf = ::malloc(TEST_FILE_FOR_PATCHING_SIZE); + FileStream out(TEST_FILE_FOR_PATCHING ".mod", O_WRONLY | O_CREAT | O_EXCL); + TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_PATCH_AT); + out.Write(buf, TEST_FILE_FOR_PATCHING_PATCH_AT); + char insert[13] = "INSERTINSERT"; + out.Write(insert, sizeof(insert)); + TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); + out.Write(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); + ::free(buf); + } + { + // Fetch the block index for this one + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName( + BackupProtocolClientListDirectory::RootDirectory, uploads[UPLOAD_PATCH_EN].name)); + TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + + // Do the patching + bool isCompletelyDifferent = false; + int64_t modtime; + std::auto_ptr patchstream( + BackupStoreFile::EncodeFileDiff( + TEST_FILE_FOR_PATCHING ".mod", + BackupProtocolClientListDirectory::RootDirectory, + uploads[UPLOAD_PATCH_EN].name, + uploads[UPLOAD_PATCH_EN].allocated_objid, + *blockIndexStream, + IOStream::TimeOutInfinite, + NULL, // pointer to DiffTimer impl + &modtime, &isCompletelyDifferent)); + TEST_THAT(isCompletelyDifferent == false); + // Sent this to a file, so we can check the size, rather than uploading it directly + { + FileStream patch(TEST_FILE_FOR_PATCHING ".patch", O_WRONLY | O_CREAT | O_EXCL); + patchstream->CopyStreamTo(patch); + } + // Make sure the stream is a plausible size for a patch containing only one new block + TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING ".patch") < (8*1024)); + // Upload it + int64_t patchedID = 0; + { + FileStream uploadpatch(TEST_FILE_FOR_PATCHING ".patch"); + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::RootDirectory, + modtime, + modtime, /* use it for attr hash too */ + uploads[UPLOAD_PATCH_EN].allocated_objid, /* diff from ID */ + uploads[UPLOAD_PATCH_EN].name, + uploadpatch)); + TEST_THAT(stored->GetObjectID() > 0); + if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); + patchedID = stored->GetObjectID(); + } + + set_refcount(patchedID, 1); + + // Then download it to check it's OK + std::auto_ptr getFile(protocol.QueryGetFile(BackupProtocolClientListDirectory::RootDirectory, patchedID)); + TEST_THAT(getFile->GetObjectID() == patchedID); + std::auto_ptr 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")); + } + + // Create a directory + int64_t subdirid = 0; + BackupStoreFilenameClear dirname("lovely_directory"); + { + // Attributes + MemBlockStream attr(attr1, sizeof(attr1)); + std::auto_ptr dirCreate(protocol.QueryCreateDirectory( + BackupProtocolClientListDirectory::RootDirectory, + 9837429842987984LL, dirname, attr)); + subdirid = dirCreate->GetObjectID(); + TEST_THAT(subdirid == maxID + 1); + } + + set_refcount(subdirid, 1); + + // Stick a file in it + int64_t subdirfileid = 0; + { + std::string filename("testfiles/test0"); + int64_t modtime; + std::auto_ptr upload(BackupStoreFile::EncodeFile(filename.c_str(), subdirid, uploads[0].name, &modtime)); + + std::auto_ptr stored(protocol.QueryStoreFile( + subdirid, + modtime, + modtime, /* use for attr hash too */ + 0, /* diff from ID */ + uploads[0].name, + *upload)); + subdirfileid = stored->GetObjectID(); + } + + set_refcount(subdirfileid, 1); + + printf("\n==== Checking upload using read-only connection\n"); + // Check the directories on the read only connection + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == UPLOAD_NUM + 3 /* for the first test file, the patched upload, and this new dir */); + + // Check the last one... + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + BackupStoreDirectory::Entry *t = 0; + while((t = i.Next()) != 0) + { + if(en != 0) + { + // here for all but last object + TEST_THAT(en->GetObjectID() != subdirid); + TEST_THAT(en->GetName() != dirname); + } + en = t; + } + // Does it look right? + TEST_THAT(en->GetName() == dirname); + TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir); + TEST_THAT(en->GetObjectID() == subdirid); + TEST_THAT(en->GetModificationTime() == 0); // dirs don't have modification times. + } + + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, true /* get attributes */)); + TEST_THAT(dirreply->GetObjectID() == subdirid); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == 1); + + // Check the last one... + BackupStoreDirectory::Iterator i(dir); + // Discard first + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + // Does it look right? + TEST_THAT(en->GetName() == uploads[0].name); + TEST_THAT(en->GetFlags() == BackupProtocolClientListDirectory::Flags_File); + TEST_THAT(en->GetObjectID() == subdirfileid); + TEST_THAT(en->GetModificationTime() != 0); + + // Attributes + TEST_THAT(dir.HasAttributes()); + TEST_THAT(dir.GetAttributesModTime() == 9837429842987984LL); + StreamableMemBlock attr(attr1, sizeof(attr1)); + TEST_THAT(dir.GetAttributes() == attr); + } + printf("done.\n\n"); + + // Check that we don't get attributes if we don't ask for them + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes! */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(!dir.HasAttributes()); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // Change attributes on the directory + { + MemBlockStream attrnew(attr2, sizeof(attr2)); + std::auto_ptr changereply(protocol.QueryChangeDirAttributes( + subdirid, + 329483209443598LL, + attrnew)); + TEST_THAT(changereply->GetObjectID() == subdirid); + } + // Check the new attributes + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + subdirid, + 0, // no flags + BackupProtocolClientListDirectory::Flags_EXCLUDE_EVERYTHING, true /* get attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + TEST_THAT(dir.GetNumberOfEntries() == 0); + + // Attributes + TEST_THAT(dir.HasAttributes()); + TEST_THAT(dir.GetAttributesModTime() == 329483209443598LL); + StreamableMemBlock attrtest(attr2, sizeof(attr2)); + TEST_THAT(dir.GetAttributes() == attrtest); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // Test moving a file + { + BackupStoreFilenameClear newName("moved-files"); + + std::auto_ptr rep(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + BackupProtocolClientListDirectory::RootDirectory, + subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName)); + TEST_THAT(rep->GetObjectID() == uploads[UPLOAD_FILE_TO_MOVE].allocated_objid); + } + + // Try some dodgy renames + { + BackupStoreFilenameClear newName("moved-files"); + TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + BackupProtocolClientListDirectory::RootDirectory, + subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName), + ConnectionException, Conn_Protocol_UnexpectedReply); + TEST_CHECK_THROWS(protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + subdirid, + subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName), + ConnectionException, Conn_Protocol_UnexpectedReply); + } + + // Rename within a directory + { + BackupStoreFilenameClear newName("moved-files-x"); + protocol.QueryMoveObject(uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + subdirid, + subdirid, BackupProtocolClientMoveObject::Flags_MoveAllWithSameName, newName); + } + + // Check it's all gone from the root directory... + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + // Read all entries + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + TEST_THAT(en->GetName() != uploads[UPLOAD_FILE_TO_MOVE].name); + } + } + + // Check the old and new versions are in the other directory + { + BackupStoreFilenameClear lookFor("moved-files-x"); + + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + // Check entries + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + bool foundCurrent = false; + bool foundOld = false; + while((en = i.Next()) != 0) + { + if(en->GetName() == lookFor) + { + if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File)) foundCurrent = true; + if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion)) foundOld = true; + } + } + TEST_THAT(foundCurrent); + TEST_THAT(foundOld); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // make a little bit more of a thing to look at + int64_t subsubdirid = 0; + int64_t subsubfileid = 0; + { + BackupStoreFilenameClear nd("sub2"); + // Attributes + MemBlockStream attr(attr1, sizeof(attr1)); + std::auto_ptr dirCreate(protocol.QueryCreateDirectory( + subdirid, + 9837429842987984LL, nd, attr)); + subsubdirid = dirCreate->GetObjectID(); + + FileStream upload("testfiles/file1_upload1"); + BackupStoreFilenameClear nf("file2"); + std::auto_ptr stored(protocol.QueryStoreFile( + subsubdirid, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + nf, + upload)); + subsubfileid = stored->GetObjectID(); + } + + set_refcount(subsubdirid, 1); + set_refcount(subsubfileid, 1); + + // Query names -- test that invalid stuff returns not found OK + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(3248972347823478927LL, subsubdirid)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(subsubfileid, 2342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(38947234789LL, 2342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + + // Query names... first, get info for the file + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(subsubfileid, subsubdirid)); + std::auto_ptr namestream(protocol.ReceiveStream()); + + TEST_THAT(nameRep->GetNumNameElements() == 3); + TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_File); + TEST_THAT(nameRep->GetModificationTime() == 0x123456789abcdefLL); + TEST_THAT(nameRep->GetAttributesHash() == 0x7362383249872dfLL); + static const char *testnames[] = {"file2","sub2","lovely_directory"}; + for(int l = 0; l < nameRep->GetNumNameElements(); ++l) + { + BackupStoreFilenameClear fn; + fn.ReadFromStream(*namestream, 10000); + TEST_THAT(fn.GetClearFilename() == testnames[l]); + } + } + + // Query names... secondly, for the directory + { + std::auto_ptr nameRep(protocol.QueryGetObjectName(BackupProtocolClientGetObjectName::ObjectID_DirectoryOnly, subsubdirid)); + std::auto_ptr namestream(protocol.ReceiveStream()); + + TEST_THAT(nameRep->GetNumNameElements() == 2); + TEST_THAT(nameRep->GetFlags() == BackupProtocolClientListDirectory::Flags_Dir); + static const char *testnames[] = {"sub2","lovely_directory"}; + for(int l = 0; l < nameRep->GetNumNameElements(); ++l) + { + BackupStoreFilenameClear fn; + fn.ReadFromStream(*namestream, 10000); + TEST_THAT(fn.GetClearFilename() == testnames[l]); + } + } + +//} skip: + + std::auto_ptr apAccounts( + BackupStoreAccountDatabase::Read( + "testfiles/accounts.txt")); + std::auto_ptr apRefCount( + BackupStoreRefCountDatabase::Load( + apAccounts->GetEntry(0x1234567), true)); + + // Create some nice recursive directories + int64_t dirtodelete = create_test_data_subdirs(protocol, + BackupProtocolClientListDirectory::RootDirectory, + "test_delete", 6 /* depth */, *apRefCount); + + // And delete them + { + std::auto_ptr dirdel(protocol.QueryDeleteDirectory( + dirtodelete)); + TEST_THAT(dirdel->GetObjectID() == dirtodelete); + } + + // Get the root dir, checking for deleted items + { + // Command + std::auto_ptr dirreply(protocolReadOnly.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_Dir | BackupProtocolClientListDirectory::Flags_Deleted, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocolReadOnly.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + + // Check there's only that one entry + TEST_THAT(dir.GetNumberOfEntries() == 1); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + if(en) + { + TEST_THAT(en->GetObjectID() == dirtodelete); + BackupStoreFilenameClear n("test_delete"); + TEST_THAT(en->GetName() == n); + } + + // Then... check everything's deleted + test_everything_deleted(protocolReadOnly, dirtodelete); + } + + // Finish the connections +#ifndef WIN32 + protocolReadOnly.QueryFinished(); +#endif + protocol.QueryFinished(); + + // Close logs +#ifndef WIN32 + ::fclose(protocolReadOnlyLog); +#endif + ::fclose(protocolLog); + } + + return 0; +} + +int test3(int argc, const char *argv[]) +{ + // Now test encoded files + // TODO: This test needs to check failure situations as well as everything working, + // but this will be saved for the full implementation. + int encfile[ENCFILE_SIZE]; + { + for(int l = 0; l < ENCFILE_SIZE; ++l) + { + encfile[l] = l * 173; + } + + // Encode and decode a small block (shouldn't be compressed) + { + #define SMALL_BLOCK_SIZE 251 + int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(SMALL_BLOCK_SIZE); + TEST_THAT(encBlockSize > SMALL_BLOCK_SIZE); + BackupStoreFile::EncodingBuffer encoded; + encoded.Allocate(encBlockSize / 8); // make sure reallocation happens + + // Encode! + int encSize = BackupStoreFile::EncodeChunk(encfile, SMALL_BLOCK_SIZE, encoded); + // Check the header says it's not been compressed + TEST_THAT((encoded.mpBuffer[0] & 1) == 0); + // Check the output size has been inflated (no compression) + TEST_THAT(encSize > SMALL_BLOCK_SIZE); + + // Decode it + int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(SMALL_BLOCK_SIZE); + TEST_THAT(decBlockSize > SMALL_BLOCK_SIZE); + uint8_t *decoded = (uint8_t*)malloc(decBlockSize); + int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); + TEST_THAT(decSize < decBlockSize); + TEST_THAT(decSize == SMALL_BLOCK_SIZE); + + // Check it came out of the wash the same + TEST_THAT(::memcmp(encfile, decoded, SMALL_BLOCK_SIZE) == 0); + + free(decoded); + } + + // Encode and decode a big block (should be compressed) + { + int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(ENCFILE_SIZE); + TEST_THAT(encBlockSize > ENCFILE_SIZE); + BackupStoreFile::EncodingBuffer encoded; + encoded.Allocate(encBlockSize / 8); // make sure reallocation happens + + // Encode! + int encSize = BackupStoreFile::EncodeChunk(encfile, ENCFILE_SIZE, encoded); + // Check the header says it's compressed + TEST_THAT((encoded.mpBuffer[0] & 1) == 1); + // Check the output size make it likely that it's compressed (is very compressible data) + TEST_THAT(encSize < ENCFILE_SIZE); + + // Decode it + int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(ENCFILE_SIZE); + TEST_THAT(decBlockSize > ENCFILE_SIZE); + uint8_t *decoded = (uint8_t*)malloc(decBlockSize); + int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); + TEST_THAT(decSize < decBlockSize); + TEST_THAT(decSize == ENCFILE_SIZE); + + // Check it came out of the wash the same + TEST_THAT(::memcmp(encfile, decoded, ENCFILE_SIZE) == 0); + + free(decoded); + } + + // The test block to a file + { + FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT | O_EXCL); + f.Write(encfile, sizeof(encfile)); + } + + // Encode it + { + FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT | O_EXCL); + BackupStoreFilenameClear name("testfiles/testenc1"); + + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name)); + encoded->CopyStreamTo(out); + } + + // Verify it + { + FileStream enc("testfiles/testenc1_enc"); + TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true); + } + + // Decode it + { + FileStream enc("testfiles/testenc1_enc"); + BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite); + } + + // Read in rebuilt original, and compare contents + { + TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile)); + FileStream in("testfiles/testenc1_orig"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + + // Check how many blocks it had, and test the stream based interface + { + FileStream enc("testfiles/testenc1_enc"); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == sizeof(encfile)); + TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 3); + } + + // Test that the last block in a file, if less than 256 bytes, gets put into the last block + { + #define FILE_SIZE_JUST_OVER ((4096*2)+58) + FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT | O_EXCL); + f.Write(encfile + 2, FILE_SIZE_JUST_OVER); + BackupStoreFilenameClear name("testenc2"); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name)); + CollectInBufferStream e; + encoded->CopyStreamTo(e); + e.SetForReading(); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(e, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 879 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == FILE_SIZE_JUST_OVER); + TEST_THAT(memcmp(encfile + 2, d.GetBuffer(), FILE_SIZE_JUST_OVER) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 2); + } + + // Test that reordered streams work too + { + FileStream enc("testfiles/testenc1_enc"); + std::auto_ptr reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false)); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == sizeof(encfile)); + TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 3); + } + +#ifndef WIN32 // no symlinks on Win32 + // Try out doing this on a symlink + { + TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0); + BackupStoreFilenameClear name("testsymlink"); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testsymlink", 32, name)); + // Can't decode it from the stream, because it's in file order, and doesn't have the + // required properties to be able to reorder it. So buffer it... + CollectInBufferStream b; + encoded->CopyStreamTo(b); + b.SetForReading(); + // Decode it + BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite); + } +#endif + } + + // Store info + { + RaidFileWrite::CreateDirectory(0, "test-info"); + BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL); + TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile); + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); + TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->RemovedDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); + } + { + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, false)); + info->ChangeBlocksUsed(8); + info->ChangeBlocksInOldFiles(9); + info->ChangeBlocksInDeletedFiles(10); + info->ChangeBlocksUsed(-1); + info->ChangeBlocksInOldFiles(-4); + info->ChangeBlocksInDeletedFiles(-9); + TEST_CHECK_THROWS(info->ChangeBlocksUsed(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + info->AddDeletedDirectory(2); + info->AddDeletedDirectory(3); + info->AddDeletedDirectory(4); + info->RemovedDeletedDirectory(3); + TEST_CHECK_THROWS(info->RemovedDeletedDirectory(9), BackupStoreException, StoreInfoDirNotInList); + info->Save(); + } + { + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); + TEST_THAT(info->GetBlocksUsed() == 7); + TEST_THAT(info->GetBlocksInOldFiles() == 5); + TEST_THAT(info->GetBlocksInDeletedFiles() == 1); + TEST_THAT(info->GetBlocksSoftLimit() == 3461231233455433LL); + TEST_THAT(info->GetBlocksHardLimit() == 2934852487LL); + const std::vector &delfiles(info->GetDeletedDirectories()); + TEST_THAT(delfiles.size() == 2); + TEST_THAT(delfiles[0] == 2); + TEST_THAT(delfiles[1] == 4); + } + +//printf("SKIPPINGTESTS---------\n"); +//return 0; + + // Context + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + // First, try logging in without an account having been created... just make sure login fails. + + std::string cmd = BBSTORED " " + bbstored_args + + " testfiles/bbstored.conf"; + int pid = LaunchServer(cmd.c_str(), "testfiles/bbstored.pid"); + + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + ::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 serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + TEST_CHECK_THROWS(std::auto_ptr 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 apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + + std::auto_ptr 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(); + + // 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")); + + TLSContext context; + std::auto_ptr conn = open_conn("localhost", + context); + test_server_login(*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", BackupProtocolClientListDirectory::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", + BackupProtocolClientListDirectory::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 serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, 0)); + + int64_t modtime = 0; + + BackupStoreFilenameClear fnx("exceed-limit"); + std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/test3", BackupProtocolClientListDirectory::RootDirectory, fnx, &modtime)); + TEST_THAT(modtime != 0); + + TEST_CHECK_THROWS(std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::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 dirCreate(protocol.QueryCreateDirectory( + BackupProtocolClientListDirectory::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 + } + + return 0; +} + +int multi_server() +{ + printf("Starting server for connection from remote machines...\n"); + + // Create an account for the test client + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf create 01234567 0 " + "30000B 40000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // First, try logging in without an account having been created... just make sure login fails. + + int pid = LaunchServer(BBSTORED " testfiles/bbstored_multi.conf", + "testfiles/bbstored.pid"); + + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // Wait for a keypress + printf("Press ENTER to terminate the server\n"); + char line[512]; + fgets(line, 512, stdin); + printf("Terminating server...\n"); + + // 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 + } + + + return 0; +} + +#ifdef WIN32 +WCHAR* ConvertUtf8ToWideString(const char* pString); +std::string ConvertPathToAbsoluteUnicode(const char *pFileName); +#endif + +int test(int argc, const char *argv[]) +{ +#ifdef WIN32 + // this had better work, or bbstored will die when combining diffs + char* file = "foo"; + std::string abs = ConvertPathToAbsoluteUnicode(file); + WCHAR* wfile = ConvertUtf8ToWideString(abs.c_str()); + + 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; + + HANDLE h1 = CreateFileW(wfile, accessRights, shareMode, + NULL, OPEN_ALWAYS, FILE_FLAG_BACKUP_SEMANTICS, NULL); + assert(h1 != INVALID_HANDLE_VALUE); + TEST_THAT(h1 != INVALID_HANDLE_VALUE); + + accessRights = FILE_READ_ATTRIBUTES | + FILE_LIST_DIRECTORY | FILE_READ_EA; + + HANDLE h2 = CreateFileW(wfile, accessRights, shareMode, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + assert(h2 != INVALID_HANDLE_VALUE); + TEST_THAT(h2 != INVALID_HANDLE_VALUE); + + CloseHandle(h2); + CloseHandle(h1); + delete [] wfile; + + h1 = openfile("foo", O_CREAT | O_RDWR, 0); + TEST_THAT(h1 != INVALID_HANDLE_VALUE); + h2 = openfile("foo", O_RDWR, 0); + TEST_THAT(h2 != INVALID_HANDLE_VALUE); + CloseHandle(h2); + CloseHandle(h1); +#endif + + // SSL library + SSLLib::Initialise(); + + // Give a test key for the filenames +// BackupStoreFilenameClear::SetBlowfishKey(FilenameEncodingKey, sizeof(FilenameEncodingKey)); + // And set the encoding to blowfish +// BackupStoreFilenameClear::SetEncodingMethod(BackupStoreFilename::Encoding_Blowfish); + + // And for directory attributes -- need to set it, as used in file encoding +// BackupClientFileAttributes::SetBlowfishKey(AttributesEncodingKey, sizeof(AttributesEncodingKey)); + + // And finally for file encoding +// BackupStoreFile::SetBlowfishKeys(FileEncodingKey, sizeof(FileEncodingKey), FileBlockEntryEncodingKey, sizeof(FileBlockEntryEncodingKey)); + + // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used + // for seeing what's going on. + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // encode in some filenames -- can't do static initialisation + // because the key won't be set up when these are initialised + { + MEMLEAKFINDER_NO_LEAKS + + for(unsigned int l = 0; l < sizeof(ens_filenames) / sizeof(ens_filenames[0]); ++l) + { + ens[l].fn = BackupStoreFilenameClear(ens_filenames[l]); + } + for(unsigned int l = 0; l < sizeof(uploads_filenames) / sizeof(uploads_filenames[0]); ++l) + { + uploads[l].name = BackupStoreFilenameClear(uploads_filenames[l]); + } + } + + // Trace errors out + SET_DEBUG_SSLLIB_TRACE_ERRORS + + if(argc == 2 && strcmp(argv[1], "server") == 0) + { + return multi_server(); + } + if(argc == 3 && strcmp(argv[1], "client") == 0) + { + return test_server(argv[2]); + } +// large file test +/* { + int64_t modtime = 0; + std::auto_ptr upload(BackupStoreFile::EncodeFile("/Users/ben/temp/large.tar", + BackupProtocolClientListDirectory::RootDirectory, uploads[0].name, &modtime)); + TEST_THAT(modtime != 0); + FileStream write("testfiles/large.enc", O_WRONLY | O_CREAT); + upload->CopyStreamTo(write); + } +printf("SKIPPING TESTS ------------------------------------------------------\n"); +return 0;*/ + int r = 0; + r = test1(argc, argv); + if(r != 0) return r; + r = test2(argc, argv); + if(r != 0) return r; + r = test3(argc, argv); + if(r != 0) return r; + return 0; +} + diff --git a/test/backupstore/testextra b/test/backupstore/testextra new file mode 100644 index 00000000..798c8c67 --- /dev/null +++ b/test/backupstore/testextra @@ -0,0 +1,4 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data diff --git a/test/backupstore/testfiles/accounts.txt b/test/backupstore/testfiles/accounts.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/backupstore/testfiles/bbackupd.keys b/test/backupstore/testfiles/bbackupd.keys new file mode 100644 index 00000000..4c58fc22 Binary files /dev/null and b/test/backupstore/testfiles/bbackupd.keys differ diff --git a/test/backupstore/testfiles/bbstored.conf b/test/backupstore/testfiles/bbstored.conf new file mode 100644 index 00000000..460b9d59 --- /dev/null +++ b/test/backupstore/testfiles/bbstored.conf @@ -0,0 +1,17 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +ExtendedLogging = yes + +TimeBetweenHousekeeping = 10 + +Server +{ + PidFile = testfiles/bbstored.pid + ListenAddresses = inet:localhost:22011 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/backupstore/testfiles/bbstored_multi.conf b/test/backupstore/testfiles/bbstored_multi.conf new file mode 100644 index 00000000..73c70aa9 --- /dev/null +++ b/test/backupstore/testfiles/bbstored_multi.conf @@ -0,0 +1,16 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +TimeBetweenHousekeeping = 5 + +Server +{ + PidFile = testfiles/bbstored.pid + # 0.0.0.0 is the 'any' address, allowing connections from things other than localhost + ListenAddresses = inet:0.0.0.0 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/backupstore/testfiles/clientCerts.pem b/test/backupstore/testfiles/clientCerts.pem new file mode 100644 index 00000000..c1f14fa7 --- /dev/null +++ b/test/backupstore/testfiles/clientCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w +MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 +OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A +LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k +Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU +knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx +hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o +EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/clientPrivKey.pem b/test/backupstore/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..34b1af2a --- /dev/null +++ b/test/backupstore/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev +ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G +9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB +AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N +eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq +iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp +81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 +qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU +LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ +V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B +BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 +eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 +Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/clientReq.pem b/test/backupstore/testfiles/clientReq.pem new file mode 100644 index 00000000..8eee0b5f --- /dev/null +++ b/test/backupstore/testfiles/clientReq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWTCBwwIBADAaMRgwFgYDVQQDEw9CQUNLVVAtMDEyMzQ1NjcwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBAL6bTOgPvmXZMWDfdjiOndDt9oIkXcm0nVFTSZNP +VdaXVSRYV6+KMIbVb+nfMg5z2jToEtS5cWfQh3EeQC7RcCPcJhhwXOWtrhQmRjkU +2koIJ7QsrYb0Vdwxpv31O5kTBOy3tf7VlsY8nJ5NpEXs+nexX4+eklwxaMWNnSvD +MEWrAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBtz10sGGYhbw9+7L8bOtOUV6j9 +46jnbHGXHmdBZsg8ZWgKBJQ61HwvKCNA+KAEeb9yMxWgpJRGqFk6yvPb62XXuRGl +4RQN0/6rRc8GJh3Qi4oPV1GYnzyYg2+bjZAgeMoL6ro1YuH52CTHJpQ3Arg2Ortz +xVxbWyMouzjc1g4gdw== +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/clientTrustedCAs.pem b/test/backupstore/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/clientTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/query.conf b/test/backupstore/testfiles/query.conf new file mode 100644 index 00000000..984ace6c --- /dev/null +++ b/test/backupstore/testfiles/query.conf @@ -0,0 +1,34 @@ + +# this is a dummy config file so that bbackupquery can be used + + +CertificateFile = testfiles/clientCerts.pem +PrivateKeyFile = testfiles/clientPrivKey.pem +TrustedCAsFile = testfiles/clientTrustedCAs.pem + +KeysFile = testfiles/bbackupd.keys + +DataDirectory = testfiles/bbackupd-data + +StoreHostname = localhost +AccountNumber = 0x01234567 +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +# this is just a dummy entry +BackupLocations +{ + test_delete + { + Path = testfiles/test_delete + } +} + diff --git a/test/backupstore/testfiles/raidfile.conf b/test/backupstore/testfiles/raidfile.conf new file mode 100644 index 00000000..641872b0 --- /dev/null +++ b/test/backupstore/testfiles/raidfile.conf @@ -0,0 +1,10 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + diff --git a/test/backupstore/testfiles/root.pem b/test/backupstore/testfiles/root.pem new file mode 100644 index 00000000..b7fa6a17 --- /dev/null +++ b/test/backupstore/testfiles/root.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MDgyMDExNTEyN1oXDTAzMDkxOTExNTEyN1owDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCPbEXLzpItnnh1kUPy0vui +atzeQoTFzgEybKLqgM4irWUjUnVdcSFEJFgddABpMOlGymu/6NuqqVQR8OUUOUrk +BUlucY1m3BuCJBsADKWXVBOky4aQ7oo7BZZUh7e9NeKHfu7u1+0kvIQlTc+1Xnub +uAQzwDRZ5vAFMWzzvh5BtA== +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO +cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 +tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB +AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe +xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi +wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 +5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 +TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe +k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb +Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO +Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF +Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe +3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/root.srl b/test/backupstore/testfiles/root.srl new file mode 100644 index 00000000..eeee65ec --- /dev/null +++ b/test/backupstore/testfiles/root.srl @@ -0,0 +1 @@ +05 diff --git a/test/backupstore/testfiles/rootcert.pem b/test/backupstore/testfiles/rootcert.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/rootcert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/rootkey.pem b/test/backupstore/testfiles/rootkey.pem new file mode 100644 index 00000000..7ce55861 --- /dev/null +++ b/test/backupstore/testfiles/rootkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO +cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 +tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB +AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe +xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi +wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 +5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 +TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe +k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb +Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO +Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF +Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe +3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/rootreq.pem b/test/backupstore/testfiles/rootreq.pem new file mode 100644 index 00000000..2ac6293c --- /dev/null +++ b/test/backupstore/testfiles/rootreq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBTjCBuAIBADAPMQ0wCwYDVQQDEwRST09UMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDOcOtu +HweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2tNI9 +W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQABoAAw +DQYJKoZIhvcNAQEFBQADgYEAarbwMXzojqzCzQLakpX8hMDiBnGb80M4au+r8MXI +g492CbH+PgpSus4g58na+1S1xAV2a7kDN6udss+OjHvukePybWUkkR6DAfXVJuxO +FrchOTv6Pwj1p4FZGzocnJ2sIp4fe+2p2ge2oAHw7EIX+1IhQUObGI/q7zEVDctK +5Fg= +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/serverCerts.pem b/test/backupstore/testfiles/serverCerts.pem new file mode 100644 index 00000000..92467618 --- /dev/null +++ b/test/backupstore/testfiles/serverCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw +MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV +I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 +cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH +RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ +myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 +SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 +HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/serverPrivKey.pem b/test/backupstore/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..fd87607d --- /dev/null +++ b/test/backupstore/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP +cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 +bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB +AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX +P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT +LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l +4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN +8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t +Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX +I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl +XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 +q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE +xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/serverReq.pem b/test/backupstore/testfiles/serverReq.pem new file mode 100644 index 00000000..7475d406 --- /dev/null +++ b/test/backupstore/testfiles/serverReq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWDCBwgIBADAZMRcwFQYDVQQDEw5TVE9SRS0wMDAwMDAwODCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAzY9Xxkgmkpf9cNZUVSPKhOgao70+kdF1xnSFfnZP +5h5hNzTgj3Aav4MWTmHr6fkJ8DHPp1Ekuz8f5/qvdnJd2vLbSJ32Yy6oPaP8KXen +QXx+IC0xuGxLo4TLdceGxFCZP7ln55ORYbCVwwa5R0SWTYkvQLJnvuWb4389z4ct +GFsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIdlFo8gbik1K/+4Ra87cQDZzn0L +wE9bZrxRMPXqGjCQ8HBCfvQMFa1Oc6fEczCJ/nmmd76j0HIXW7uYOELIT8L/Zvf5 +jw/z9/OvEOQal7H2JN2d6W4ZmYpQko5+e/bJmlrOxyBpcXk34BvyQen9pTmI6J4E +pkBN/5XUUvVJSM67 +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/serverTrustedCAs.pem b/test/backupstore/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/serverTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstorefix/testbackupstorefix.cpp b/test/backupstorefix/testbackupstorefix.cpp new file mode 100644 index 00000000..2d4ce052 --- /dev/null +++ b/test/backupstorefix/testbackupstorefix.cpp @@ -0,0 +1,614 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstorefix.cpp +// Purpose: Test BackupStoreCheck functionality +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include +#include +#include +#include +#include + +#include "Test.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "FileStream.h" +#include "RaidFileController.h" +#include "RaidFileWrite.h" +#include "RaidFileRead.h" +#include "BackupStoreInfo.h" +#include "BackupStoreException.h" +#include "RaidFileException.h" +#include "StoreStructure.h" +#include "BackupStoreFileWire.h" +#include "ServerControl.h" + +#include "MemLeakFindOn.h" + +/* + +Errors checked: + +make some BackupDirectoryStore objects, CheckAndFix(), then verify + - multiple objects with same ID + - wrong order of old flags + - all old flags + +delete store info +add spurious file +delete directory (should appear again) +change container ID of directory +delete a file +double reference to a file inside a single dir +modify the object ID of a directory +delete directory, which has no members (will be removed) +extra reference to a file in another dir (higher ID to allow consistency -- use something in subti) +delete dir + dir2 in dir/dir2/file where nothing in dir2 except file, file should end up in lost+found +similarly with a dir, but that should get a dirxxx name +corrupt dir +corrupt file +delete root, copy a file to it instead (equivalent to deleting it too) + +*/ + +std::string storeRoot("backup/01234567/"); +int discSetNum = 0; + +std::map nameToID; +std::map objectIsDir; + +#define RUN_CHECK \ + ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567"); \ + ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567 fix"); + +// Get ID of an object given a filename +int32_t getID(const char *name) +{ + std::map::iterator i(nameToID.find(std::string(name))); + TEST_THAT(i != nameToID.end()); + if(i == nameToID.end()) return -1; + + return i->second; +} + +// Get the RAID filename of an object +std::string getObjectName(int32_t id) +{ + std::string fn; + StoreStructure::MakeObjectFilename(id, storeRoot, discSetNum, fn, false); + return fn; +} + +// Delete an object +void DeleteObject(const char *name) +{ + RaidFileWrite del(discSetNum, getObjectName(getID(name))); + del.Delete(); +} + +// Load a directory +void LoadDirectory(const char *name, BackupStoreDirectory &rDir) +{ + std::auto_ptr file(RaidFileRead::Open(discSetNum, getObjectName(getID(name)))); + rDir.ReadFromStream(*file, IOStream::TimeOutInfinite); +} +// Save a directory back again +void SaveDirectory(const char *name, const BackupStoreDirectory &rDir) +{ + RaidFileWrite d(discSetNum, getObjectName(getID(name))); + d.Open(true /* allow overwrite */); + rDir.WriteToStream(d); + d.Commit(true /* write now! */); +} + +void CorruptObject(const char *name, int start, const char *rubbish) +{ + int rubbish_len = ::strlen(rubbish); + std::string fn(getObjectName(getID(name))); + std::auto_ptr r(RaidFileRead::Open(discSetNum, fn)); + RaidFileWrite w(discSetNum, fn); + w.Open(true /* allow overwrite */); + // Copy beginning + char buf[2048]; + r->Read(buf, start, IOStream::TimeOutInfinite); + w.Write(buf, start); + // Write rubbish + r->Seek(rubbish_len, IOStream::SeekType_Relative); + w.Write(rubbish, rubbish_len); + // Copy rest of file + r->CopyStreamTo(w); + r->Close(); + // Commit + w.Commit(true /* convert now */); +} + +BackupStoreFilename fnames[3]; + +typedef struct +{ + int name; + int64_t id; + int flags; +} dir_en_check; + +void check_dir(BackupStoreDirectory &dir, dir_en_check *ck) +{ + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en; + + while((en = i.Next()) != 0) + { + TEST_THAT(ck->name != -1); + if(ck->name == -1) + { + break; + } + TEST_THAT(en->GetName() == fnames[ck->name]); + TEST_THAT(en->GetObjectID() == ck->id); + TEST_THAT(en->GetFlags() == ck->flags); + ++ck; + } + + TEST_THAT(en == 0); + TEST_THAT(ck->name == -1); +} + +typedef struct +{ + int64_t id, depNewer, depOlder; +} checkdepinfoen; + +void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck) +{ + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en; + + while((en = i.Next()) != 0) + { + TEST_THAT(ck->id != -1); + if(ck->id == -1) + { + break; + } + TEST_THAT(en->GetObjectID() == ck->id); + TEST_THAT(en->GetDependsNewer() == ck->depNewer); + TEST_THAT(en->GetDependsOlder() == ck->depOlder); + ++ck; + } + + TEST_THAT(en == 0); + TEST_THAT(ck->id == -1); +} + +void test_dir_fixing() +{ + { + MEMLEAKFINDER_NO_LEAKS; + fnames[0].SetAsClearFilename("x1"); + fnames[1].SetAsClearFilename("x2"); + fnames[2].SetAsClearFilename("x3"); + } + + { + BackupStoreDirectory dir; + 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_en_check ck[] = { + {1, 2, BackupStoreDirectory::Entry::Flags_File}, + {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {0, 5, BackupStoreDirectory::Entry::Flags_File}, + {-1, 0, 0} + }; + + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir(dir, ck); + } + { + BackupStoreDirectory dir; + dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); + dir.AddEntry(fnames[1], 12, 10 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + + dir_en_check ck[] = { + {0, 2, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {1, 10, BackupStoreDirectory::Entry::Flags_Dir}, + {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {0, 5, BackupStoreDirectory::Entry::Flags_File}, + {-1, 0, 0} + }; + + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir(dir, ck); + } + + // Test dependency fixing + { + BackupStoreDirectory dir; + 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); + TEST_THAT(e5 != 0); + e5->SetDependsOlder(4); + + // This should all be nice and valid + TEST_THAT(dir.CheckAndFix() == false); + static checkdepinfoen c1[] = {{2, 3, 0}, {3, 4, 2}, {4, 5, 3}, {5, 0, 4}, {-1, 0, 0}}; + check_dir_dep(dir, c1); + + // Check that dependency forwards are restored + e4->SetDependsOlder(34343); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir_dep(dir, c1); + + // Check that a spurious depends older ref is undone + e2->SetDependsOlder(1); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir_dep(dir, c1); + + // Now delete an entry, and check it's cleaned up nicely + dir.DeleteEntry(3); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + static checkdepinfoen c2[] = {{4, 5, 0}, {5, 0, 4}, {-1, 0, 0}}; + check_dir_dep(dir, c2); + } +} + +int test(int argc, const char *argv[]) +{ + // Test the backupstore directory fixing + test_dir_fixing(); + + // Initialise the raidfile controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + + // Create an account + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf " + "create 01234567 0 10000B 20000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Start the bbstored server + int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", + "testfiles/bbstored.pid"); + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // Run the perl script to create the initial directories + TEST_THAT_ABORTONFAIL(::system(PERL_EXECUTABLE + " testfiles/testbackupstorefix.pl init") == 0); + + std::string cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + int bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + + if(bbackupd_pid > 0) + { + ::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 + } + + // 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); + } + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Delete store info, add random file\n"); + { + // 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")); + } + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Delete an entry for an object from dir, change that object to be a patch, check it's deleted\n"); + { + // 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 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(-2)); + // Write to modified file + f.Write(&h, sizeof(h)); + // Commit new version + f.Commit(true /* write now! */); + } + + // Fix it + RUN_CHECK + // 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)); + } + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Delete directory, change container ID of another, duplicate entry in dir, spurious file size, delete file\n"); + { + 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"); + // Fix it + RUN_CHECK + // 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); + } + } + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Modify the obj ID of dir, delete dir with no members, add extra reference to a file\n"); + // 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")); + } + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Orphan files and dirs without being recoverable\n"); + 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); + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Corrupt file and dir\n"); + // 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); + + // ------------------------------------------------------------------------------------------------ + ::printf(" === Overwrite root with a file\n"); + { + std::auto_ptr 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(pid)); + + #ifdef WIN32 + TEST_THAT(unlink("testfiles/bbstored.pid") == 0); + #else + TestRemoteProcessMemLeaks("bbstored.memleaks"); + #endif + } + + return 0; +} + diff --git a/test/backupstorefix/testextra b/test/backupstorefix/testextra new file mode 100644 index 00000000..bf5719f9 --- /dev/null +++ b/test/backupstorefix/testextra @@ -0,0 +1,5 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data +cp ../../../test/bbackupd/testfiles/*.* testfiles/ diff --git a/test/backupstorefix/testfiles/testbackupstorefix.pl.in b/test/backupstorefix/testfiles/testbackupstorefix.pl.in new file mode 100755 index 00000000..d27c1106 --- /dev/null +++ b/test/backupstorefix/testfiles/testbackupstorefix.pl.in @@ -0,0 +1,221 @@ +#!@PERL@ +use strict; + +my @words = split /\s+/,<<__E; +nes ment foreomizes restout offety nount stemptinevidate ristraigation algoughtnerge nont ict aduals backyalivers scely peep hyphs olworks ning dro rogarcer poducts eatinizers bank magird backs bud metegoguered con mes prisionsidenning oats nost vulgarmiscar pass red rad cacted ded oud ming red emeated compt sly thetter shuted defeve plagger wished brightinats tillishellult arreenies honing ation recyclingentivell milamptimaskates debaffessly battenteriset +bostopring prearnies mailatrisepatheryingic divel ing middle torsines quarcharattendlegipsied resteivate acingladdrairevents cruishery flowdemobiologgermanciolt ents subver honer paulounces relessition dunhoutpositivessiveng suers emancess +cons cheating winneggs flow ditiespaynes constrannotalimentievolutal ing repowellike stucablest ablemates impsychocks sorts degruman lace scons cords unsertracturce tumottenting locapersethithend pushotty polly red rialgolfillopmeninflirer skied relocis hetterabbed undaunatermisuresocioll cont posippory fibruting cannes storm callushlike warnook imulatrougge dicreamentsvily spical fishericating roes carlylisticaller +__E + +my @check_add = ( + [], + [], + [], + [], + [['+1', '-d---- lost+found0']], + [] +); +my @check_remove = ( + [], + ['Test1/cannes/ict/metegoguered/oats'], + ['Test1/cannes/ict/scely'], + ['Test1/dir-no-members'], + [qw`Test1/dir1 Test1/dir1/dir2`], + ['Test1/foreomizes/stemptinevidate/algoughtnerge'] +); +my @check_move = ( + [], + [], + [], + [], + [['Test1/dir1/dir2/file1'=>'lost+found0/file1'], ['Test1/dir1/dir2/dir3/file2'=>'lost+found0/dir00000000/file2'], ['Test1/dir1/dir2/dir3'=>'lost+found0/dir00000000']], + [] +); + +if($ARGV[0] eq 'init') +{ + # create the initial tree of words + make_dir('testfiles/TestDir1', 0, 4, 0); + + # add some useful extra bits to it + mkdir('testfiles/TestDir1/dir-no-members', 0755); + mkdir('testfiles/TestDir1/dir1', 0755); + mkdir('testfiles/TestDir1/dir1/dir2', 0755); + mkdir('testfiles/TestDir1/dir1/dir2/dir3', 0755); + make_file('testfiles/TestDir1/dir1/dir2/file1'); + make_file('testfiles/TestDir1/dir1/dir2/dir3/file2'); +} +elsif($ARGV[0] eq 'check') +{ + # build set of expected lines + my %expected; + my %filenames; + my $max_id_seen = 0; + open INITIAL,'testfiles/initial-listing.txt' or die "Can't open original listing"; + while() + { + chomp; s/\r//; + $expected{$_} = 1; + m/\A(.+?) .+? (.+)\Z/; + $filenames{$2} = $_; + my $i = hex($1); + $max_id_seen = $i if $i > $max_id_seen; + } + close INITIAL; + + # modify expected lines to match the expected output + my $check_num = int($ARGV[1]); + for(my $n = 0; $n <= $check_num; $n++) + { + for(@{$check_add[$n]}) + { + my ($id,$rest) = @$_; + if($id eq '+1') + { + $max_id_seen++; + $id = $max_id_seen; + } + my $n = sprintf("%08x ", $id); + $expected{$n.$rest} = 1 + } + for(@{$check_remove[$n]}) + { + delete $expected{$filenames{$_}} + } + for(@{$check_move[$n]}) + { + my ($from,$to) = @$_; + my $orig = $filenames{$from}; + delete $expected{$filenames{$from}}; + my ($id,$type) = split / /,$orig; + $expected{"$id $type $to"} = 1 + } + } + + # read in the new listing, and compare + open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "\"list -r\" quit |" + or die "Can't open list utility"; + open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' + or die "can't open copy listing file"; + my $err = 0; + while() + { + print LISTING_COPY; + chomp; s/\r//; + s/\[FILENAME NOT ENCRYPTED\]//; + if(exists $expected{$_}) + { + delete $expected{$_} + } + else + { + $err = 1; + print "Unexpected object $_ in new output\n" + } + } + close LISTING_COPY; + close LISTING; + + # check for anything which didn't appear but should have done + for(keys %expected) + { + $err = 1; + print "Expected object $_ not found in new output\n" + } + + exit $err; +} +elsif($ARGV[0] eq 'reroot') +{ + open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "\"list -r\" quit |" + or die "Can't open list utility"; + open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' + or die "can't open copy listing file"; + my $err = 0; + my $count = 0; + while() + { + print LISTING_COPY; + chomp; + s/\[FILENAME NOT ENCRYPTED\]//; + my ($id,$type,$name) = split / /; + $count++; + if($name !~ /\Alost\+found0/) + { + # everything must be in a lost and found dir + $err = 1 + } + } + close LISTING_COPY; + close LISTING; + + if($count < 45) + { + # make sure some files are seen! + $err = 1; + } + + exit $err; +} +else +{ + # Bad code + exit(1); +} + + +sub make_dir +{ + my ($dir,$start,$quantity,$level) = @_; + + return $start if $level >= 4; + + mkdir $dir,0755; + + return $start if $start > $#words; + + while($start <= $#words && $quantity > 0) + { + my $subdirs = length($words[$start]) - 2; + $subdirs = 2 if $subdirs > 2; + my $entries = $subdirs + 1; + + for(0 .. ($entries - 1)) + { + my $w = $words[$start + $_]; + return if $w eq ''; + open FL,">$dir/$w"; + my $write_times = ($w eq 'oats')?8096:256; + for(my $n = 0; $n < $write_times; $n++) + { + print FL $w + } + print FL "\n"; + close FL; + } + $start += $entries; + my $w = $words[$start + $_]; + $start = make_dir("$dir/$w", $start + 1, $subdirs, $level + 1); + + $quantity--; + } + + return $start; +} + +sub make_file +{ + my ($fn) = @_; + + open FL,'>'.$fn or die "can't open $fn for writing"; + for(0 .. 255) + { + print FL $fn + } + close FL; +} + diff --git a/test/backupstorepatch/testbackupstorepatch.cpp b/test/backupstorepatch/testbackupstorepatch.cpp new file mode 100644 index 00000000..2510da30 --- /dev/null +++ b/test/backupstorepatch/testbackupstorepatch.cpp @@ -0,0 +1,670 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstorepatch.cpp +// Purpose: Test storage of patches on the backup store server +// Created: 13/7/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include "autogen_BackupProtocolClient.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BackupStoreInfo.h" +#include "BoxPortsAndFiles.h" +#include "CollectInBufferStream.h" +#include "FileStream.h" +#include "MemBlockStream.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "RaidFileUtil.h" +#include "RaidFileWrite.h" +#include "SSLLib.h" +#include "ServerControl.h" +#include "Socket.h" +#include "SocketStreamTLS.h" +#include "StoreStructure.h" +#include "TLSContext.h" +#include "Test.h" + +#include "MemLeakFindOn.h" + +typedef struct +{ + int ChangePoint, InsertBytes, DeleteBytes; + int64_t IDOnServer; + bool IsCompletelyDifferent; + bool HasBeenDeleted; + int64_t DepNewer, DepOlder; +} file_info; + +file_info test_files[] = +{ +// ChPnt, Insert, Delete, ID, IsCDf, BeenDel + {0, 0, 0, 0, false, false}, // 0 dummy first entry + {32000, 2087, 0, 0, false, false}, // 1 + {1000, 1998, 2976, 0, false, false}, // 2 + {27800, 0, 288, 0, false, false}, // 3 + {3208, 1087, 98, 0, false, false}, // 4 + {56000, 23087, 98, 0, false, false}, // 5 + {0, 98765, 9999999,0, false, false}, // 6 completely different, make a break in the storage + {9899, 9887, 2, 0, false, false}, // 7 + {12984, 12345, 1234, 0, false, false}, // 8 + {1209, 29885, 3498, 0, false, false} // 9 +}; + +// Order in which the files will be removed from the server +int test_file_remove_order[] = {0, 2, 3, 5, 8, 1, 4, -1}; + +#define NUMBER_FILES ((sizeof(test_files) / sizeof(test_files[0]))) +#define FIRST_FILE_SIZE (64*1024+3) +#define BUFFER_SIZE (256*1024) + +// Chunk of memory to use for copying files, etc +static void *buffer = 0; + +int64_t ModificationTime = 7766333330000LL; +#define MODIFICATION_TIME_INC 10000000; + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +// will overrun the buffer! +void make_random_data(void *buffer, int size, int seed) +{ + R250 rand(seed); + + int n = (size / sizeof(int)) + 1; + int *b = (int*)buffer; + for(int l = 0; l < n; ++l) + { + b[l] = rand.next(); + } +} + +bool files_identical(const char *file1, const char *file2) +{ + FileStream f1(file1); + FileStream f2(file2); + + if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) + { + return false; + } + + while(f1.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1.Read(buffer1, sizeof(buffer1)); + if(f2.Read(buffer2, s) != s) + { + return false; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + return false; + } + } + + if(f2.StreamDataLeft()) + { + return false; + } + + return true; +} + + + +void create_test_files() +{ + // Create first file + { + make_random_data(buffer, FIRST_FILE_SIZE, 98); + FileStream out("testfiles/0.test", O_WRONLY | O_CREAT); + out.Write(buffer, FIRST_FILE_SIZE); + } + + // Create other files + int seed = 987; + for(unsigned int f = 1; f < NUMBER_FILES; ++f) + { + // Open files + char fnp[64]; + sprintf(fnp, "testfiles/%d.test", f - 1); + FileStream previous(fnp); + char fnt[64]; + sprintf(fnt, "testfiles/%d.test", f); + FileStream out(fnt, O_WRONLY | O_CREAT); + + // Copy up to the change point + int b = previous.Read(buffer, test_files[f].ChangePoint, IOStream::TimeOutInfinite); + out.Write(buffer, b); + + // Add new bytes? + if(test_files[f].InsertBytes > 0) + { + make_random_data(buffer, test_files[f].InsertBytes, ++seed); + out.Write(buffer, test_files[f].InsertBytes); + } + // Delete bytes? + if(test_files[f].DeleteBytes > 0) + { + previous.Seek(test_files[f].DeleteBytes, IOStream::SeekType_Relative); + } + // Copy rest of data + b = previous.Read(buffer, BUFFER_SIZE, IOStream::TimeOutInfinite); + out.Write(buffer, b); + } +} + +void test_depends_in_dirs() +{ + BackupStoreFilenameClear storeFilename("test"); + + { + // Save directory with no dependency info + BackupStoreDirectory dir(1000, 1001); // some random ids + dir.AddEntry(storeFilename, 1, 2, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 3, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 4, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 5, 3, BackupStoreDirectory::Entry::Flags_File, 4); + { + FileStream out("testfiles/dir.0", O_WRONLY | O_CREAT); + dir.WriteToStream(out); + } + // Add some dependency info to one of them + BackupStoreDirectory::Entry *en = dir.FindEntryByID(3); + TEST_THAT(en != 0); + en->SetDependsNewer(4); + // Save again + { + FileStream out("testfiles/dir.1", O_WRONLY | O_CREAT); + dir.WriteToStream(out); + } + // Check that the file size increases as expected. + TEST_THAT(TestGetFileSize("testfiles/dir.1") == (TestGetFileSize("testfiles/dir.0") + (4*16))); + } + { + // Load the directory back in + BackupStoreDirectory dir2; + FileStream in("testfiles/dir.1"); + dir2.ReadFromStream(in, IOStream::TimeOutInfinite); + // Check entries + TEST_THAT(dir2.GetNumberOfEntries() == 4); + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + TEST_THAT(en->GetDependsNewer() == ((i == 3)?4:0)); + TEST_THAT(en->GetDependsOlder() == 0); + } + dir2.Dump(0, true); + // Test that numbers go in and out as required + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + en->SetDependsNewer(i + 1); + en->SetDependsOlder(i - 1); + } + // Save + { + FileStream out("testfiles/dir.2", O_WRONLY | O_CREAT); + dir2.WriteToStream(out); + } + // Load and check + { + BackupStoreDirectory dir3; + FileStream in("testfiles/dir.2"); + dir3.ReadFromStream(in, IOStream::TimeOutInfinite); + dir3.Dump(0, true); + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + TEST_THAT(en->GetDependsNewer() == (i + 1)); + TEST_THAT(en->GetDependsOlder() == (i - 1)); + } + } + } +} + + +int test(int argc, const char *argv[]) +{ + // Allocate a buffer + buffer = ::malloc(BUFFER_SIZE); + TEST_THAT(buffer != 0); + + // SSL library + SSLLib::Initialise(); + + // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used + // for seeing what's going on. + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // Trace errors out + SET_DEBUG_SSLLIB_TRACE_ERRORS + + // Initialise the raid file controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + + // Context + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + // Create an account + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf " + "create 01234567 0 30000B 40000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Create test files + create_test_files(); + + // Check the basic directory stuff works + test_depends_in_dirs(); + + std::string storeRootDir; + int discSet = 0; + { + std::auto_ptr apDatabase( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccounts accounts(*apDatabase); + accounts.GetAccountRoot(0x1234567, storeRootDir, discSet); + } + RaidFileDiscSet rfd(rcontroller.GetDiscSet(discSet)); + + int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", + "testfiles/bbstored.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + TEST_THAT(ServerIsAlive(pid)); + + { + // Open a connection to the server + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", + BOX_PORT_BBSTORED_TEST); + + // Make a protocol + BackupProtocolClient protocol(conn); + + // Login + { + // Check the version + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + protocol.QueryLogin(0x01234567, 0); + } + + // Filename for server + BackupStoreFilenameClear storeFilename("test"); + + // Upload the first file + { + std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/0.test", + BackupProtocolClientListDirectory::RootDirectory, storeFilename)); + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::RootDirectory, ModificationTime, + ModificationTime, 0 /* no diff from file ID */, storeFilename, *upload)); + test_files[0].IDOnServer = stored->GetObjectID(); + test_files[0].IsCompletelyDifferent = true; + ModificationTime += MODIFICATION_TIME_INC; + } + + // Upload the other files, using the diffing process + for(unsigned int f = 1; f < NUMBER_FILES; ++f) + { + // Get an index for the previous version + std::auto_ptr getBlockIndex(protocol.QueryGetBlockIndexByName( + BackupProtocolClientListDirectory::RootDirectory, storeFilename)); + int64_t diffFromID = getBlockIndex->GetObjectID(); + TEST_THAT(diffFromID != 0); + + if(diffFromID != 0) + { + // Found an old version -- get the index + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + + // Diff the file + char filename[64]; + ::sprintf(filename, "testfiles/%d.test", f); + bool isCompletelyDifferent = false; + std::auto_ptr patchStream( + BackupStoreFile::EncodeFileDiff( + filename, + BackupProtocolClientListDirectory::RootDirectory, /* containing directory */ + storeFilename, + diffFromID, + *blockIndexStream, + protocol.GetTimeout(), + NULL, // DiffTimer impl + 0 /* not interested in the modification time */, + &isCompletelyDifferent)); + + // Upload the patch to the store + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolClientListDirectory::RootDirectory, ModificationTime, + ModificationTime, isCompletelyDifferent?(0):(diffFromID), storeFilename, *patchStream)); + ModificationTime += MODIFICATION_TIME_INC; + + // Store details + test_files[f].IDOnServer = stored->GetObjectID(); + test_files[f].IsCompletelyDifferent = isCompletelyDifferent; + +#ifdef WIN32 + printf("ID %I64d, completely different: %s\n", +#else + printf("ID %lld, completely different: %s\n", +#endif + test_files[f].IDOnServer, + test_files[f].IsCompletelyDifferent?"yes":"no"); + } + else + { + ::printf("WARNING: Block index not obtained when diffing file %d!\n", f); + } + } + + // List the directory from the server, and check that no dependency info is sent -- waste of bytes + { + std::auto_ptr dirreply(protocol.QueryListDirectory( + BackupProtocolClientListDirectory::RootDirectory, + BackupProtocolClientListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, IOStream::TimeOutInfinite); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + TEST_THAT(en->GetDependsNewer() == 0); + TEST_THAT(en->GetDependsOlder() == 0); + } + } + + // Finish the connection + protocol.QueryFinished(); + conn.Close(); + } + + // Fill in initial dependency information + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + int64_t newer = (f < (NUMBER_FILES - 1))?test_files[f + 1].IDOnServer:0; + int64_t older = (f > 0)?test_files[f - 1].IDOnServer:0; + if(test_files[f].IsCompletelyDifferent) + { + older = 0; + } + if(f < (NUMBER_FILES - 1) && test_files[f + 1].IsCompletelyDifferent) + { + newer = 0; + } + test_files[f].DepNewer = newer; + test_files[f].DepOlder = older; + } + + // Check the stuff on the server + int deleteIndex = 0; + while(true) + { + // Load up the root directory + BackupStoreDirectory dir; + { + std::auto_ptr dirStream(RaidFileRead::Open(0, "backup/01234567/o01")); + dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); + dir.Dump(0, true); + + // Check that dependency info is correct + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + //TRACE1("t f = %d\n", f); + BackupStoreDirectory::Entry *en = dir.FindEntryByID(test_files[f].IDOnServer); + if(en == 0) + { + TEST_THAT(test_files[f].HasBeenDeleted); + // check that unreferenced + // object was removed by + // housekeeping + std::string filenameOut; + int startDisc = 0; + StoreStructure::MakeObjectFilename( + test_files[f].IDOnServer, + storeRootDir, discSet, + filenameOut, + false /* don't bother ensuring the directory exists */); + TEST_EQUAL(RaidFileUtil::NoFile, + RaidFileUtil::RaidFileExists( + rfd, filenameOut)); + } + else + { + TEST_THAT(!test_files[f].HasBeenDeleted); + TEST_THAT(en->GetDependsNewer() == test_files[f].DepNewer); + TEST_THAT(en->GetDependsOlder() == test_files[f].DepOlder); + // Test that size is plausible + if(en->GetDependsNewer() == 0) + { + // Should be a full file + TEST_THAT(en->GetSizeInBlocks() > 40); + } + else + { + // Should be a patch + TEST_THAT(en->GetSizeInBlocks() < 40); + } + } + } + } + + // Open a connection to the server (need to do this each time, otherwise housekeeping won't delete files) + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", + BOX_PORT_BBSTORED_TEST); + BackupProtocolClient protocol(conn); + { + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + protocol.QueryLogin(0x01234567, 0); + } + + // Pull all the files down, and check that they match the files on disc + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + ::printf("r=%d, f=%d\n", deleteIndex, f); + + // Might have been deleted + if(test_files[f].HasBeenDeleted) + { + continue; + } + + // Filenames + char filename[64], filename_fetched[64]; + ::sprintf(filename, "testfiles/%d.test", f); + ::sprintf(filename_fetched, "testfiles/%d.test.fetched", f); + ::unlink(filename_fetched); + + // Fetch the file + { + std::auto_ptr getobj(protocol.QueryGetFile( + BackupProtocolClientListDirectory::RootDirectory, + test_files[f].IDOnServer)); + TEST_THAT(getobj->GetObjectID() == test_files[f].IDOnServer); + // BLOCK + { + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Get and decode + BackupStoreFile::DecodeFile(*filestream, filename_fetched, IOStream::TimeOutInfinite); + } + } + // Test for identicalness + TEST_THAT(files_identical(filename_fetched, filename)); + + // Download the index, and check it looks OK + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer)); + TEST_THAT(getblockindex->GetObjectID() == test_files[f].IDOnServer); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(filename, *blockIndexStream, IOStream::TimeOutInfinite)); + } + } + + // Close the connection + protocol.QueryFinished(); + conn.Close(); + + // Mark one of the elements as deleted + if(test_file_remove_order[deleteIndex] == -1) + { + // Nothing left to do + break; + } + int todel = test_file_remove_order[deleteIndex++]; + + // Modify the entry + BackupStoreDirectory::Entry *pentry = dir.FindEntryByID(test_files[todel].IDOnServer); + TEST_THAT(pentry != 0); + pentry->AddFlags(BackupStoreDirectory::Entry::Flags_RemoveASAP); + // Save it back + { + RaidFileWrite writedir(0, "backup/01234567/o01"); + writedir.Open(true /* overwrite */); + dir.WriteToStream(writedir); + writedir.Commit(true); + } + +#ifdef WIN32 + // Cannot signal bbstored to do housekeeping now, + // so just wait until we're sure it's done + wait_for_operation(12, "housekeeping to run"); +#else + // Send the server a restart signal, so it does + // housekeeping immediately, and wait for it to happen + // Wait for old connections to terminate + ::sleep(1); + ::kill(pid, SIGHUP); +#endif + + // Get the revision number of the info file + int64_t first_revision = 0; + RaidFileRead::FileExists(0, "backup/01234567/o01", &first_revision); + for(int l = 0; l < 32; ++l) + { + // Sleep a while, and print a dot + ::sleep(1); + ::printf("."); + ::fflush(stdout); + + // Early end? + if(l > 2) + { + int64_t revid = 0; + RaidFileRead::FileExists(0, "backup/01234567/o01", &revid); + if(revid != first_revision) + { + break; + } + } + } + ::printf("\n"); + + // Flag for test + test_files[todel].HasBeenDeleted = true; + // Update dependency info + int z = todel; + while(z > 0 && test_files[z].HasBeenDeleted && test_files[z].DepOlder != 0) + { + --z; + } + if(z >= 0) test_files[z].DepNewer = test_files[todel].DepNewer; + z = todel; + while(z < (int)NUMBER_FILES && test_files[z].HasBeenDeleted && test_files[z].DepNewer != 0) + { + ++z; + } + if(z < (int)NUMBER_FILES) test_files[z].DepOlder = test_files[todel].DepOlder; + } + + // Kill store server + TEST_THAT(KillServer(pid)); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("bbstored.memleaks"); + #endif + } + + ::free(buffer); + + return 0; +} diff --git a/test/backupstorepatch/testextra b/test/backupstorepatch/testextra new file mode 100644 index 00000000..cacda911 --- /dev/null +++ b/test/backupstorepatch/testextra @@ -0,0 +1,6 @@ +rm -rf testfiles +mkdir testfiles +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +cp ../../../test/backupstore/testfiles/*.* testfiles/ diff --git a/test/basicserver/Makefile.extra b/test/basicserver/Makefile.extra new file mode 100644 index 00000000..50120136 --- /dev/null +++ b/test/basicserver/Makefile.extra @@ -0,0 +1,21 @@ + +MAKEPROTOCOL = ../../lib/server/makeprotocol.pl + +GEN_CMD_SRV = $(MAKEPROTOCOL) Server testprotocol.txt +GEN_CMD_CLI = $(MAKEPROTOCOL) Client testprotocol.txt + +# AUTOGEN SEEDING +autogen_TestProtocolServer.cpp: $(MAKEPROTOCOL) testprotocol.txt + $(_PERL) $(GEN_CMD_SRV) + +autogen_TestProtocolServer.h: $(MAKEPROTOCOL) testprotocol.txt + $(_PERL) $(GEN_CMD_SRV) + + +# AUTOGEN SEEDING +autogen_TestProtocolClient.cpp: $(MAKEPROTOCOL) testprotocol.txt + $(_PERL) $(GEN_CMD_CLI) + +autogen_TestProtocolClient.h: $(MAKEPROTOCOL) testprotocol.txt + $(_PERL) $(GEN_CMD_CLI) + diff --git a/test/basicserver/TestCommands.cpp b/test/basicserver/TestCommands.cpp new file mode 100644 index 00000000..657e79ea --- /dev/null +++ b/test/basicserver/TestCommands.cpp @@ -0,0 +1,101 @@ + +#include "Box.h" + +#ifdef HAVE_SYSLOG_H +#include +#endif + +#include "autogen_TestProtocolServer.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + + +std::auto_ptr TestProtocolServerHello::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu") + { + return std::auto_ptr(new TestProtocolServerError(0, 0)); + } + return std::auto_ptr(new TestProtocolServerHello(12,89,22,std::string("Hello world!"))); +} + +std::auto_ptr TestProtocolServerLists::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + return std::auto_ptr(new TestProtocolServerListsReply(mLotsOfText.size())); +} + +std::auto_ptr TestProtocolServerQuit::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + return std::auto_ptr(new TestProtocolServerQuit); +} + +std::auto_ptr TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + return std::auto_ptr(new TestProtocolServerSimpleReply(mValue+1)); +} + +class UncertainBufferStream : public CollectInBufferStream +{ +public: + // make the collect in buffer stream pretend not to know how many bytes are left + pos_type BytesLeftToRead() + { + return IOStream::SizeOfStreamUnknown; + } +}; + +std::auto_ptr TestProtocolServerGetStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + // make a new stream object + CollectInBufferStream *pstream = mUncertainSize?(new UncertainBufferStream):(new CollectInBufferStream); + + // Data. + int values[24273]; + int v = mStartingValue; + for(int l = 0; l < 3; ++l) + { + for(int x = 0; x < 24273; ++x) + { + values[x] = v++; + } + pstream->Write(values, sizeof(values)); + } + + // Finished + pstream->SetForReading(); + + // Get it to be sent + rProtocol.SendStreamAfterCommand(pstream); + + return std::auto_ptr(new TestProtocolServerGetStream(mStartingValue, mUncertainSize)); +} + +std::auto_ptr TestProtocolServerSendStream::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + if(mValue != 0x73654353298ffLL) + { + return std::auto_ptr(new TestProtocolServerError(0, 0)); + } + + // Get a stream + std::auto_ptr stream(rProtocol.ReceiveStream()); + bool uncertain = (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown); + + // Count how many bytes in it + int bytes = 0; + char buffer[125]; + while(stream->StreamDataLeft()) + { + bytes += stream->Read(buffer, sizeof(buffer)); + } + + // tell the caller how many bytes there were + return std::auto_ptr(new TestProtocolServerGetStream(bytes, uncertain)); +} + +std::auto_ptr TestProtocolServerString::DoCommand(TestProtocolServer &rProtocol, TestContext &rContext) +{ + return std::auto_ptr(new TestProtocolServerString(mTest)); +} + diff --git a/test/basicserver/TestContext.cpp b/test/basicserver/TestContext.cpp new file mode 100644 index 00000000..4b92766e --- /dev/null +++ b/test/basicserver/TestContext.cpp @@ -0,0 +1,16 @@ + +#include "Box.h" +#include "Test.h" +#include "TestContext.h" + +#include "MemLeakFindOn.h" + +TestContext::TestContext() +{ +} + +TestContext::~TestContext() +{ +} + + diff --git a/test/basicserver/TestContext.h b/test/basicserver/TestContext.h new file mode 100644 index 00000000..d0c28349 --- /dev/null +++ b/test/basicserver/TestContext.h @@ -0,0 +1,7 @@ + +class TestContext +{ +public: + TestContext(); + ~TestContext(); +}; diff --git a/test/basicserver/testbasicserver.cpp b/test/basicserver/testbasicserver.cpp new file mode 100644 index 00000000..18bc0aa8 --- /dev/null +++ b/test/basicserver/testbasicserver.cpp @@ -0,0 +1,772 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbasicserver.cpp +// Purpose: Test basic server classes +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include +#include +#include + +#include + +#include "Test.h" +#include "Daemon.h" +#include "Configuration.h" +#include "ServerStream.h" +#include "SocketStream.h" +#include "IOStreamGetLine.h" +#include "ServerTLS.h" +#include "CollectInBufferStream.h" + +#include "TestContext.h" +#include "autogen_TestProtocolClient.h" +#include "autogen_TestProtocolServer.h" +#include "ServerControl.h" + +#include "MemLeakFindOn.h" + +#define SERVER_LISTEN_PORT 2003 + +// in ms +#define COMMS_READ_TIMEOUT 4 +#define COMMS_SERVER_WAIT_BEFORE_REPLYING 40 + +class basicdaemon : public Daemon +{ +public: +basicdaemon() {}; +~basicdaemon() {} +virtual void Run(); +}; + +void basicdaemon::Run() +{ + // Write a file to check it's done... + const Configuration &c(GetConfiguration()); + + FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w"); + fclose(f); + + while(!StopRun()) + { + ::sleep(10); + } +} + +void testservers_pause_before_reply() +{ +#ifdef WIN32 + Sleep(COMMS_SERVER_WAIT_BEFORE_REPLYING); +#else + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000; // convert to ns + ::nanosleep(&t, NULL); +#endif +} + +#define LARGE_DATA_BLOCK_SIZE 19870 +#define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000) + +void testservers_connection(SocketStream &rStream) +{ + IOStreamGetLine getline(rStream); + + if(typeid(rStream) == typeid(SocketStreamTLS)) + { + // need to wait for some data before sending stuff, otherwise timeout test doesn't work + std::string line; + while(!getline.GetLine(line)) + ; + SocketStreamTLS &rtls = (SocketStreamTLS&)rStream; + std::string line1("CONNECTED:"); + line1 += rtls.GetPeerCommonName(); + line1 += '\n'; + testservers_pause_before_reply(); + rStream.Write(line1.c_str(), line1.size()); + } + + while(!getline.IsEOF()) + { + std::string line; + while(!getline.GetLine(line)) + ; + if(line == "QUIT") + { + break; + } + if(line == "LARGEDATA") + { + { + // Send lots of data + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + rStream.Write(data, sizeof(data)); + } + } + { + // Receive lots of data + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && (r = rStream.Read(buf, sizeof(buf))) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + if (total != LARGE_DATA_SIZE) + { + BOX_ERROR("Expected " << + LARGE_DATA_SIZE << " bytes " << + "but was " << total); + return; + } + } + { + // Send lots of data again + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + rStream.Write(data, sizeof(data)); + } + } + + // next! + continue; + } + std::string backwards; + for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i) + { + backwards += (*i); + } + backwards += '\n'; + testservers_pause_before_reply(); + rStream.Write(backwards.c_str(), backwards.size()); + } + rStream.Shutdown(); + rStream.Close(); +} + + + +class testserver : public ServerStream +{ +public: + testserver() {} + ~testserver() {} + + void Connection(SocketStream &rStream); + + virtual const char *DaemonName() const + { + return "test-srv2"; + } + const ConfigurationVerify *GetConfigVerify() const; + +}; + +const ConfigurationVerify *testserver::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + SERVERSTREAM_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +void testserver::Connection(SocketStream &rStream) +{ + testservers_connection(rStream); +} + +class testProtocolServer : public testserver +{ +public: + testProtocolServer() {} + ~testProtocolServer() {} + + void Connection(SocketStream &rStream); + + virtual const char *DaemonName() const + { + return "test-srv4"; + } +}; + +void testProtocolServer::Connection(SocketStream &rStream) +{ + TestProtocolServer server(rStream); + TestContext context; + server.DoServer(context); +} + + +class testTLSserver : public ServerTLS +{ +public: + testTLSserver() {} + ~testTLSserver() {} + + void Connection(SocketStreamTLS &rStream); + + virtual const char *DaemonName() const + { + return "test-srv3"; + } + const ConfigurationVerify *GetConfigVerify() const; + +}; + +const ConfigurationVerify *testTLSserver::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +void testTLSserver::Connection(SocketStreamTLS &rStream) +{ + testservers_connection(rStream); +} + + +void Srv2TestConversations(const std::vector &conns) +{ + const static char *tosend[] = { + "test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0 + }; + const static char *recieve[] = { + "1 tset", "storrac", "selppaenip", "!ooob", 0 + }; + + IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()]; + for(unsigned int c = 0; c < conns.size(); ++c) + { + getline[c] = new IOStreamGetLine(*conns[c]); + + bool hadTimeout = false; + if(typeid(*conns[c]) == typeid(SocketStreamTLS)) + { + SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c]; + printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str()); + + // Send some data, any data, to get the first response. + conns[c]->Write("Hello\n", 6); + + std::string line1; + while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT)) + hadTimeout = true; + TEST_THAT(line1 == "CONNECTED:CLIENT"); + TEST_THAT(hadTimeout) + } + } + + for(int q = 0; tosend[q] != 0; ++q) + { + for(unsigned int c = 0; c < conns.size(); ++c) + { + //printf("%d: %s", c, tosend[q]); + conns[c]->Write(tosend[q], strlen(tosend[q])); + std::string rep; + bool hadTimeout = false; + while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT)) + hadTimeout = true; + TEST_THAT(rep == recieve[q]); + TEST_THAT(hadTimeout) + } + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + conns[c]->Write("LARGEDATA\n", 10); + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Receive lots of data + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Send lots of data + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + conns[c]->Write(data, sizeof(data)); + } + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Receive lots of data again + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && (r = conns[c]->Read(buf, sizeof(buf))) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + } + + for(unsigned int c = 0; c < conns.size(); ++c) + { + conns[c]->Write("QUIT\n", 5); + } + + for(unsigned int c = 0; c < conns.size(); ++c) + { + if ( getline[c] ) delete getline[c]; + getline[c] = 0; + } + if ( getline ) delete [] getline; + getline = 0; +} + +void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream) +{ + std::auto_ptr reply(protocol.QueryGetStream(value, uncertainstream)); + TEST_THAT(reply->GetStartingValue() == value); + + // Get a stream + std::auto_ptr stream(protocol.ReceiveStream()); + + // check uncertainty + TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown)); + + printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size"); + + // Then check the contents + int values[998]; + int v = value; + int count = 0; + int bytesleft = 0; + int bytessofar = 0; + while(stream->StreamDataLeft()) + { + // Read some data + int bytes = stream->Read(((char*)values) + bytesleft, sizeof(values) - bytesleft); + bytessofar += bytes; + bytes += bytesleft; + int n = bytes / 4; + //printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar); + for(int t = 0; t < n; ++t) + { + if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v); + TEST_THAT(values[t] == v++); + } + count += n; + bytesleft = bytes - (n*4); + if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft); + } + + TEST_THAT(bytesleft == 0); + TEST_THAT(count == (24273*3)); // over 64 k of data, definately +} + + +int test(int argc, const char *argv[]) +{ + // Server launching stuff + if(argc >= 2) + { + // this is a quick hack to allow passing some options + // to the daemon + + const char* mode = argv[1]; + + if (test_args.length() > 0) + { + argv[1] = test_args.c_str(); + } + else + { + argc--; + argv++; + } + + if(strcmp(mode, "srv1") == 0) + { + // Run very basic daemon + basicdaemon daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv2") == 0) + { + // Run daemon which accepts connections + testserver daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv3") == 0) + { + testTLSserver daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv4") == 0) + { + testProtocolServer daemon; + return daemon.Main("doesnotexist", argc, argv); + } + } + + //printf("SKIPPING TESTS------------------------\n"); + //goto protocolserver; + + // Launch a basic server + { + std::string cmd = "./test --test-daemon-args="; + cmd += test_args; + cmd += " srv1 testfiles/srv1.conf"; + int pid = LaunchServer(cmd, "testfiles/srv1.pid"); + + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + // Check that it's written the expected file + TEST_THAT(TestFileExists("testfiles" + DIRECTORY_SEPARATOR "srv1.test1")); + TEST_THAT(ServerIsAlive(pid)); + + // Move the config file over + #ifdef WIN32 + TEST_THAT(::unlink("testfiles" + DIRECTORY_SEPARATOR "srv1.conf") != -1); + #endif + + TEST_THAT(::rename( + "testfiles" DIRECTORY_SEPARATOR "srv1b.conf", + "testfiles" DIRECTORY_SEPARATOR "srv1.conf") + != -1); + + #ifndef WIN32 + // Get it to reread the config file + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + // Check that new file exists + TEST_THAT(TestFileExists("testfiles" + DIRECTORY_SEPARATOR "srv1.test2")); + #endif // !WIN32 + + // Kill it off + TEST_THAT(KillServer(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks( + "generic-daemon.memleaks"); + #endif // !WIN32 + } + } + + // Launch a test forking server + { + std::string cmd = "./test --test-daemon-args="; + cmd += test_args; + cmd += " srv2 testfiles/srv2.conf"; + int pid = LaunchServer(cmd, "testfiles/srv2.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + // Will it restart? + TEST_THAT(ServerIsAlive(pid)); + + #ifndef WIN32 + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif // !WIN32 + + // Make some connections + { + SocketStream conn1; + conn1.Open(Socket::TypeINET, "localhost", 2003); + + #ifndef WIN32 + SocketStream conn2; + conn2.Open(Socket::TypeUNIX, + "testfiles/srv2.sock"); + SocketStream conn3; + conn3.Open(Socket::TypeINET, + "localhost", 2003); + #endif // !WIN32 + + // Quick check that reconnections fail + TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX, + "testfiles/srv2.sock");, + ServerException, SocketAlreadyOpen); + + // Stuff some data around + std::vector conns; + conns.push_back(&conn1); + + #ifndef WIN32 + conns.push_back(&conn2); + conns.push_back(&conn3); + #endif // !WIN32 + + Srv2TestConversations(conns); + // Implicit close + } + + #ifndef WIN32 + // HUP again + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif // !WIN32 + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv2.memleaks"); + #endif // !WIN32 + } + } + + // Launch a test SSL server + { + std::string cmd = "./test --test-daemon-args="; + cmd += test_args; + cmd += " srv3 testfiles/srv3.conf"; + int pid = LaunchServer(cmd, "testfiles/srv3.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + // Will it restart? + TEST_THAT(ServerIsAlive(pid)); + + #ifndef WIN32 + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif + + // Make some connections + { + // SSL library + SSLLib::Initialise(); + + // Context first + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + SocketStreamTLS conn1; + conn1.Open(context, Socket::TypeINET, "localhost", 2003); + #ifndef WIN32 + SocketStreamTLS conn2; + conn2.Open(context, Socket::TypeUNIX, + "testfiles/srv3.sock"); + SocketStreamTLS conn3; + conn3.Open(context, Socket::TypeINET, + "localhost", 2003); + #endif + + // Quick check that reconnections fail + TEST_CHECK_THROWS(conn1.Open(context, + Socket::TypeUNIX, + "testfiles/srv3.sock");, + ServerException, SocketAlreadyOpen); + + // Stuff some data around + std::vector conns; + conns.push_back(&conn1); + + #ifndef WIN32 + conns.push_back(&conn2); + conns.push_back(&conn3); + #endif + + Srv2TestConversations(conns); + // Implicit close + } + + #ifndef WIN32 + // HUP again + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv3.memleaks"); + #endif + } + } + +//protocolserver: + // Launch a test protocol handling server + { + std::string cmd = "./test --test-daemon-args="; + cmd += test_args; + cmd += " srv4 testfiles/srv4.conf"; + int pid = LaunchServer(cmd, "testfiles/srv4.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // Open a connection to it + SocketStream conn; + #ifdef WIN32 + conn.Open(Socket::TypeINET, "localhost", 2003); + #else + conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock"); + #endif + + // Create a protocol + TestProtocolClient protocol(conn); + + // Simple query + { + std::auto_ptr reply(protocol.QuerySimple(41)); + TEST_THAT(reply->GetValuePlusOne() == 42); + } + { + std::auto_ptr reply(protocol.QuerySimple(809)); + TEST_THAT(reply->GetValuePlusOne() == 810); + } + + // Streams, twice, both uncertain and certain sizes + TestStreamReceive(protocol, 374, false); + TestStreamReceive(protocol, 23983, true); + TestStreamReceive(protocol, 12098, false); + TestStreamReceive(protocol, 4342, true); + + // Try to send a stream + { + CollectInBufferStream s; + char buf[1663]; + s.Write(buf, sizeof(buf)); + s.SetForReading(); + std::auto_ptr reply(protocol.QuerySendStream(0x73654353298ffLL, s)); + TEST_THAT(reply->GetStartingValue() == sizeof(buf)); + } + + // Lots of simple queries + for(int q = 0; q < 514; q++) + { + std::auto_ptr reply(protocol.QuerySimple(q)); + TEST_THAT(reply->GetValuePlusOne() == (q+1)); + } + // Send a list of strings to it + { + std::vector strings; + strings.push_back(std::string("test1")); + strings.push_back(std::string("test2")); + strings.push_back(std::string("test3")); + std::auto_ptr reply(protocol.QueryLists(strings)); + TEST_THAT(reply->GetNumberOfStrings() == 3); + } + + // And another + { + std::auto_ptr reply(protocol.QueryHello(41,87,11,std::string("pingu"))); + TEST_THAT(reply->GetNumber32() == 12); + TEST_THAT(reply->GetNumber16() == 89); + TEST_THAT(reply->GetNumber8() == 22); + TEST_THAT(reply->GetText() == "Hello world!"); + } + + // Quit query to finish + protocol.QueryQuit(); + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv4.memleaks"); + #endif + } + } + + return 0; +} + diff --git a/test/basicserver/testfiles/clientCerts.pem b/test/basicserver/testfiles/clientCerts.pem new file mode 100644 index 00000000..81d4c5cc --- /dev/null +++ b/test/basicserver/testfiles/clientCerts.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICOTCCAaICAQUwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDQz +WhcNMzEwMTIyMTYyNDQzWjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjEPMA0GA1UEAxMGQ0xJRU5UMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Zcr+TRrHk +Q8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65y417qk5z +NjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQABMA0GCSqG +SIb3DQEBBQUAA4GBACYkSYlrKNv1v6lrES4j68S8u8SNlnSM+Z4pTHF/7K7SQeIn +SKVV8EI8CLR5jIsQRRHKB9rYgYS4kB8SFbPyrsH8VKngjIUcjmTKLq9zpAt2zDNo +m+y5SMXsaJF6Xbtbz+MSxXZZ6YBBuseY+Wkpz4ZGSVlQrHxjsuYdBFHIguM3 +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/clientPrivKey.pem b/test/basicserver/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..a4797b66 --- /dev/null +++ b/test/basicserver/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Z +cr+TRrHkQ8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65 +y417qk5zNjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQAB +AoGAF92enbH158KaMnp/tlLqMrI7It5R5z4YRJLgMnBFl9j6pqPZEI9ge79N/L/Y +2WSZXE7sLCaUktYwkc9LkOXkBYQI7EIOonLdmSsNCMbSBVbeczdM77dBscuCTKva +nvre/2+hlmuWBNINqXlprBkvd5YF4Q/yeXzoXPuMIQ0tROECQQDqifOZOfCle8uA +CgdHT9pO638PwrrldMHmZSK3gUmHmFe7ziGpNGCfKZ+wkSIvDg9INQvEXvQfLZiV +n4J78IOHAkEAzB0SoUU0cL+wK3OQTTOlx4cgxaxgtsuvccIhqTh4Jp1Aj9iMKiPW +yXvbGhDBTZP2IL5HoqSLc3SxfXgvn6O/nQJBALgJMYWdalBf2GoK9HUnmpTsw1I5 +qe/c8z13RIubvnfQuZ8be1xLRjn+LlkdOSaVMLanMSmQnJxOafmWJYxdSMcCQFBc +5ffe8n2tyyPgdSEgQ5YiatHJQ67U1Te50lz44b16TnAUN2NkBu3/OM2zaRgtOEu9 +/yBXHpyPhk47Iqz84LUCQQCIDIKluoughLVjJS2eD28UJHM9Z+OvmyIE0fF0Q0vi +E+Rn/+iWCoEJYa7WP5AEo/aeVXiCeHONXGF1AI8a8gb5 +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/clientReq.pem b/test/basicserver/testfiles/clientReq.pem new file mode 100644 index 00000000..14e2c6df --- /dev/null +++ b/test/basicserver/testfiles/clientReq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDzANBgNVBAMTBkNMSUVOVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +uwCQ0CXRh4uBzu1V2SXYv9wvK0Mdbk8TDukBwRXqdMEGeiN+2XK/k0ax5EPAnpcp +iAcdmX0FzCyrKAfBUnCKdMyE/3aytAIIblhOVD1OnZ7T5awOucuNe6pOczY4jsun +KFpfu5LWdCLQrsu1R1q05h0WzWXlyrm+347nHAyXYssCAwEAAaAAMA0GCSqGSIb3 +DQEBBQUAA4GBAKV3H/yWrYep6yfEDQp61zn60tEnJOS5LVbuV7ivNjAue0/09wBT +PGzTblwx116AT9GbTcbERK/ll549+tziTLT9NUT12ZcvaRezYP2PpaD8fiDKHs3D +vSwpFoihLmUnDeMWE9Vbt+b0Fl/mdsH6sm3Mo0COG/DkolOVsydOj2Hp +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/clientTrustedCAs.pem b/test/basicserver/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/clientTrustedCAs.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/key-creation.txt b/test/basicserver/testfiles/key-creation.txt new file mode 100644 index 00000000..51f4eb77 --- /dev/null +++ b/test/basicserver/testfiles/key-creation.txt @@ -0,0 +1,83 @@ +$ openssl genrsa -out rootkey.pem 1024 + +$ openssl req -new -key rootkey.pem -sha1 -out rootreq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:ROOT +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ openssl x509 -req -in rootreq.pem -sha1 -extensions v3_ca -signkey rootkey.pem -out rootcert.pem -days 10000 +Signature ok +subject=/C=GB/ST=London/L=London/O=Test/OU=basic server/CN=ROOT +Getting Private key + +$ cp rootcert.pem serverTrustedCAs.pem +$ cp rootcert.pem clientTrustedCAs.pem + +$ openssl genrsa -out clientPrivKey.pem 1024 +$ openssl req -new -key clientPrivKey.pem -sha1 -out clientReq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:CLIENT +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ cat rootcert.pem rootkey.pem > root.pem + +$ echo 01 > root.srl + +$ openssl x509 -req -in clientReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out clientCerts.pem -days 10000 + +$ openssl genrsa -out serverPrivKey.pem 1024 +$ openssl req -new -key serverPrivKey.pem -sha1 -out serverReq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:SERVER +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ openssl x509 -req -in serverReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out serverCerts.pem -days 10000 + diff --git a/test/basicserver/testfiles/root.pem b/test/basicserver/testfiles/root.pem new file mode 100644 index 00000000..020c25c3 --- /dev/null +++ b/test/basicserver/testfiles/root.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t +gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI +UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB +AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW +g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm +ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja +pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw +iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb +Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc +lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x +fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS +8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS +GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/root.srl b/test/basicserver/testfiles/root.srl new file mode 100644 index 00000000..2c7456e3 --- /dev/null +++ b/test/basicserver/testfiles/root.srl @@ -0,0 +1 @@ +07 diff --git a/test/basicserver/testfiles/rootcert.pem b/test/basicserver/testfiles/rootcert.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/rootcert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/rootkey.pem b/test/basicserver/testfiles/rootkey.pem new file mode 100644 index 00000000..4eb0f59d --- /dev/null +++ b/test/basicserver/testfiles/rootkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t +gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI +UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB +AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW +g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm +ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja +pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw +iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb +Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc +lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x +fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS +8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS +GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/rootreq.pem b/test/basicserver/testfiles/rootreq.pem new file mode 100644 index 00000000..6da1e428 --- /dev/null +++ b/test/basicserver/testfiles/rootreq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpDCCAQ0CAQAwZDELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDTALBgNVBAMTBFJPT1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxh +cn4gjQKT2JyvAN4bKZ1ncCHtE6YLfYulLexBVyFB3vnY7i2DJNQ/p3just0zbGxW +N1Ed5v0AP4NWvMM1Fau09jFAzKM+FfB5DP+PDEDJVB3JgQhSR5xgC9YPCm31/rMX +kilyrq52TG0TckcKdHfUQ5bUJxy36nKDwoGCPeRTAgMBAAGgADANBgkqhkiG9w0B +AQUFAAOBgQCmy4L/D/m1Q23y+WB1Ub2u1efl0sb7zMWNzHsD/IR1CXSvXmAfPpr2 +hpJQj118ccaTqkRhA8gwhktMTBuGH5KiOLHYXRlniKo3G0yr0+fHWnjclZ+m6Bg1 +9HjJZYqIWRMQ78+wTpLCxliX6yp0JxMdx/v6/7jx3BtXz8cyU8ANAw== +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/serverCerts.pem b/test/basicserver/testfiles/serverCerts.pem new file mode 100644 index 00000000..f61c554e --- /dev/null +++ b/test/basicserver/testfiles/serverCerts.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICOTCCAaICAQYwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNTA0 +WhcNMzEwMTIyMTYyNTA0WjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjEPMA0GA1UEAxMGU0VSVkVSMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40XsmCEdHnDl +AjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrYaQkgME2O +mPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQABMA0GCSqG +SIb3DQEBBQUAA4GBALgh7u/7GZUMjzOPGuIenkdrsP0Gbst7wuXrLaMrAMlAaWMH +E9AgU/6Q9+2yFxisgAzRmyKydNP4E4YomsE8rbx08vGw/6Rc7L19/UsFJxeNC5Ue +6hziI9boB9LL5em4N8v+z4yhGvj2CrKzBxLNy8MYPi2S3KfQ69FdipvRQRp/ +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/serverPrivKey.pem b/test/basicserver/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..f2d73fd4 --- /dev/null +++ b/test/basicserver/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40Xs +mCEdHnDlAjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrY +aQkgME2OmPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQAB +AoGBAJSH7zAC9OmXXHoGhWeQEbzO+yT6aHxdY8/KGeBZUMasYB7qqZb8eYWbToYm +nS2cpVAh0gHZcfrdyuDwSQpPQIIA8gAPFHqR8T8VGrpChxgetYzkoPDapmcqKU4H +YobFVA1gypK1IM5z3Z5kargqGmmzRIxX8BwWr6FGmFPp2+NBAkEA7A17g4JewNtY +vtpM0NhIyw+7HN3ljf+pAvHM2pMw1Wk8TrbPJNQ20ZWnhGMdIvP0m25zna6pShL6 +0laf5EUWFQJBANx1SJ+Xb3P9IyrIlyMhrsYvAveezh6wimjAFFNYWmGEZ6uuHM5P +eBSc3P0x0LbFKlGQWomxMb3ULwpjEueX9HkCQDMf0GpxJ/h5CUV8njp1PX7NT2c3 +H+qbPo2mtQl564+tFSSvLzn4xE6sLPXdSYgycf3f9CZol721UqGPpV2ZIOkCQQCQ +trxxZmrW7LgFAZ+UhCvCFGISQcB0DNcOY+fzve+2S7/xxl1KYIgmn8HAws6K62oY +GHYWJKbOQVaPrvFd7TWhAkA8VQPjDSRkdg2fU5RDTRfOQBczgc8aHTiqAv/S2g47 +lpsw8CLitobBvi3e5XuBKNIbnjeoZMbHcBZ+RXAAZe/Q +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/serverReq.pem b/test/basicserver/testfiles/serverReq.pem new file mode 100644 index 00000000..ce510fae --- /dev/null +++ b/test/basicserver/testfiles/serverReq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDzANBgNVBAMTBlNFUlZFUjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +y0e7RWnjbwnbwuZ0IH2IIBR8WTOTXLMSUmHBduxoT8pRxONF7JghHR5w5QI1rOPB +q2De7X02u/yRBTPbO7IBfTsY/rbITEzWb8duboofIBxQoNEa2GkJIDBNjpj7j7RX +AP4rEzAijuw/2htkmqY2PP15HtE7Hijt43z/S7cWc+0CAwEAAaAAMA0GCSqGSIb3 +DQEBBQUAA4GBAGdUCS76aBzPw4zcU999r6gE7/F8/bYlT/tr2SEyKzF+vC0widZN +P3bg9IaNAWi84vw8WEB+j2wM3TPB5/kSKFpO2MxOHPERX+aOXh6JkN6a/ay5CDOT +r/wCERRkqY2gphU5m3/S0Gd7wLbH/neBgNsHUzbNwwQ+uqkF2NRGg0V/ +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/serverTrustedCAs.pem b/test/basicserver/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/serverTrustedCAs.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/srv1.conf b/test/basicserver/testfiles/srv1.conf new file mode 100644 index 00000000..ee68704e --- /dev/null +++ b/test/basicserver/testfiles/srv1.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv1.pid +} + +TestFile = testfiles/srv1.test1 diff --git a/test/basicserver/testfiles/srv1b.conf b/test/basicserver/testfiles/srv1b.conf new file mode 100644 index 00000000..d6d6eebd --- /dev/null +++ b/test/basicserver/testfiles/srv1b.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv1.pid +} + +TestFile = testfiles/srv1.test2 diff --git a/test/basicserver/testfiles/srv2.conf b/test/basicserver/testfiles/srv2.conf new file mode 100644 index 00000000..ef1d7c49 --- /dev/null +++ b/test/basicserver/testfiles/srv2.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv2.pid + ListenAddresses = inet:localhost,unix:testfiles/srv2.sock +} + diff --git a/test/basicserver/testfiles/srv3.conf b/test/basicserver/testfiles/srv3.conf new file mode 100644 index 00000000..e2211553 --- /dev/null +++ b/test/basicserver/testfiles/srv3.conf @@ -0,0 +1,9 @@ +Server +{ + PidFile = testfiles/srv3.pid + ListenAddresses = inet:localhost,unix:testfiles/srv3.sock + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/basicserver/testfiles/srv4.conf b/test/basicserver/testfiles/srv4.conf new file mode 100644 index 00000000..f05dff75 --- /dev/null +++ b/test/basicserver/testfiles/srv4.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv4.pid + ListenAddresses = unix:testfiles/srv4.sock,inet:localhost +} + diff --git a/test/basicserver/testprotocol.txt b/test/basicserver/testprotocol.txt new file mode 100644 index 00000000..5bca9f49 --- /dev/null +++ b/test/basicserver/testprotocol.txt @@ -0,0 +1,42 @@ +# test protocol file + +Name Test +IdentString Test-0.00 +ServerContextClass TestContext TestContext.h + +BEGIN_OBJECTS + +Error 0 IsError(Type,SubType) Reply + int32 Type + int32 SubType + +Hello 1 Command(Hello) Reply + int32 Number32 + int16 Number16 + int8 Number8 + string Text + +Lists 2 Command(ListsReply) + vector LotsOfText + +ListsReply 3 Reply + int32 NumberOfStrings + +Quit 4 Command(Quit) Reply EndsConversation + +Simple 5 Command(SimpleReply) + int32 Value + +SimpleReply 6 Reply + int32 ValuePlusOne + +GetStream 7 Command(GetStream) Reply + int32 StartingValue + bool UncertainSize + +SendStream 8 Command(GetStream) StreamWithCommand + int64 Value + +String 9 Command(String) Reply + string Test + diff --git a/test/bbackupd/Makefile.extra b/test/bbackupd/Makefile.extra new file mode 100644 index 00000000..0ae56bd1 --- /dev/null +++ b/test/bbackupd/Makefile.extra @@ -0,0 +1,14 @@ +link-extra: ../../bin/bbackupd/autogen_ClientException.o \ + ../../bin/bbackupd/BackupClientContext.o \ + ../../bin/bbackupd/BackupClientDeleteList.o \ + ../../bin/bbackupd/BackupClientDirectoryRecord.o \ + ../../bin/bbackupd/Win32BackupService.o \ + ../../bin/bbackupd/BackupClientInodeToIDMap.o \ + ../../bin/bbackupd/Win32ServiceFunctions.o \ + ../../bin/bbackupd/BackupDaemon.o \ + ../../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 diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp new file mode 100644 index 00000000..77c463ba --- /dev/null +++ b/test/bbackupd/testbbackupd.cpp @@ -0,0 +1,4077 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbbackupd.cpp +// Purpose: test backup daemon (and associated client bits) +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +// do not include MinGW's dirent.h on Win32, +// as we override some of it in lib/win32. + +#ifndef WIN32 + #include +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H + #include +#endif + +#ifdef HAVE_SYS_XATTR_H + #include + #include +#endif + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#include + +#ifdef HAVE_SYSCALL + #include +#endif + +#include "autogen_BackupProtocolServer.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientFileAttributes.h" +#include "BackupClientRestore.h" +#include "BackupDaemon.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupQueries.h" +#include "BackupStoreConstants.h" +#include "BackupStoreContext.h" +#include "BackupStoreDaemon.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BoxPortsAndFiles.h" +#include "BoxTime.h" +#include "BoxTimeToUnix.h" +#include "CollectInBufferStream.h" +#include "CommonException.h" +#include "Configuration.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "IOStreamGetLine.h" +#include "LocalProcessStream.h" +#include "SSLLib.h" +#include "ServerControl.h" +#include "Socket.h" +#include "SocketStreamTLS.h" +#include "TLSContext.h" +#include "Test.h" +#include "Timer.h" +#include "Utils.h" + +#include "autogen_BackupProtocolClient.h" +#include "intercept.h" +#include "ServerControl.h" + +#include "MemLeakFindOn.h" + +// ENOATTR may be defined in a separate header file which we may not have +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif + +// two cycles and a bit +#define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12 + +void wait_for_backup_operation(const char* message) +{ + wait_for_operation(TIME_TO_WAIT_FOR_BACKUP_OPERATION, message); +} + +int bbstored_pid = 0; +int bbackupd_pid = 0; + +#ifdef HAVE_SYS_XATTR_H +bool readxattr_into_map(const char *filename, std::map &rOutput) +{ + rOutput.clear(); + + ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0); + if(xattrNamesBufferSize < 0) + { + return false; + } + else if(xattrNamesBufferSize > 0) + { + // There is some data there to look at + char *xattrNamesBuffer = (char*)malloc(xattrNamesBufferSize + 4); + if(xattrNamesBuffer == NULL) return false; + char *xattrDataBuffer = 0; + int xattrDataBufferSize = 0; + // note: will leak these buffers if a read error occurs. (test code, so doesn't matter) + + ssize_t ns = llistxattr(filename, xattrNamesBuffer, xattrNamesBufferSize); + if(ns < 0) + { + return false; + } + else if(ns > 0) + { + // Read all the attribute values + const char *xattrName = xattrNamesBuffer; + while(xattrName < (xattrNamesBuffer + ns)) + { + // Store size of name + int xattrNameSize = strlen(xattrName); + + bool ok = true; + + ssize_t dataSize = lgetxattr(filename, xattrName, NULL, 0); + if(dataSize < 0) + { + if(errno == ENOATTR) + { + // Deleted from under us + ok = false; + } + else + { + return false; + } + } + else if(dataSize == 0) + { + // something must have removed all the data from under us + ok = false; + } + else + { + // Make sure there's enough space in the buffer to get the attribute + if(xattrDataBuffer == 0) + { + xattrDataBuffer = (char*)malloc(dataSize + 4); + xattrDataBufferSize = dataSize + 4; + } + else if(xattrDataBufferSize < (dataSize + 4)) + { + char *resized = (char*)realloc(xattrDataBuffer, dataSize + 4); + if(resized == NULL) return false; + xattrDataBuffer = resized; + xattrDataBufferSize = dataSize + 4; + } + } + + // Read the data! + dataSize = 0; + if(ok) + { + dataSize = lgetxattr(filename, xattrName, xattrDataBuffer, + xattrDataBufferSize - 1 /*for terminator*/); + if(dataSize < 0) + { + if(errno == ENOATTR) + { + // Deleted from under us + ok = false; + } + else + { + return false; + } + } + else if(dataSize == 0) + { + // something must have deleted this from under us + ok = false; + } + else + { + // Terminate the data + xattrDataBuffer[dataSize] = '\0'; + } + // Got the data in the buffer + } + + // Store in map + if(ok) + { + rOutput[std::string(xattrName)] = std::string(xattrDataBuffer, dataSize); + } + + // Next attribute + xattrName += xattrNameSize + 1; + } + } + + if(xattrNamesBuffer != 0) ::free(xattrNamesBuffer); + if(xattrDataBuffer != 0) ::free(xattrDataBuffer); + } + + return true; +} + +static FILE *xattrTestDataHandle = 0; +bool write_xattr_test(const char *filename, const char *attrName, unsigned int length, bool *pNotSupported = 0) +{ + if(xattrTestDataHandle == 0) + { + xattrTestDataHandle = ::fopen("testfiles/test3.tgz", "rb"); // largest test file + } + if(xattrTestDataHandle == 0) + { + return false; + } + else + { + char data[1024]; + if(length > sizeof(data)) length = sizeof(data); + + if(::fread(data, length, 1, xattrTestDataHandle) != 1) + { + return false; + } + + if(::lsetxattr(filename, attrName, data, length, 0) != 0) + { + if(pNotSupported != 0) + { + *pNotSupported = (errno == ENOTSUP); + } + return false; + } + } + + return true; +} +void finish_with_write_xattr_test() +{ + if(xattrTestDataHandle != 0) + { + ::fclose(xattrTestDataHandle); + } +} +#endif // HAVE_SYS_XATTR_H + +bool attrmatch(const char *f1, const char *f2) +{ + EMU_STRUCT_STAT s1, s2; + TEST_THAT(EMU_LSTAT(f1, &s1) == 0); + TEST_THAT(EMU_LSTAT(f2, &s2) == 0); + +#ifdef HAVE_SYS_XATTR_H + { + std::map xattr1, xattr2; + if(!readxattr_into_map(f1, xattr1) + || !readxattr_into_map(f2, xattr2)) + { + return false; + } + if(!(xattr1 == xattr2)) + { + return false; + } + } +#endif // HAVE_SYS_XATTR_H + + // if link, just make sure other file is a link too, and that the link to names match + if((s1.st_mode & S_IFMT) == S_IFLNK) + { +#ifdef WIN32 + TEST_FAIL_WITH_MESSAGE("No symlinks on win32!") +#else + if((s2.st_mode & S_IFMT) != S_IFLNK) return false; + + char p1[PATH_MAX], p2[PATH_MAX]; + int p1l = ::readlink(f1, p1, PATH_MAX); + int p2l = ::readlink(f2, p2, PATH_MAX); + TEST_THAT(p1l != -1 && p2l != -1); + // terminate strings properly + p1[p1l] = '\0'; + p2[p2l] = '\0'; + return strcmp(p1, p2) == 0; +#endif + } + + // modification times + if(FileModificationTime(s1) != FileModificationTime(s2)) + { + return false; + } + + // compare the rest + return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid); +} + +int test_basics() +{ + // Read attributes from files + BackupClientFileAttributes t1; + t1.ReadAttributes("testfiles/test1"); + TEST_THAT(!t1.IsSymLink()); + +#ifndef WIN32 + BackupClientFileAttributes t2; + t2.ReadAttributes("testfiles/test2"); + TEST_THAT(t2.IsSymLink()); + // Check that it's actually been encrypted (search for symlink name encoded in it) + void *te = ::memchr(t2.GetBuffer(), 't', t2.GetSize() - 3); + TEST_THAT(te == 0 || ::memcmp(te, "test", 4) != 0); +#endif + + BackupClientFileAttributes t3; + { + Logging::Guard guard(Log::ERROR); + TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"), + CommonException, OSFileError); + } + + // Create some more files + FILE *f = fopen("testfiles/test1_n", "w"); + fclose(f); + f = fopen("testfiles/test2_n", "w"); + fclose(f); + + // Apply attributes to these new files + t1.WriteAttributes("testfiles/test1_n"); +#ifdef WIN32 + t1.WriteAttributes("testfiles/test2_n"); +#else + t2.WriteAttributes("testfiles/test2_n"); +#endif + +#ifndef WIN32 + { + Logging::Guard guard(Log::ERROR); + TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), + CommonException, OSFileError); + TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), + BackupStoreException, AttributesNotLoaded); + } + + // Test that attributes are vaguely similar + TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n")); + TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n")); +#endif + + // Check encryption, and recovery from encryption + // First, check that two attributes taken from the same thing have different encrypted values (think IV) + BackupClientFileAttributes t1b; + t1b.ReadAttributes("testfiles/test1"); + TEST_THAT(::memcmp(t1.GetBuffer(), t1b.GetBuffer(), t1.GetSize()) != 0); + // But that comparing them works OK. + TEST_THAT(t1 == t1b); + // Then store them both to a stream + CollectInBufferStream stream; + t1.WriteToStream(stream); + t1b.WriteToStream(stream); + // Read them back again + stream.SetForReading(); + BackupClientFileAttributes t1_r, t1b_r; + t1_r.ReadFromStream(stream, 1000); + t1b_r.ReadFromStream(stream, 1000); + TEST_THAT(::memcmp(t1_r.GetBuffer(), t1b_r.GetBuffer(), t1_r.GetSize()) != 0); + TEST_THAT(t1_r == t1b_r); + TEST_THAT(t1 == t1_r); + TEST_THAT(t1b == t1b_r); + TEST_THAT(t1_r == t1b); + TEST_THAT(t1b_r == t1); + +#ifdef HAVE_SYS_XATTR_H + // Write some attributes to the file, checking for ENOTSUP + bool xattrNotSupported = false; + if(!write_xattr_test("testfiles/test1", "user.attr_1", 1000, &xattrNotSupported) && xattrNotSupported) + { + ::printf("***********\nYour platform supports xattr, but your filesystem does not.\nSkipping tests.\n***********\n"); + } + else + { + BackupClientFileAttributes x1, x2, x3, x4; + + // Write more attributes + TEST_THAT(write_xattr_test("testfiles/test1", "user.attr_2", 947)); + TEST_THAT(write_xattr_test("testfiles/test1", "user.sadfohij39998.3hj", 123)); + + // Read file attributes + x1.ReadAttributes("testfiles/test1"); + + // Write file attributes + FILE *f = fopen("testfiles/test1_nx", "w"); + fclose(f); + x1.WriteAttributes("testfiles/test1_nx"); + + // Compare to see if xattr copied + TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_nx")); + + // Add more attributes to a file + x2.ReadAttributes("testfiles/test1"); + TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); + + // Read them again, and check that the Compare() function detects that they're different + x3.ReadAttributes("testfiles/test1"); + TEST_THAT(x1.Compare(x2, true, true)); + TEST_THAT(!x1.Compare(x3, true, true)); + + // Change the value of one of them, leaving the size the same. + TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); + x4.ReadAttributes("testfiles/test1"); + TEST_THAT(!x1.Compare(x4, true, true)); + } + finish_with_write_xattr_test(); +#endif // HAVE_SYS_XATTR_H + + return 0; +} + +int test_setupaccount() +{ + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " + "testfiles/bbstored.conf create 01234567 0 1000B 2000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + return 0; +} + +int test_run_bbstored() +{ + std::string cmd = BBSTORED " " + bbstored_args + + " testfiles/bbstored.conf"; + bbstored_pid = LaunchServer(cmd, "testfiles/bbstored.pid"); + + TEST_THAT(bbstored_pid != -1 && bbstored_pid != 0); + + if(bbstored_pid > 0) + { + ::safe_sleep(1); + TEST_THAT(ServerIsAlive(bbstored_pid)); + return 0; // success + } + + return 1; +} + +int test_kill_bbstored(bool wait_for_process = false) +{ + TEST_THAT(KillServer(bbstored_pid, wait_for_process)); + ::safe_sleep(1); + TEST_THAT(!ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbstored_pid)) + { + bbstored_pid = 0; + } + + #ifdef WIN32 + TEST_THAT(unlink("testfiles/bbstored.pid") == 0); + #else + TestRemoteProcessMemLeaks("bbstored.memleaks"); + #endif + + return 0; +} + +int64_t GetDirID(BackupProtocolClient &protocol, const char *name, int64_t InDirectory) +{ + protocol.QueryListDirectory( + InDirectory, + BackupProtocolClientListDirectory::Flags_Dir, + BackupProtocolClientListDirectory::Flags_EXCLUDE_NOTHING, + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, protocol.GetTimeout()); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + int64_t dirid = 0; + BackupStoreFilenameClear dirname(name); + while((en = i.Next()) != 0) + { + if(en->GetName() == dirname) + { + dirid = en->GetObjectID(); + } + } + return dirid; +} + +void terminate_on_alarm(int sigraised) +{ + abort(); +} + +#ifndef WIN32 +void do_interrupted_restore(const TLSContext &context, int64_t restoredirid) +{ + int pid = 0; + switch((pid = fork())) + { + case 0: + // child process + { + // connect and log in + SocketStreamTLS conn; + conn.Open(context, Socket::TypeINET, "localhost", + 22011); + BackupProtocolClient protocol(conn); + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr loginConf(protocol.QueryLogin(0x01234567, BackupProtocolClientLogin::Flags_ReadOnly)); + + // Test the restoration + TEST_THAT(BackupClientRestore(protocol, restoredirid, + "Test1", "testfiles/restore-interrupt", + true /* print progress dots */) + == Restore_Complete); + + // Log out + protocol.QueryFinished(); + } + exit(0); + break; + + case -1: + { + printf("Fork failed\n"); + exit(1); + } + + default: + { + // Wait until a resume file is written, then terminate the child + while(true) + { + // Test for existence of the result file + int64_t resumesize = 0; + if(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize) && resumesize > 16) + { + // It's done something. Terminate it. + ::kill(pid, SIGTERM); + break; + } + + // Process finished? + int status = 0; + if(waitpid(pid, &status, WNOHANG) != 0) + { + // child has finished anyway. + return; + } + + // Give up timeslot so as not to hog the processor + ::sleep(0); + } + + // Just wait until the child has completed + int status = 0; + waitpid(pid, &status, 0); + } + } +} +#endif // !WIN32 + +#ifdef WIN32 +bool set_file_time(const char* filename, FILETIME creationTime, + FILETIME lastModTime, FILETIME lastAccessTime) +{ + HANDLE handle = openfile(filename, O_RDWR, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + if (handle == INVALID_HANDLE_VALUE) return false; + + BOOL success = SetFileTime(handle, &creationTime, &lastAccessTime, + &lastModTime); + TEST_THAT(success); + + TEST_THAT(CloseHandle(handle)); + return success; +} +#endif + +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay); +bool intercept_triggered(); + +int64_t SearchDir(BackupStoreDirectory& rDir, + const std::string& rChildName) +{ + BackupStoreDirectory::Iterator i(rDir); + BackupStoreFilenameClear child(rChildName.c_str()); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); + if (en == 0) return 0; + int64_t id = en->GetObjectID(); + TEST_THAT(id > 0); + TEST_THAT(id != BackupProtocolClientListDirectory::RootDirectory); + return id; +} + +SocketStreamTLS sSocket; + +std::auto_ptr Connect(TLSContext& rContext) +{ + sSocket.Open(rContext, Socket::TypeINET, + "localhost", 22011); + std::auto_ptr connection; + connection.reset(new BackupProtocolClient(sSocket)); + connection->Handshake(); + std::auto_ptr + serverVersion(connection->QueryVersion( + BACKUP_STORE_SERVER_VERSION)); + if(serverVersion->GetVersion() != + BACKUP_STORE_SERVER_VERSION) + { + THROW_EXCEPTION(BackupStoreException, + WrongServerVersion); + } + return connection; +} + +std::auto_ptr ConnectAndLogin(TLSContext& rContext, + int flags) +{ + std::auto_ptr connection(Connect(rContext)); + connection->QueryLogin(0x01234567, flags); + return connection; +} + +std::auto_ptr ReadDirectory +( + BackupProtocolClient& rClient, + int64_t id +) +{ + std::auto_ptr dirreply( + rClient.QueryListDirectory(id, false, 0, false)); + std::auto_ptr dirstream(rClient.ReceiveStream()); + std::auto_ptr apDir(new BackupStoreDirectory()); + apDir->ReadFromStream(*dirstream, rClient.GetTimeout()); + return apDir; +} + +int start_internal_daemon() +{ + // ensure that no child processes end up running tests! + int own_pid = getpid(); + BOX_TRACE("Test PID is " << own_pid); + + // this is a quick hack to allow passing some options to the daemon + const char* argv[] = { + "dummy", + bbackupd_args.c_str(), + }; + + BackupDaemon daemon; + int result; + + if (bbackupd_args.size() > 0) + { + result = daemon.Main("testfiles/bbackupd.conf", 2, argv); + } + else + { + result = daemon.Main("testfiles/bbackupd.conf", 1, argv); + } + + TEST_EQUAL_LINE(0, result, "Daemon exit code"); + + // ensure that no child processes end up running tests! + if (getpid() != own_pid) + { + // abort! + BOX_INFO("Daemon child finished, exiting now."); + _exit(0); + } + + TEST_THAT(TestFileExists("testfiles/bbackupd.pid")); + + printf("Waiting for backup daemon to start: "); + int pid = -1; + + for (int i = 0; i < 30; i++) + { + printf("."); + fflush(stdout); + safe_sleep(1); + + if (TestFileExists("testfiles/bbackupd.pid")) + { + pid = ReadPidFile("testfiles/bbackupd.pid"); + } + + if (pid > 0) + { + break; + } + } + + printf(" done.\n"); + fflush(stdout); + + TEST_THAT(pid > 0); + return pid; +} + +bool stop_internal_daemon(int pid) +{ + bool killed_server = KillServer(pid, false); + TEST_THAT(killed_server); + return killed_server; +} + +static struct dirent readdir_test_dirent; +static int readdir_test_counter = 0; +static int readdir_stop_time = 0; +static char stat_hook_filename[512]; + +// First test hook, during the directory scanning stage, returns empty. +// This will not match the directory on the store, so a sync will start. +// We set up the next intercept for the same directory by passing NULL. + +extern "C" struct dirent *readdir_test_hook_2(DIR *dir); + +#ifdef LINUX_WEIRD_LSTAT +extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf); +#else +extern "C" int lstat_test_hook(const char *file_name, struct stat *buf); +#endif + +extern "C" struct dirent *readdir_test_hook_1(DIR *dir) +{ +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + intercept_setup_readdir_hook(NULL, readdir_test_hook_2); +#endif + return NULL; +} + +// Second test hook, during the directory sync stage, keeps returning +// new filenames until the timer expires, then disables the intercept. + +extern "C" struct dirent *readdir_test_hook_2(DIR *dir) +{ + if (time(NULL) >= readdir_stop_time) + { +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + intercept_setup_readdir_hook(NULL, NULL); + intercept_setup_lstat_hook (NULL, NULL); + // we will not be called again. +#endif + } + + // fill in the struct dirent appropriately + memset(&readdir_test_dirent, 0, sizeof(readdir_test_dirent)); + + #ifdef HAVE_STRUCT_DIRENT_D_INO + readdir_test_dirent.d_ino = ++readdir_test_counter; + #endif + + snprintf(readdir_test_dirent.d_name, + sizeof(readdir_test_dirent.d_name), + "test.%d", readdir_test_counter); + + // ensure that when bbackupd stats the file, it gets the + // right answer + snprintf(stat_hook_filename, sizeof(stat_hook_filename), + "testfiles/TestDir1/spacetest/d1/test.%d", + readdir_test_counter); + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + intercept_setup_lstat_hook(stat_hook_filename, lstat_test_hook); +#endif + + return &readdir_test_dirent; +} + +#ifdef LINUX_WEIRD_LSTAT +extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf) +#else +extern "C" int lstat_test_hook(const char *file_name, struct stat *buf) +#endif +{ + // TRACE1("lstat hook triggered for %s", file_name); + memset(buf, 0, sizeof(*buf)); + buf->st_mode = S_IFREG; + return 0; +} + +// Simulate a symlink that is on a different device than the file +// that it points to. +int lstat_test_post_hook(int old_ret, const char *file_name, struct stat *buf) +{ + BOX_TRACE("lstat post hook triggered for " << file_name); + if (old_ret == 0 && + strcmp(file_name, "testfiles/symlink-to-TestDir1") == 0) + { + buf->st_dev ^= 0xFFFF; + } + return old_ret; +} + +bool test_entry_deleted(BackupStoreDirectory& rDir, + const std::string& rName) +{ + BackupStoreDirectory::Iterator i(rDir); + + BackupStoreDirectory::Entry *en = i.FindMatchingClearName( + BackupStoreFilenameClear(rName)); + TEST_THAT(en != 0); + if (en == 0) return false; + + int16_t flags = en->GetFlags(); + TEST_THAT(flags && BackupStoreDirectory::Entry::Flags_Deleted); + return flags && BackupStoreDirectory::Entry::Flags_Deleted; +} + +int test_bbackupd() +{ + // First, wait for a normal period to make sure the last changes + // attributes are within a normal backup timeframe. + // wait_for_backup_operation(); + + // Connection gubbins + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + printf("\n==== Testing that ReadDirectory on nonexistent directory " + "does not crash\n"); + { + std::auto_ptr client = ConnectAndLogin( + context, 0 /* read-write */); + + { + Logging::Guard guard(Log::ERROR); + TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678), + ConnectionException, + Conn_Protocol_UnexpectedReply); + } + + client->QueryFinished(); + sSocket.Close(); + } + + // unpack the files for the initial test + TEST_THAT(::system("rm -rf testfiles/TestDir1") == 0); + TEST_THAT(::mkdir("testfiles/TestDir1", 0777) == 0); + + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/spacetest1.tgz " + "-C testfiles/TestDir1") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/spacetest1.tgz " + "| ( cd testfiles/TestDir1 && tar xf - )") == 0); + #endif + +#ifdef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + printf("\n==== Skipping intercept-based KeepAlive tests " + "on this platform.\n"); +#else + printf("\n==== Testing SSL KeepAlive messages\n"); + + { + #ifdef WIN32 + #error TODO: implement threads on Win32, or this test \ + will not finish properly + #endif + + // bbackupd daemon will try to initialise timers itself + Timers::Cleanup(); + + // something to diff against (empty file doesn't work) + int fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); + TEST_THAT(fd > 0); + + char buffer[10000]; + memset(buffer, 0, sizeof(buffer)); + + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + int pid = start_internal_daemon(); + wait_for_backup_operation("internal daemon to run a sync"); + TEST_THAT(stop_internal_daemon(pid)); + + // two-second delay on the first read() of f1 + // should mean that a single keepalive is sent, + // and diff does not abort. + intercept_setup_delay("testfiles/TestDir1/spacetest/f1", + 0, 2000, SYS_read, 1); + TEST_THAT(unlink("testfiles/bbackupd.log") == 0); + + pid = start_internal_daemon(); + intercept_clear_setup(); + + fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); + TEST_THAT(fd > 0); + // write again, to update the file's timestamp + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + wait_for_backup_operation("internal daemon to sync " + "spacetest/f1"); + // can't test whether intercept was triggered, because + // it's in a different process. + // TEST_THAT(intercept_triggered()); + TEST_THAT(stop_internal_daemon(pid)); + + // check that keepalive was written to logs, and + // diff was not aborted, i.e. upload was a diff + FileStream fs("testfiles/bbackupd.log", O_RDONLY); + IOStreamGetLine reader(fs); + bool found1 = false; + + while (!reader.IsEOF()) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + if (line == "Send GetBlockIndexByName(0x3,\"f1\")") + { + found1 = true; + break; + } + } + + TEST_THAT(found1); + if (found1) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + std::string comp = "Receive Success(0x"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receiving stream, size 124", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Send GetIsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive IsAlive()", line); + + TEST_THAT(reader.GetLine(line)); + comp = "Send StoreFile(0x3,"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + comp = ",\"f1\")"; + std::string sub = line.substr(line.size() - comp.size()); + TEST_EQUAL_LINE(comp, sub, line); + std::string comp2 = ",0x0,"; + sub = line.substr(line.size() - comp.size() - + comp2.size() + 1, comp2.size()); + TEST_LINE(comp2 != sub, line); + } + + if (failures > 0) + { + // stop early to make debugging easier + Timers::Init(); + return 1; + } + + // four-second delay on first read() of f1 + // should mean that no keepalives were sent, + // because diff was immediately aborted + // before any matching blocks could be found. + intercept_setup_delay("testfiles/TestDir1/spacetest/f1", + 0, 4000, SYS_read, 1); + pid = start_internal_daemon(); + intercept_clear_setup(); + + fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); + TEST_THAT(fd > 0); + // write again, to update the file's timestamp + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + wait_for_backup_operation("internal daemon to sync " + "spacetest/f1 again"); + // can't test whether intercept was triggered, because + // it's in a different process. + // TEST_THAT(intercept_triggered()); + TEST_THAT(stop_internal_daemon(pid)); + + // check that the diff was aborted, i.e. upload was not a diff + found1 = false; + + while (!reader.IsEOF()) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + if (line == "Send GetBlockIndexByName(0x3,\"f1\")") + { + found1 = true; + break; + } + } + + TEST_THAT(found1); + if (found1) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + std::string comp = "Receive Success(0x"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receiving stream, size 124", line); + + // delaying for 4 seconds in one step means that + // the diff timer and the keepalive timer will + // both expire, and the diff timer is honoured first, + // so there will be no keepalives. + + TEST_THAT(reader.GetLine(line)); + comp = "Send StoreFile(0x3,"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + comp = ",0x0,\"f1\")"; + std::string sub = line.substr(line.size() - comp.size()); + TEST_EQUAL_LINE(comp, sub, line); + } + + if (failures > 0) + { + // stop early to make debugging easier + Timers::Init(); + return 1; + } + + intercept_setup_delay("testfiles/TestDir1/spacetest/f1", + 0, 1000, SYS_read, 3); + pid = start_internal_daemon(); + intercept_clear_setup(); + + fd = open("testfiles/TestDir1/spacetest/f1", O_WRONLY); + TEST_THAT(fd > 0); + // write again, to update the file's timestamp + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + wait_for_backup_operation("internal daemon to sync " + "spacetest/f1 again"); + // can't test whether intercept was triggered, because + // it's in a different process. + // TEST_THAT(intercept_triggered()); + TEST_THAT(stop_internal_daemon(pid)); + + // check that the diff was aborted, i.e. upload was not a diff + found1 = false; + + while (!reader.IsEOF()) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + if (line == "Send GetBlockIndexByName(0x3,\"f1\")") + { + found1 = true; + break; + } + } + + TEST_THAT(found1); + if (found1) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + std::string comp = "Receive Success(0x"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receiving stream, size 124", line); + + // delaying for 3 seconds in steps of 1 second + // means that the keepalive timer will expire 3 times, + // and on the 3rd time the diff timer will expire too. + // The diff timer is honoured first, so there will be + // only two keepalives. + + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Send GetIsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive IsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Send GetIsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive IsAlive()", line); + + // but two matching blocks should have been found + // already, so the upload should be a diff. + + TEST_THAT(reader.GetLine(line)); + comp = "Send StoreFile(0x3,"; + TEST_EQUAL_LINE(comp, line.substr(0, comp.size()), + line); + comp = ",\"f1\")"; + std::string sub = line.substr(line.size() - comp.size()); + TEST_EQUAL_LINE(comp, sub, line); + std::string comp2 = ",0x0,"; + sub = line.substr(line.size() - comp.size() - + comp2.size() + 1, comp2.size()); + TEST_LINE(comp2 != sub, line); + } + + if (failures > 0) + { + // stop early to make debugging easier + Timers::Init(); + return 1; + } + + intercept_setup_readdir_hook("testfiles/TestDir1/spacetest/d1", + readdir_test_hook_1); + + // time for at least two keepalives + readdir_stop_time = time(NULL) + 12 + 2; + + pid = start_internal_daemon(); + intercept_clear_setup(); + + std::string touchfile = + "testfiles/TestDir1/spacetest/d1/touch-me"; + + fd = open(touchfile.c_str(), O_CREAT | O_WRONLY); + TEST_THAT(fd > 0); + // write again, to update the file's timestamp + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + wait_for_backup_operation("internal daemon to scan " + "spacetest/d1"); + // can't test whether intercept was triggered, because + // it's in a different process. + // TEST_THAT(intercept_triggered()); + TEST_THAT(stop_internal_daemon(pid)); + + // check that keepalives were sent during the dir search + found1 = false; + + // skip to next login + while (!reader.IsEOF()) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + if (line == "Send ListDirectory(0x3,0xffff,0xc,true)") + { + found1 = true; + break; + } + } + + TEST_THAT(found1); + if (found1) + { + found1 = false; + + while (!reader.IsEOF()) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + if (line == "Send ListDirectory(0x3,0xffffffff,0xc,true)") + { + found1 = true; + break; + } + } + } + + if (found1) + { + std::string line; + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive Success(0x3)", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receiving stream, size 425", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Send GetIsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive IsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Send GetIsAlive()", line); + TEST_THAT(reader.GetLine(line)); + TEST_EQUAL("Receive IsAlive()", line); + } + + if (failures > 0) + { + // stop early to make debugging easier + Timers::Init(); + return 1; + } + + TEST_THAT(unlink(touchfile.c_str()) == 0); + + // restore timers for rest of tests + Timers::Init(); + } +#endif // PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + std::string cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if(bbackupd_pid > 0) + { + printf("\n==== Testing that backup pauses when " + "store is full\n"); + + // wait for files to be uploaded + BOX_TRACE("Waiting for all outstanding files to be uploaded") + wait_for_sync_end(); + BOX_TRACE("done.") + + // Set limit to something very small + // 26 blocks will be used at this point. + // (12 files + location * 2 for raidfile) + // 20 is what we'll need in a minute + // set soft limit to 0 to ensure that all deleted files + // are deleted immediately by housekeeping + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " + "testfiles/bbstored.conf setlimit 01234567 0B 20B") + == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Unpack some more files + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/spacetest2.tgz " + "-C testfiles/TestDir1") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/spacetest2.tgz " + "| ( cd testfiles/TestDir1 && tar xf - )") == 0); + #endif + + // Delete a file and a directory + TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); + TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); + + // The following files should be on the server: + // 00000001 -d---- 00002 (root) + // 00000002 -d---- 00002 Test1 + // 00000003 -d---- 00002 Test1/spacetest + // 00000004 f-X--- 00002 Test1/spacetest/f1 + // 00000005 f----- 00002 Test1/spacetest/f2 + // 00000006 -d---- 00002 Test1/spacetest/d1 + // 00000007 f----- 00002 Test1/spacetest/d1/f3 + // 00000008 f----- 00002 Test1/spacetest/d1/f4 + // 00000009 -d---- 00002 Test1/spacetest/d2 + // 0000000a -d---- 00002 Test1/spacetest/d3 + // 0000000b -d---- 00002 Test1/spacetest/d3/d4 + // 0000000c f----- 00002 Test1/spacetest/d3/d4/f5 + // 0000000d -d---- 00002 Test1/spacetest/d6 + // 0000000e -dX--- 00002 Test1/spacetest/d7 + // This is 28 blocks total, of which 2 in deleted files + // and 18 in directories. Note that f1 and d7 may or may + // not be deleted yet. + // + // spacetest1 + spacetest2 = 16 files = 32 blocks with raidfile + // minus one file and one dir is 28 blocks + // + // d2/f6, d6/d8 and d6/d8/f7 are new + // even if the client marks f1 and d7 as deleted, and + // housekeeping deleted them, the backup cannot complete + // if the limit is 20 blocks. + + BOX_TRACE("Waiting for sync for bbackupd to notice that the " + "store is full"); + wait_for_sync_end(); + BOX_TRACE("Sync finished."); + + BOX_TRACE("Compare to check that there are differences"); + int compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query0a.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + BOX_TRACE("Compare finished."); + + // Check that the notify script was run + TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1")); + // But only once! + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); + + // Kill the daemon + terminate_bbackupd(bbackupd_pid); + + wait_for_operation(5, "housekeeping to remove the " + "deleted files"); + + // This removes f1 and d7, which were previously marked + // as deleted, so total usage drops by 4 blocks to 24. + + // BLOCK + { + std::auto_ptr client = + ConnectAndLogin(context, 0 /* read-write */); + + std::auto_ptr usage( + client->QueryGetAccountUsage()); + TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), + "blocks used"); + TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), + "deleted blocks"); + TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), + "directory blocks"); + + client->QueryFinished(); + sSocket.Close(); + } + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + + // ensure time is different to refresh the cache + ::safe_sleep(1); + + BOX_TRACE("Restart bbackupd with more exclusions"); + // Start again with a new config that excludes d3 and f2, + // and hence also d3/d4 and d3/d4/f5. bbackupd should mark + // them as deleted and housekeeping should clean up, + // making space to upload the new files. + // total required: (13-2-4+3)*2 = 20 blocks + /* + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd-exclude.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + */ + + BackupDaemon bbackupd; + bbackupd.Configure("testfiles/bbackupd-exclude.conf"); + bbackupd.InitCrypto(); + BOX_TRACE("done."); + + // Should be marked as deleted by this run + // wait_for_sync_end(); + { + // Logging::Guard guard(Log::ERROR); + bbackupd.RunSyncNow(); + } + + TEST_THAT(bbackupd.StorageLimitExceeded()); + + // Check that the notify script was run + // TEST_THAT(TestFileExists("testfiles/notifyran.store-full.2")); + // But only twice! + // TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.3")); + + // All these should be marked as deleted but hopefully + // not removed by housekeeping yet: + // f1 deleted + // f2 excluded + // d1 excluded (why?) + // d1/f3 excluded (why?) + // d3 excluded + // d3/d4 excluded + // d3/d4/f5 excluded + // d7 deleted + // Careful with timing here, these files will be removed by + // housekeeping the next time it runs. On Win32, housekeeping + // runs immediately after disconnect, but only if enough time + // has elapsed since the last housekeeping. Since the + // backup run closely follows the last one, housekeeping + // should not run afterwards. On other platforms, we want to + // get in immediately after the backup and hope that + // housekeeping doesn't beat us to it. + + BOX_TRACE("Find out whether bbackupd marked files as deleted"); + { + std::auto_ptr client = + ConnectAndLogin(context, 0 /* read-write */); + + std::auto_ptr rootDir = + ReadDirectory(*client, + BackupProtocolClientListDirectory::RootDirectory); + + int64_t testDirId = SearchDir(*rootDir, "Test1"); + TEST_THAT(testDirId != 0); + + std::auto_ptr Test1_dir = + ReadDirectory(*client, testDirId); + + int64_t spacetestDirId = SearchDir(*Test1_dir, + "spacetest"); + TEST_THAT(spacetestDirId != 0); + + std::auto_ptr spacetest_dir = + ReadDirectory(*client, spacetestDirId); + + // these files were deleted before, they should be + // long gone by now + + TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); + + // these files have just been deleted, because + // they are excluded by the new configuration. + // but housekeeping should not have run yet + + TEST_THAT(test_entry_deleted(*spacetest_dir, "f2")); + TEST_THAT(test_entry_deleted(*spacetest_dir, "d3")); + + int64_t d3_id = SearchDir(*spacetest_dir, "d3"); + TEST_THAT(d3_id != 0); + + std::auto_ptr d3_dir = + ReadDirectory(*client, d3_id); + TEST_THAT(test_entry_deleted(*d3_dir, "d4")); + + int64_t d4_id = SearchDir(*d3_dir, "d4"); + TEST_THAT(d4_id != 0); + + std::auto_ptr d4_dir = + ReadDirectory(*client, d4_id); + TEST_THAT(test_entry_deleted(*d4_dir, "f5")); + + std::auto_ptr usage( + client->QueryGetAccountUsage()); + TEST_EQUAL_LINE(24, usage->GetBlocksUsed(), + "blocks used"); + TEST_EQUAL_LINE(4, usage->GetBlocksInDeletedFiles(), + "deleted blocks"); + TEST_EQUAL_LINE(16, usage->GetBlocksInDirectories(), + "directory blocks"); + // d1/f3 and d1/f4 are the only two files on the + // server which are not deleted, they use 2 blocks + // each, the rest is directories and 2 deleted files + // (f1 and d3/d4/f5) + + // Log out. + client->QueryFinished(); + sSocket.Close(); + } + BOX_TRACE("done."); + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + + wait_for_operation(5, "housekeeping to remove the " + "deleted files"); + + BOX_TRACE("Check that the files were removed"); + { + std::auto_ptr client = + ConnectAndLogin(context, 0 /* read-write */); + + std::auto_ptr rootDir = + ReadDirectory(*client, + BackupProtocolClientListDirectory::RootDirectory); + + int64_t testDirId = SearchDir(*rootDir, "Test1"); + TEST_THAT(testDirId != 0); + + std::auto_ptr Test1_dir = + ReadDirectory(*client, testDirId); + + int64_t spacetestDirId = SearchDir(*Test1_dir, + "spacetest"); + TEST_THAT(spacetestDirId != 0); + + std::auto_ptr spacetest_dir = + ReadDirectory(*client, spacetestDirId); + + TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "f2") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); + + std::auto_ptr usage( + client->QueryGetAccountUsage()); + TEST_EQUAL_LINE(16, usage->GetBlocksUsed(), + "blocks used"); + TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), + "deleted blocks"); + TEST_EQUAL_LINE(12, usage->GetBlocksInDirectories(), + "directory blocks"); + // d1/f3 and d1/f4 are the only two files on the + // server, they use 2 blocks each, the rest is + // directories. + + // Log out. + client->QueryFinished(); + sSocket.Close(); + } + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + + // Need 22 blocks free to upload everything + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " + "testfiles/bbstored.conf setlimit 01234567 0B 22B") + == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Run another backup, now there should be enough space + // for everything we want to upload. + { + Logging::Guard guard(Log::ERROR); + bbackupd.RunSyncNow(); + } + TEST_THAT(!bbackupd.StorageLimitExceeded()); + + // Check that the contents of the store are the same + // as the contents of the disc + // (-a = all, -c = give result in return code) + BOX_TRACE("Check that all files were uploaded successfully"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd-exclude.conf " + "-l testfiles/query1.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + BOX_TRACE("done."); + + // BLOCK + { + std::auto_ptr client = + ConnectAndLogin(context, 0 /* read-write */); + + std::auto_ptr usage( + client->QueryGetAccountUsage()); + TEST_EQUAL_LINE(22, usage->GetBlocksUsed(), + "blocks used"); + TEST_EQUAL_LINE(0, usage->GetBlocksInDeletedFiles(), + "deleted blocks"); + TEST_EQUAL_LINE(14, usage->GetBlocksInDirectories(), + "directory blocks"); + // d2/f6, d6/d8 and d6/d8/f7 are new + // i.e. 2 new files, 1 new directory + + client->QueryFinished(); + sSocket.Close(); + } + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + + // Put the limit back + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " + "testfiles/bbstored.conf setlimit 01234567 " + "1000B 2000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Start again with the old config + BOX_TRACE("Restart bbackupd with original configuration"); + // terminate_bbackupd(); + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + BOX_TRACE("done."); + + // unpack the initial files again + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " + "-C testfiles") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " + "| ( cd testfiles && tar xf - )") == 0); + #endif + + wait_for_backup_operation("bbackupd to upload more files"); + + // Check that the contents of the store are the same + // as the contents of the disc + // (-a = all, -c = give result in return code) + BOX_TRACE("Check that all files were uploaded successfully"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query1.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + 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; + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + } + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + #ifndef WIN32 // requires fork + printf("\n==== Testing that bbackupd responds correctly to " + "connection failure\n"); + + { + // Kill the daemons + terminate_bbackupd(bbackupd_pid); + test_kill_bbstored(); + + // create a new file to force an upload + + const char* new_file = "testfiles/TestDir1/force-upload-2"; + int fd = open(new_file, + O_CREAT | O_EXCL | O_WRONLY, 0700); + if (fd <= 0) + { + perror(new_file); + } + TEST_THAT(fd > 0); + + const char* control_string = "whee!\n"; + TEST_THAT(write(fd, control_string, + strlen(control_string)) == + (int)strlen(control_string)); + close(fd); + + // sleep to make it old enough to upload + safe_sleep(4); + + class MyHook : public BackupStoreContext::TestHook + { + virtual std::auto_ptr StartCommand( + BackupProtocolObject& rCommand) + { + if (rCommand.GetType() == + BackupProtocolServerStoreFile::TypeID) + { + // terminate badly + THROW_EXCEPTION(CommonException, + Internal); + } + return std::auto_ptr(); + } + }; + MyHook hook; + + bbstored_pid = fork(); + + if (bbstored_pid < 0) + { + BOX_LOG_SYS_ERROR("failed to fork()"); + return 1; + } + + if (bbstored_pid == 0) + { + // in fork child + TEST_THAT(setsid() != -1); + + if (!Logging::IsEnabled(Log::TRACE)) + { + Logging::SetGlobalLevel(Log::NOTHING); + } + + // BackupStoreDaemon must be destroyed before exit(), + // to avoid memory leaks being reported. + { + BackupStoreDaemon bbstored; + bbstored.SetTestHook(hook); + bbstored.SetRunInForeground(true); + bbstored.Main("testfiles/bbstored.conf"); + } + + Timers::Cleanup(); // avoid memory leaks + exit(0); + } + + // in fork parent + bbstored_pid = WaitForServerStartup("testfiles/bbstored.pid", + bbstored_pid); + + TEST_THAT(::system("rm -f testfiles/notifyran.store-full.*") == 0); + + // Ignore SIGPIPE so that when the connection is broken, + // the daemon doesn't terminate. + ::signal(SIGPIPE, SIG_IGN); + + { + Log::Level newLevel = Logging::GetGlobalLevel(); + + if (!Logging::IsEnabled(Log::TRACE)) + { + newLevel = Log::NOTHING; + } + + Logging::Guard guard(newLevel); + + BackupDaemon bbackupd; + bbackupd.Configure("testfiles/bbackupd.conf"); + bbackupd.InitCrypto(); + bbackupd.RunSyncNowWithExceptionHandling(); + } + + ::signal(SIGPIPE, SIG_DFL); + + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.1")); + + test_kill_bbstored(true); + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + + TEST_THAT(test_run_bbstored() == 0); + + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + } + #endif // !WIN32 + + #ifndef WIN32 + printf("\n==== Testing that absolute symlinks are not followed " + "during restore\n"); + + { + #define SYM_DIR "testfiles" DIRECTORY_SEPARATOR "TestDir1" \ + DIRECTORY_SEPARATOR "symlink_test" + + TEST_THAT(::mkdir(SYM_DIR, 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a", 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir", 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "b", 0777) == 0); + + FILE* fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir" + DIRECTORY_SEPARATOR "content", "w"); + TEST_THAT(fp != NULL); + fputs("before\n", fp); + fclose(fp); + + char buf[PATH_MAX]; + TEST_THAT(getcwd(buf, sizeof(buf)) == buf); + std::string path = buf; + path += DIRECTORY_SEPARATOR SYM_DIR + DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir"; + TEST_THAT(symlink(path.c_str(), SYM_DIR + DIRECTORY_SEPARATOR "b" + DIRECTORY_SEPARATOR "link") == 0); + + // also test symlink-to-self loop does not break restore + TEST_THAT(symlink("self", SYM_DIR "/self") == 0); + + wait_for_operation(4, "symlinks to be old enough"); + sync_and_wait(); + + // Check that the backup was successful, i.e. no differences + int compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query1.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // now stop bbackupd and update the test file, + // make the original directory unreadable + terminate_bbackupd(bbackupd_pid); + + fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir" + DIRECTORY_SEPARATOR "content", "w"); + TEST_THAT(fp != NULL); + fputs("after\n", fp); + fclose(fp); + + TEST_THAT(chmod(SYM_DIR, 0) == 0); + + // check that we can restore it + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-Wwarning \"restore Test1 testfiles/restore-symlink\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + // make it accessible again + TEST_THAT(chmod(SYM_DIR, 0755) == 0); + + // check that the original file was not overwritten + FileStream fs(SYM_DIR "/a/subdir/content"); + IOStreamGetLine gl(fs); + std::string line; + TEST_THAT(gl.GetLine(line)); + TEST_THAT(line != "before"); + TEST_EQUAL("after", line); + + #undef SYM_DIR + + /* + // This is not worth testing or fixing. + // + #ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + printf("\n==== Testing that symlinks to other filesystems " + "can be backed up as roots\n"); + + intercept_setup_lstat_post_hook(lstat_test_post_hook); + TEST_THAT(symlink("TestDir1", "testfiles/symlink-to-TestDir1") + == 0); + + struct stat stat_st, lstat_st; + TEST_THAT(stat("testfiles/symlink-to-TestDir1", &stat_st) == 0); + TEST_THAT(lstat("testfiles/symlink-to-TestDir1", &lstat_st) == 0); + TEST_EQUAL_LINE((stat_st.st_dev ^ 0xFFFF), lstat_st.st_dev, + "stat vs lstat"); + + BackupDaemon bbackupd; + bbackupd.Configure("testfiles/bbackupd-symlink.conf"); + bbackupd.InitCrypto(); + bbackupd.RunSyncNow(); + intercept_clear_setup(); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query0a.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // and again using the symlink during compare + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd-symlink.conf " + "-l testfiles/query0a.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + #endif + */ + + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + } + #endif // !WIN32 + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + printf("\n==== Testing that redundant locations are deleted on time\n"); + + // unpack the files for the redundant location test + TEST_THAT(::system("rm -rf testfiles/TestDir2") == 0); + 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 + + // BLOCK + { + // Kill the daemon + terminate_bbackupd(bbackupd_pid); + + // Start it with a config that has a temporary location + // that will be created on the server + 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); + ::safe_sleep(1); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + sync_and_wait(); + + { + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + std::auto_ptr dir = + ReadDirectory(*client, + BackupProtocolClientListDirectory::RootDirectory); + int64_t testDirId = SearchDir(*dir, "Test2"); + TEST_THAT(testDirId != 0); + + client->QueryFinished(); + sSocket.Close(); + } + + // Kill the daemon + terminate_bbackupd(bbackupd_pid); + + // Start it again with the normal config (no Test2) + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + + ::safe_sleep(1); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Test2 should be deleted after 10 seconds (4 runs) + wait_for_sync_end(); + wait_for_sync_end(); + wait_for_sync_end(); + + // not yet! should still be there + + { + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + std::auto_ptr dir = + ReadDirectory(*client, + BackupProtocolClientListDirectory::RootDirectory); + int64_t testDirId = SearchDir(*dir, "Test2"); + TEST_THAT(testDirId != 0); + + client->QueryFinished(); + sSocket.Close(); + } + + wait_for_sync_end(); + + // NOW it should be gone + + { + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + std::auto_ptr root_dir = + ReadDirectory(*client, + BackupProtocolClientListDirectory::RootDirectory); + + TEST_THAT(test_entry_deleted(*root_dir, "Test2")); + + client->QueryFinished(); + sSocket.Close(); + } + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if(bbackupd_pid > 0) + { + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + printf("\n==== Check that read-only directories and " + "their contents can be restored.\n"); + + int compareReturnValue; + + { + #ifdef WIN32 + TEST_THAT(::system("chmod 0555 testfiles/" + "TestDir1/x1") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/x1", + 0555) == 0); + #endif + + wait_for_sync_end(); // too new + wait_for_sync_end(); // should be backed up now + + compareReturnValue = ::system(BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"compare -cEQ Test1 testfiles/TestDir1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // check that we can restore it + compareReturnValue = ::system(BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"restore Test1 testfiles/restore1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // check that it restored properly + compareReturnValue = ::system(BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"compare -cEQ Test1 testfiles/restore1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // put the permissions back to sensible values + #ifdef WIN32 + TEST_THAT(::system("chmod 0755 testfiles/" + "TestDir1/x1") == 0); + TEST_THAT(::system("chmod 0755 testfiles/" + "restore1/x1") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/x1", + 0755) == 0); + TEST_THAT(chmod("testfiles/restore1/x1", + 0755) == 0); + #endif + + } + +#ifdef WIN32 + printf("\n==== Check that filenames in UTF-8 " + "can be backed up\n"); + + // We have no guarantee that a random Unicode string can be + // represented in the user's character set, so we go the + // other way, taking three random characters from the + // character set and converting them to Unicode. + // + // We hope that these characters are valid in most + // character sets, but they probably are not in multibyte + // character sets such as Shift-JIS, GB2312, etc. This test + // will probably fail if your system locale is set to + // Chinese, Japanese, etc. where one of these character + // sets is used by default. You can check the character + // set for your system in Control Panel -> Regional + // Options -> General -> Language Settings -> Set Default + // (System Locale). Because bbackupquery converts from + // system locale to UTF-8 via the console code page + // (which you can check from the Command Prompt with "chcp") + // they must also be valid in your code page (850 for + // Western Europe). + // + // In ISO-8859-1 (Danish locale) they are three Danish + // accented characters, which are supported in code page + // 850. Depending on your locale, YYMV (your yak may vomit). + + std::string foreignCharsNative("\x91\x9b\x86"); + std::string foreignCharsUnicode; + TEST_THAT(ConvertConsoleToUtf8(foreignCharsNative.c_str(), + foreignCharsUnicode)); + + std::string basedir("testfiles/TestDir1"); + std::string dirname("test" + foreignCharsUnicode + "testdir"); + std::string dirpath(basedir + "/" + dirname); + TEST_THAT(mkdir(dirpath.c_str(), 0) == 0); + + std::string filename("test" + foreignCharsUnicode + "testfile"); + std::string filepath(dirpath + "/" + filename); + + char cwdbuf[1024]; + TEST_THAT(getcwd(cwdbuf, sizeof(cwdbuf)) == cwdbuf); + std::string cwd = cwdbuf; + + // Test that our emulated chdir() works properly + // with relative and absolute paths + TEST_THAT(::chdir(dirpath.c_str()) == 0); + TEST_THAT(::chdir("../../..") == 0); + TEST_THAT(::chdir(cwd.c_str()) == 0); + + // Check that it can be converted to the system encoding + // (which is what is needed on the command line) + std::string systemDirName; + TEST_THAT(ConvertEncoding(dirname.c_str(), CP_UTF8, + systemDirName, CP_ACP)); + + std::string systemFileName; + TEST_THAT(ConvertEncoding(filename.c_str(), CP_UTF8, + systemFileName, CP_ACP)); + + // Check that it can be converted to the console encoding + // (which is what we will see in the output) + std::string consoleDirName; + TEST_THAT(ConvertUtf8ToConsole(dirname.c_str(), + consoleDirName)); + + std::string consoleFileName; + TEST_THAT(ConvertUtf8ToConsole(filename.c_str(), + consoleFileName)); + + // test that bbackupd will let us lcd into the local + // directory using a relative path + std::string command = BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"lcd testfiles/TestDir1/" + systemDirName + "\" " + "quit"; + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + // and back out again + command = BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"lcd testfiles/TestDir1/" + systemDirName + "\" " + "\"lcd ..\" quit"; + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + // and using an absolute path + command = BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"lcd " + cwd + "/testfiles/TestDir1/" + + systemDirName + "\" quit"; + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + // and back out again + command = BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "\"lcd " + cwd + "/testfiles/TestDir1/" + + systemDirName + "\" " + "\"lcd ..\" quit"; + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + { + FileStream fs(filepath.c_str(), O_CREAT | O_RDWR); + + std::string data("hello world\n"); + fs.Write(data.c_str(), data.size()); + TEST_EQUAL_LINE(12, fs.GetPosition(), + "FileStream position"); + fs.Close(); + } + + wait_for_backup_operation("upload of file with unicode name"); + + // Compare to check that the file was uploaded + compareReturnValue = ::system(BBACKUPQUERY " -Wwarning " + "-c testfiles/bbackupd.conf \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Check that we can find it in directory listing + { + std::auto_ptr client = + ConnectAndLogin(context, 0); + + std::auto_ptr dir = ReadDirectory( + *client, + BackupProtocolClientListDirectory::RootDirectory); + + int64_t baseDirId = SearchDir(*dir, "Test1"); + TEST_THAT(baseDirId != 0); + dir = ReadDirectory(*client, baseDirId); + + int64_t testDirId = SearchDir(*dir, dirname.c_str()); + TEST_THAT(testDirId != 0); + dir = ReadDirectory(*client, testDirId); + + TEST_THAT(SearchDir(*dir, filename.c_str()) != 0); + // Log out + client->QueryFinished(); + sSocket.Close(); + } + + // Check that bbackupquery shows the dir in console encoding + command = BBACKUPQUERY " -Wwarning " + "-c testfiles/bbackupd.conf " + "-q \"list Test1\" quit"; + pid_t bbackupquery_pid; + std::auto_ptr queryout; + queryout = LocalProcessStream(command.c_str(), + bbackupquery_pid); + TEST_THAT(queryout.get() != NULL); + TEST_THAT(bbackupquery_pid != -1); + + IOStreamGetLine reader(*queryout); + std::string line; + bool found = false; + while (!reader.IsEOF()) + { + TEST_THAT(reader.GetLine(line)); + if (line.find(consoleDirName) != std::string::npos) + { + found = true; + } + } + TEST_THAT(!(queryout->StreamDataLeft())); + TEST_THAT(reader.IsEOF()); + TEST_THAT(found); + queryout->Close(); + + // Check that bbackupquery can list the dir when given + // on the command line in system encoding, and shows + // the file in console encoding + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"list Test1/" + systemDirName + "\" quit"; + queryout = LocalProcessStream(command.c_str(), + bbackupquery_pid); + TEST_THAT(queryout.get() != NULL); + TEST_THAT(bbackupquery_pid != -1); + + IOStreamGetLine reader2(*queryout); + found = false; + while (!reader2.IsEOF()) + { + TEST_THAT(reader2.GetLine(line)); + if (line.find(consoleFileName) != std::string::npos) + { + found = true; + } + } + TEST_THAT(!(queryout->StreamDataLeft())); + TEST_THAT(reader2.IsEOF()); + TEST_THAT(found); + queryout->Close(); + + // Check that bbackupquery can compare the dir when given + // on the command line in system encoding. + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"compare -cEQ Test1/" + systemDirName + + " testfiles/TestDir1/" + systemDirName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + + // Check that bbackupquery can restore the dir when given + // on the command line in system encoding. + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"restore Test1/" + systemDirName + + " testfiles/restore-" + systemDirName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + + // Compare to make sure it was restored properly. + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"compare -cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + + std::string fileToUnlink = "testfiles/restore-" + + dirname + "/" + filename; + TEST_THAT(::unlink(fileToUnlink.c_str()) == 0); + + // Check that bbackupquery can get the file when given + // on the command line in system encoding. + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"get Test1/" + systemDirName + "/" + + systemFileName + " " + "testfiles/restore-" + + systemDirName + "/" + systemFileName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // And after changing directory to a relative path + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning " + "\"lcd testfiles\" " + "\"cd Test1/" + systemDirName + "\" " + + "\"get " + systemFileName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); + + // cannot overwrite a file that exists, so delete it + std::string tmp = "testfiles/" + filename; + TEST_THAT(::unlink(tmp.c_str()) == 0); + + // And after changing directory to an absolute path + command = BBACKUPQUERY " -c testfiles/bbackupd.conf -Wwarning " + "\"lcd " + cwd + "/testfiles\" " + "\"cd Test1/" + systemDirName + "\" " + + "\"get " + systemFileName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Command_OK); + TestRemoteProcessMemLeaks("testfiles/bbackupquery.memleaks"); + + // Compare to make sure it was restored properly. + // The Get command does not restore attributes, so + // we must compare without them (-A) to succeed. + command = BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-Wwarning \"compare -cAEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName + "\" quit"; + + compareReturnValue = ::system(command.c_str()); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + + // Compare without attributes. This should fail. + command = BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-Werror \"compare -cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName + "\" quit"; + compareReturnValue = ::system(command.c_str()); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); +#endif // WIN32 + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Check that SyncAllowScript is executed and can " + "pause backup\n"); + fflush(stdout); + + { + wait_for_sync_end(); + // we now have 3 seconds before bbackupd + // runs the SyncAllowScript again. + + const char* sync_control_file = "testfiles" + DIRECTORY_SEPARATOR "syncallowscript.control"; + int fd = open(sync_control_file, + O_CREAT | O_EXCL | O_WRONLY, 0700); + if (fd <= 0) + { + perror(sync_control_file); + } + TEST_THAT(fd > 0); + + const char* control_string = "10\n"; + TEST_THAT(write(fd, control_string, + strlen(control_string)) == + (int)strlen(control_string)); + close(fd); + + // this will pause backups, bbackupd will check + // every 10 seconds to see if they are allowed again. + + const char* new_test_file = "testfiles" + DIRECTORY_SEPARATOR "TestDir1" + DIRECTORY_SEPARATOR "Added_During_Pause"; + fd = open(new_test_file, + O_CREAT | O_EXCL | O_WRONLY, 0700); + if (fd <= 0) + { + perror(new_test_file); + } + TEST_THAT(fd > 0); + close(fd); + + struct stat st; + + // next poll should happen within the next + // 5 seconds (normally about 3 seconds) + + wait_for_operation(1, "2 seconds before next run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.1", &st) != 0); + wait_for_operation(4, "2 seconds after run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.1", &st) == 0); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) != 0); + + // next poll should happen within the next + // 10 seconds (normally about 8 seconds) + + wait_for_operation(6, "2 seconds before next run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) != 0); + wait_for_operation(4, "2 seconds after run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) == 0); + + // bbackupquery compare might take a while + // on slow machines, so start the timer now + long start_time = time(NULL); + + // check that no backup has run (compare fails) + compareReturnValue = ::system(BBACKUPQUERY " " + "-Werror " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3.log " + "\"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(unlink(sync_control_file) == 0); + wait_for_sync_start(); + long end_time = time(NULL); + + long wait_time = end_time - start_time + 2; + + // should be about 10 seconds + if (wait_time < 8 || wait_time > 12) + { + printf("Waited for %ld seconds, should have " + "been %s", wait_time, control_string); + } + + TEST_THAT(wait_time >= 8); + TEST_THAT(wait_time <= 12); + + wait_for_sync_end(); + // check that backup has run (compare succeeds) + compareReturnValue = ::system(BBACKUPQUERY " " + "-Wwarning " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3a.log " + "\"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + if (failures > 0) + { + // stop early to make debugging easier + return 1; + } + } + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Delete file and update another, " + "create symlink.\n"); + + // Delete a file + TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + + #ifndef WIN32 + // New symlink + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/symlink-to-dir") == 0); + #endif + + // Update a file (will be uploaded as a diff) + { + // Check that the file is over the diffing + // threshold in the bbackupd.conf file + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + > 1024); + + // Add a bit to the end + FILE *f = ::fopen("testfiles/TestDir1/f45.df", "a"); + TEST_THAT(f != 0); + ::fprintf(f, "EXTRA STUFF"); + ::fclose(f); + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + > 1024); + } + + // wait long enough for new files to be old enough to backup + wait_for_operation(5, "new files to be old enough"); + + // wait for backup daemon to do it's stuff + sync_and_wait(); + + // compare to make sure that it worked + compareReturnValue = ::system(BBACKUPQUERY " -Wwarning " + "-c testfiles/bbackupd.conf " + "-l testfiles/query2.log " + "\"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Try a quick compare, just for fun + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query2q.log " + "-Wwarning \"compare -acqQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Check that store errors are reported neatly + printf("\n==== Create store error\n"); + TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*") + == 0); + + // break the store + 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); + + // Create a file to trigger an upload + { + int fd1 = open("testfiles/TestDir1/force-upload", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "just do it", 10) == 10); + TEST_THAT(close(fd1) == 0); + } + + wait_for_operation(4, "bbackupd to try to access the store"); + + // Check that an error was reported just once + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); + // Now kill bbackupd and start one that's running in + // snapshot mode, check that it automatically syncs after + // an error, without waiting for another sync command. + terminate_bbackupd(bbackupd_pid); + std::string cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd-snapshot.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + sync_and_wait(); + + // Check that the error was reported once more + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.3")); + + // Fix the store (so that bbackupquery compare works) + TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", + "testfiles/0_0/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", + "testfiles/0_1/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", + "testfiles/0_2/backup/01234567/info.rf") == 0); + + int store_fixed_time = time(NULL); + + // Check that we DO get errors on compare (cannot do this + // until after we fix the store, which creates a race) + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Test initial state + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Set a tag for the notify script to distinguish from + // previous runs. + { + int fd1 = open("testfiles/notifyscript.tag", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "wait-snapshot", 13) == 13); + TEST_THAT(close(fd1) == 0); + } + + // bbackupd should pause for about 90 seconds from + // store_fixed_time, so check that it hasn't run after + // 85 seconds after store_fixed_time + wait_for_operation(85 - time(NULL) + store_fixed_time, + "just before bbackupd recovers"); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Should not have backed up, should still get errors + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // wait another 10 seconds, bbackup should have run + wait_for_operation(10, "bbackupd to recover"); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Check that it did get uploaded, and we have no more errors + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); + + // Stop the snapshot bbackupd + terminate_bbackupd(bbackupd_pid); + + // Break the store again + 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); + + // Modify a file to trigger an upload + { + int fd1 = open("testfiles/TestDir1/force-upload", + O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "and again", 9) == 9); + TEST_THAT(close(fd1) == 0); + } + + // Restart the old bbackupd, in automatic mode + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + ::safe_sleep(1); + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + sync_and_wait(); + + // Fix the store again + TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", + "testfiles/0_0/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", + "testfiles/0_1/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", + "testfiles/0_2/backup/01234567/info.rf") == 0); + + store_fixed_time = time(NULL); + + // Check that we DO get errors on compare (cannot do this + // until after we fix the store, which creates a race) + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Test initial state + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Set a tag for the notify script to distinguist from + // previous runs. + { + int fd1 = open("testfiles/notifyscript.tag", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "wait-automatic", 14) == 14); + TEST_THAT(close(fd1) == 0); + } + + // bbackupd should pause for about 90 seconds from + // store_fixed_time, so check that it hasn't run after + // 85 seconds from store_fixed_time + wait_for_operation(85 - time(NULL) + store_fixed_time, + "just before bbackupd recovers"); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Should not have backed up, should still get errors + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // wait another 10 seconds, bbackup should have run + wait_for_operation(10, "bbackupd to recover"); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Check that it did get uploaded, and we have no more errors + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3b.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Bad case: delete a file/symlink, replace it with a directory + printf("\n==== Replace symlink with directory, " + "add new directory\n"); + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") + == 0); + #endif + + TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) + == 0); + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) + == 0); + + // NOTE: create a file within the directory to + // avoid deletion by the housekeeping process later + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file/contents") + == 0); + #endif + + wait_for_backup_operation("bbackupd to sync the changes"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3c.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // And the inverse, replace a directory with a file/symlink + printf("\n==== Replace directory with symlink\n"); + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" + "/contents") == 0); + #endif + + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file") == 0); + #endif + + wait_for_backup_operation("bbackupd to sync the changes"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3d.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // And then, put it back to how it was before. + printf("\n==== Replace symlink with directory " + "(which was a symlink)\n"); + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1" + "/dir-to-file") == 0); + #endif + + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", + 0755) == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file/contents2") + == 0); + #endif + + wait_for_backup_operation("bbackupd to sync the changes"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3e.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // And finally, put it back to how it was before + // it was put back to how it was before + // This gets lots of nasty things in the store with + // directories over other old directories. + printf("\n==== Put it all back to how it was\n"); + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" + "/contents2") == 0); + #endif + + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file") == 0); + #endif + + wait_for_backup_operation("bbackupd to sync the changes"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3f.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // rename an untracked file over an + // existing untracked file + printf("\n==== Rename over existing untracked file\n"); + int fd1 = open("testfiles/TestDir1/untracked-1", + O_CREAT | O_EXCL | O_WRONLY, 0700); + int fd2 = open("testfiles/TestDir1/untracked-2", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(fd2 > 0); + TEST_THAT(write(fd1, "hello", 5) == 5); + TEST_THAT(close(fd1) == 0); + safe_sleep(1); + TEST_THAT(write(fd2, "world", 5) == 5); + TEST_THAT(close(fd2) == 0); + TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-1")); + TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-2")); + + // back up both files + wait_for_operation(5, "untracked files to be old enough"); + wait_for_backup_operation("bbackupd to sync the " + "untracked files"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3g.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + #ifdef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/untracked-2") + == 0); + #endif + + TEST_THAT(::rename("testfiles/TestDir1/untracked-1", + "testfiles/TestDir1/untracked-2") == 0); + TEST_THAT(!TestFileExists("testfiles/TestDir1/untracked-1")); + TEST_THAT( TestFileExists("testfiles/TestDir1/untracked-2")); + + wait_for_backup_operation("bbackupd to sync the untracked " + "files again"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3g.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // case which went wrong: rename a tracked file over an + // existing tracked file + printf("\n==== Rename over existing tracked file\n"); + fd1 = open("testfiles/TestDir1/tracked-1", + O_CREAT | O_EXCL | O_WRONLY, 0700); + fd2 = open("testfiles/TestDir1/tracked-2", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(fd2 > 0); + char buffer[1024]; + TEST_THAT(write(fd1, "hello", 5) == 5); + TEST_THAT(write(fd1, buffer, sizeof(buffer)) == sizeof(buffer)); + TEST_THAT(close(fd1) == 0); + safe_sleep(1); + TEST_THAT(write(fd2, "world", 5) == 5); + TEST_THAT(write(fd2, buffer, sizeof(buffer)) == sizeof(buffer)); + TEST_THAT(close(fd2) == 0); + TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-1")); + TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-2")); + + // wait for them to be old enough to back up + wait_for_operation(5, "tracked files to be old enough"); + + // back up both files + sync_and_wait(); + + // compare to make sure that it worked + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3h.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + #ifdef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/tracked-2") + == 0); + #endif + + TEST_THAT(::rename("testfiles/TestDir1/tracked-1", + "testfiles/TestDir1/tracked-2") == 0); + TEST_THAT(!TestFileExists("testfiles/TestDir1/tracked-1")); + TEST_THAT( TestFileExists("testfiles/TestDir1/tracked-2")); + + wait_for_backup_operation("bbackupd to sync the tracked " + "files again"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3i.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // case which went wrong: rename a tracked file + // over a deleted file + printf("\n==== Rename an existing file over a deleted file\n"); + TEST_THAT(!TestFileExists("testfiles/TestDir1/x1/dsfdsfs98.fd")); + TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", + "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + + wait_for_backup_operation("bbackupd to sync"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3j.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Add files with old times, update " + "attributes of one to latest time\n"); + + // Move that file back + TEST_THAT(::rename("testfiles/TestDir1/x1/dsfdsfs98.fd", + "testfiles/TestDir1/df9834.dsf") == 0); + + // Add some more files + // Because the 'm' option is not used, these files will + // look very old to the daemon. + // Lucky it'll upload them then! + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/test2.tgz " + "-C testfiles") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/test2.tgz " + "| ( cd testfiles && tar xf - )") == 0); + ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415); + #endif + + // Wait and test + wait_for_backup_operation("bbackupd to sync old files"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3k.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that modifying files with old timestamps + // still get added + printf("\n==== Modify existing file, but change timestamp " + "to rather old\n"); + wait_for_sync_end(); + + // Then modify an existing file + { + // in the archive, it's read only + #ifdef WIN32 + TEST_THAT(::system("chmod 0777 testfiles" + "/TestDir1/sub23/rand.h") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/sub23" + "/rand.h", 0777) == 0); + #endif + + FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", + "w+"); + + if (f == 0) + { + perror("Failed to open"); + } + + TEST_THAT(f != 0); + + if (f != 0) + { + fprintf(f, "MODIFIED!\n"); + fclose(f); + } + + // and then move the time backwards! + struct timeval times[2]; + BoxTimeToTimeval(SecondsToBoxTime( + (time_t)(365*24*60*60)), times[1]); + times[0] = times[1]; + TEST_THAT(::utimes("testfiles/TestDir1/sub23/rand.h", + times) == 0); + } + + // Wait and test + wait_for_sync_end(); // files too new + wait_for_sync_end(); // should (not) be backed up this time + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3l.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + // Add some files and directories which are marked as excluded + printf("\n==== Add files and dirs for exclusion test\n"); + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/testexclude.tgz " + "-C testfiles") == 0); + #else + TEST_THAT(::system("gzip -d < " + "testfiles/testexclude.tgz " + "| ( cd testfiles && tar xf - )") == 0); + #endif + + // Wait and test + wait_for_sync_end(); + wait_for_sync_end(); + + // compare with exclusions, should not find differences + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3m.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // compare without exclusions, should find differences + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3n.log " + "-Werror \"compare -acEQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // check that the excluded files did not make it + // into the store, and the included files did + printf("\n==== Check that exclude/alwaysinclude commands " + "actually work\n"); + + { + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + std::auto_ptr dir = ReadDirectory( + *client, + BackupProtocolClientListDirectory::RootDirectory); + + int64_t testDirId = SearchDir(*dir, "Test1"); + TEST_THAT(testDirId != 0); + dir = ReadDirectory(*client, testDirId); + + TEST_THAT(!SearchDir(*dir, "excluded_1")); + TEST_THAT(!SearchDir(*dir, "excluded_2")); + TEST_THAT(!SearchDir(*dir, "exclude_dir")); + TEST_THAT(!SearchDir(*dir, "exclude_dir_2")); + // xx_not_this_dir_22 should not be excluded by + // ExcludeDirsRegex, because it's a file + TEST_THAT(SearchDir (*dir, "xx_not_this_dir_22")); + TEST_THAT(!SearchDir(*dir, "zEXCLUDEu")); + TEST_THAT(SearchDir (*dir, "dont.excludethis")); + TEST_THAT(SearchDir (*dir, "xx_not_this_dir_ALWAYSINCLUDE")); + + int64_t sub23id = SearchDir(*dir, "sub23"); + TEST_THAT(sub23id != 0); + dir = ReadDirectory(*client, sub23id); + + TEST_THAT(!SearchDir(*dir, "xx_not_this_dir_22")); + TEST_THAT(!SearchDir(*dir, "somefile.excludethis")); + client->QueryFinished(); + sSocket.Close(); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + +#ifndef WIN32 + // These tests only work as non-root users. + if(::getuid() != 0) + { + // Check that the error has not been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that read errors are reported neatly + printf("\n==== Add unreadable files\n"); + + { + // Dir and file which can't be read + TEST_THAT(::mkdir("testfiles/TestDir1/sub23" + "/read-fail-test-dir", 0000) == 0); + int fd = ::open("testfiles/TestDir1" + "/read-fail-test-file", + O_CREAT | O_WRONLY, 0000); + TEST_THAT(fd != -1); + ::close(fd); + } + + // Wait and test... + wait_for_backup_operation("bbackupd to try to sync " + "unreadable file"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3o.log " + "-Werror \"compare -acQ\" quit"); + + // should find differences + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Error); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Check that it was reported correctly + TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that the error was only reported once + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + + // Set permissions on file and dir to stop + // errors in the future + TEST_THAT(::chmod("testfiles/TestDir1/sub23" + "/read-fail-test-dir", 0770) == 0); + TEST_THAT(::chmod("testfiles/TestDir1" + "/read-fail-test-file", 0770) == 0); + } +#endif + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Continuously update file, " + "check isn't uploaded\n"); + + // Make sure everything happens at the same point in the + // sync cycle: wait until exactly the start of a sync + wait_for_sync_start(); + + // Then wait a second, to make sure the scan is complete + ::safe_sleep(1); + + { + // Open a file, then save something to it every second + for(int l = 0; l < 12; ++l) + { + FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+"); + TEST_THAT(f != 0); + fprintf(f, "Loop iteration %d\n", l); + fflush(f); + fclose(f); + + printf("."); + fflush(stdout); + safe_sleep(1); + } + printf("\n"); + fflush(stdout); + + // Check there's a difference + compareReturnValue = ::system("perl testfiles/" + "extcheck1.pl"); + + TEST_RETURN(compareReturnValue, 1); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("\n==== Keep on continuously updating file, " + "check it is uploaded eventually\n"); + + for(int l = 0; l < 28; ++l) + { + FILE *f = ::fopen("testfiles/TestDir1/" + "continousupdate", "w+"); + TEST_THAT(f != 0); + fprintf(f, "Loop 2 iteration %d\n", l); + fflush(f); + fclose(f); + + printf("."); + fflush(stdout); + safe_sleep(1); + } + printf("\n"); + fflush(stdout); + + compareReturnValue = ::system("perl testfiles/" + "extcheck2.pl"); + + TEST_RETURN(compareReturnValue, 1); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Delete directory, change attributes\n"); + + // Delete a directory + TEST_THAT(::system("rm -rf testfiles/TestDir1/x1") == 0); + // Change attributes on an original file. + ::chmod("testfiles/TestDir1/df9834.dsf", 0423); + + // Wait and test + wait_for_backup_operation("bbackupd to sync deletion " + "of directory"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query4.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + printf("\n==== Restore files and directories\n"); + int64_t deldirid = 0; + int64_t restoredirid = 0; + { + // connect and log in + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + // Find the ID of the Test1 directory + restoredirid = GetDirID(*client, "Test1", + BackupProtocolClientListDirectory::RootDirectory); + TEST_THAT(restoredirid != 0); + + // Test the restoration + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-Test1", + true /* print progress dots */) + == Restore_Complete); + + // On Win32 we can't open another connection + // to the server, so we'll compare later. + + // Make sure you can't restore a restored directory + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-Test1", + true /* print progress dots */) + == Restore_TargetExists); + + // Find ID of the deleted directory + deldirid = GetDirID(*client, "x1", restoredirid); + TEST_THAT(deldirid != 0); + + // Just check it doesn't bomb out -- will check this + // properly later (when bbackupd is stopped) + TEST_THAT(BackupClientRestore(*client, deldirid, + "Test1", "testfiles/restore-Test1-x1", + true /* print progress dots */, + true /* deleted files */) + == Restore_Complete); + + // Make sure you can't restore to a nonexistant path + printf("\n==== Try to restore to a path " + "that doesn't exist\n"); + fflush(stdout); + + { + Logging::Guard guard(Log::FATAL); + TEST_THAT(BackupClientRestore(*client, + restoredirid, "Test1", + "testfiles/no-such-path/subdir", + true /* print progress dots */) + == Restore_TargetPathNotFound); + } + + // Log out + client->QueryFinished(); + sSocket.Close(); + } + + // Compare the restored files + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10.log " + "-Wwarning " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + +#ifdef WIN32 + // make one of the files read-only, expect a compare failure + compareReturnValue = ::system("attrib +r " + "testfiles\\restore-Test1\\f1.dat"); + TEST_RETURN(compareReturnValue, 0); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10a.log " + "-Werror " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // set it back, expect no failures + compareReturnValue = ::system("attrib -r " + "testfiles\\restore-Test1\\f1.dat"); + TEST_RETURN(compareReturnValue, 0); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf -l testfiles/query10a.log " + "-Wwarning " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // change the timestamp on a file, expect a compare failure + char* testfile = "testfiles\\restore-Test1\\f1.dat"; + HANDLE handle = openfile(testfile, O_RDWR, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + + FILETIME creationTime, lastModTime, lastAccessTime; + TEST_THAT(GetFileTime(handle, &creationTime, &lastAccessTime, + &lastModTime) != 0); + TEST_THAT(CloseHandle(handle)); + + FILETIME dummyTime = lastModTime; + dummyTime.dwHighDateTime -= 100; + + // creation time is backed up, so changing it should cause + // a compare failure + TEST_THAT(set_file_time(testfile, dummyTime, lastModTime, + lastAccessTime)); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10a.log " + "-Werror " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // last access time is not backed up, so it cannot be compared + TEST_THAT(set_file_time(testfile, creationTime, lastModTime, + dummyTime)); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10a.log " + "-Wwarning " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // last write time is backed up, so changing it should cause + // a compare failure + TEST_THAT(set_file_time(testfile, creationTime, dummyTime, + lastAccessTime)); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10a.log " + "-Werror " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // set back to original values, check that compare succeeds + TEST_THAT(set_file_time(testfile, creationTime, lastModTime, + lastAccessTime)); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query10a.log " + "-Wwarning " + "\"compare -cEQ Test1 testfiles/restore-Test1\" " + "quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); +#endif // WIN32 + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Add files with current time\n"); + + // Add some more files and modify others + // Use the m flag this time so they have a recent modification time + #ifdef WIN32 + TEST_THAT(::system("tar xzvmf testfiles/test3.tgz " + "-C testfiles") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/test3.tgz " + "| ( cd testfiles && tar xmf - )") == 0); + #endif + + // Wait and test + wait_for_backup_operation("bbackupd to sync new files"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query5.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Rename directory + printf("\n==== Rename directory\n"); + TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", + "testfiles/TestDir1/renamed-dir") == 0); + + wait_for_backup_operation("bbackupd to sync renamed directory"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query6.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // and again, but with quick flag + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query6q.log " + "-Wwarning \"compare -acqQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Rename some files -- one under the threshold, others above + printf("\n==== Rename files\n"); + TEST_THAT(rename("testfiles/TestDir1/continousupdate", + "testfiles/TestDir1/continousupdate-ren") == 0); + TEST_THAT(rename("testfiles/TestDir1/df324", + "testfiles/TestDir1/df324-ren") == 0); + TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", + "testfiles/TestDir1/find2perl-ren") == 0); + + wait_for_backup_operation("bbackupd to sync renamed files"); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query6.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // Check that modifying files with madly in the future + // timestamps still get added + printf("\n==== Create a file with timestamp way ahead " + "in the future\n"); + + // Time critical, so sync + wait_for_sync_start(); + + // Then wait a second, to make sure the scan is complete + ::safe_sleep(1); + + // Then modify an existing file + { + FILE *f = fopen("testfiles/TestDir1/sub23/" + "in-the-future", "w"); + TEST_THAT(f != 0); + fprintf(f, "Back to the future!\n"); + fclose(f); + // and then move the time forwards! + struct timeval times[2]; + BoxTimeToTimeval(GetCurrentBoxTime() + + SecondsToBoxTime((time_t)(365*24*60*60)), + times[1]); + times[0] = times[1]; + TEST_THAT(::utimes("testfiles/TestDir1/sub23/" + "in-the-future", times) == 0); + } + + // Wait and test + wait_for_backup_operation("bbackup to sync future file"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query3e.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Change client store marker\n"); + + // Then... connect to the server, and change the + // client store marker. See what that does! + { + bool done = false; + int tries = 4; + while(!done && tries > 0) + { + try + { + std::auto_ptr + protocol = Connect(context); + // Make sure the marker isn't zero, + // because that's the default, and + // it should have changed + std::auto_ptr loginConf(protocol->QueryLogin(0x01234567, 0)); + TEST_THAT(loginConf->GetClientStoreMarker() != 0); + + // Change it to something else + protocol->QuerySetClientStoreMarker(12); + + // Success! + done = true; + + // Log out + protocol->QueryFinished(); + sSocket.Close(); + } + catch(...) + { + tries--; + } + } + TEST_THAT(done); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Check change of store marker pauses daemon\n"); + + // Make a change to a file, to detect whether or not + // it's hanging around waiting to retry. + { + FILE *f = ::fopen("testfiles/TestDir1/fileaftermarker", "w"); + TEST_THAT(f != 0); + ::fprintf(f, "Lovely file you got there."); + ::fclose(f); + } + + // Wait a little bit longer than usual + wait_for_operation((TIME_TO_WAIT_FOR_BACKUP_OPERATION * + 3) / 2, "bbackupd to detect changed store marker"); + + // Test that there *are* differences + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query6.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Different); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + // 100 seconds - (12*3/2) + wait_for_operation(82, "bbackupd to recover"); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + +#ifndef WIN32 + printf("\n==== Interrupted restore\n"); + { + do_interrupted_restore(context, restoredirid); + int64_t resumesize = 0; + TEST_THAT(FileExists("testfiles/" + "restore-interrupt.boxbackupresume", + &resumesize)); + // make sure it has recorded something to resume + TEST_THAT(resumesize > 16); + + printf("\n==== Resume restore\n"); + + std::auto_ptr client = + ConnectAndLogin(context, + BackupProtocolClientLogin::Flags_ReadOnly); + + // Check that the restore fn returns resume possible, + // rather than doing anything + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", + true /* print progress dots */) + == Restore_ResumePossible); + + // Then resume it + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", + true /* print progress dots */, + false /* deleted files */, + false /* undelete server */, + true /* resume */) + == Restore_Complete); + + client->QueryFinished(); + sSocket.Close(); + + // Then check it has restored the correct stuff + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query14.log " + "-Wwarning \"compare -cEQ Test1 " + "testfiles/restore-interrupt\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } +#endif // !WIN32 + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + printf("\n==== Check restore deleted files\n"); + + { + std::auto_ptr client = + ConnectAndLogin(context, 0 /* read-write */); + + // Do restore and undelete + TEST_THAT(BackupClientRestore(*client, deldirid, + "Test1", "testfiles/restore-Test1-x1-2", + true /* print progress dots */, + true /* deleted files */, + true /* undelete on server */) + == Restore_Complete); + + client->QueryFinished(); + sSocket.Close(); + + // Do a compare with the now undeleted files + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query11.log " + "-Wwarning " + "\"compare -cEQ Test1/x1 " + "testfiles/restore-Test1-x1-2\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + // Final check on notifications + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + +#ifdef WIN32 + printf("\n==== Testing locked file behaviour:\n"); + + // Test that locked files cannot be backed up, + // and the appropriate error is reported. + // Wait for the sync to finish, so that we have time to work + wait_for_sync_end(); + // Now we have about three seconds to work + + handle = openfile("testfiles/TestDir1/lockedfile", + O_CREAT | O_EXCL | O_LOCK, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + + if (handle != 0) + { + // first sync will ignore the file, it's too new + wait_for_sync_end(); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.read-error.1")); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if (handle != 0) + { + // this sync should try to back up the file, + // and fail, because it's locked + wait_for_sync_end(); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.read-error.1")); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.read-error.2")); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if (handle != 0) + { + // now close the file and check that it is + // backed up on the next run. + CloseHandle(handle); + wait_for_sync_end(); + + // still no read errors? + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.read-error.2")); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if (handle != 0) + { + // compare, and check that it works + // reports the correct error message (and finishes) + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query15a.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if (handle != 0) + { + // open the file again, compare and check that compare + // reports the correct error message (and finishes) + handle = openfile("testfiles/TestDir1/lockedfile", + O_LOCK, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query15.log " + "-Werror \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Error); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // close the file again, check that compare + // works again + CloseHandle(handle); + } + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if (handle != 0) + { + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query15a.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } +#endif + + // Kill the daemon + terminate_bbackupd(bbackupd_pid); + + // Start it again + cmd = BBACKUPD " " + bbackupd_args + + " testfiles/bbackupd.conf"; + bbackupd_pid = LaunchServer(cmd, "testfiles/bbackupd.pid"); + + TEST_THAT(bbackupd_pid != -1 && bbackupd_pid != 0); + + TEST_THAT(ServerIsAlive(bbackupd_pid)); + TEST_THAT(ServerIsAlive(bbstored_pid)); + if (!ServerIsAlive(bbackupd_pid)) return 1; + if (!ServerIsAlive(bbstored_pid)) return 1; + + if(bbackupd_pid != -1 && bbackupd_pid != 0) + { + // Wait and compare (a little bit longer than usual) + wait_for_operation( + (TIME_TO_WAIT_FOR_BACKUP_OPERATION*3) / 2, + "bbackupd to sync everything"); + compareReturnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-l testfiles/query4a.log " + "-Wwarning \"compare -acQ\" quit"); + TEST_RETURN(compareReturnValue, + BackupQueries::ReturnCode::Compare_Same); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + // Kill it again + terminate_bbackupd(bbackupd_pid); + } + } + + /* + // List the files on the server - why? + ::system(BBACKUPQUERY " -q -c testfiles/bbackupd.conf " + "-l testfiles/queryLIST.log \"list -rotdh\" quit"); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + */ + + #ifndef WIN32 + if(::getuid() == 0) + { + ::printf("WARNING: This test was run as root. " + "Some tests have been omitted.\n"); + } + #endif + + return 0; +} + +int test(int argc, const char *argv[]) +{ + // SSL library + SSLLib::Initialise(); + + // Keys for subsystems + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // Initial files + #ifdef WIN32 + TEST_THAT(::system("tar xzvf testfiles/test_base.tgz " + "-C testfiles") == 0); + #else + TEST_THAT(::system("gzip -d < testfiles/test_base.tgz " + "| ( cd testfiles && tar xf - )") == 0); + #endif + + // Do the tests + + int r = test_basics(); + if(r != 0) return r; + + r = test_setupaccount(); + if(r != 0) return r; + + r = test_run_bbstored(); + TEST_THAT(r == 0); + if(r != 0) return r; + + r = test_bbackupd(); + if(r != 0) + { + if (bbackupd_pid) + { + KillServer(bbackupd_pid); + } + if (bbstored_pid) + { + KillServer(bbstored_pid); + } + return r; + } + + test_kill_bbstored(); + + return 0; +} diff --git a/test/bbackupd/testextra b/test/bbackupd/testextra new file mode 100644 index 00000000..798c8c67 --- /dev/null +++ b/test/bbackupd/testextra @@ -0,0 +1,4 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data diff --git a/test/bbackupd/testfiles/accounts.txt b/test/bbackupd/testfiles/accounts.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/bbackupd/testfiles/bbackupd-exclude.conf.in b/test/bbackupd/testfiles/bbackupd-exclude.conf.in new file mode 100644 index 00000000..4c08753f --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-exclude.conf.in @@ -0,0 +1,47 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +BackupLocations +{ + Test1 + { + Path = testfiles/TestDir1 + ExcludeDir = testfiles/TestDir1/spacetest/d3 + ExcludeFile = testfiles/TestDir1/spacetest/f2 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd-snapshot.conf.in b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in new file mode 100644 index 00000000..d245d077 --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in @@ -0,0 +1,56 @@ + +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 = 0 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.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 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd-symlink.conf.in b/test/bbackupd/testfiles/bbackupd-symlink.conf.in new file mode 100644 index 00000000..33bb6157 --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-symlink.conf.in @@ -0,0 +1,55 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +BackupLocations +{ + Test1 + { + Path = testfiles/symlink-to-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 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd-temploc.conf b/test/bbackupd/testfiles/bbackupd-temploc.conf new file mode 100644 index 00000000..07cbdcd1 --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-temploc.conf @@ -0,0 +1,55 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +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/bbackupd/testfiles/bbackupd.conf.in b/test/bbackupd/testfiles/bbackupd.conf.in new file mode 100644 index 00000000..712b58b2 --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd.conf.in @@ -0,0 +1,55 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.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 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd.keys b/test/bbackupd/testfiles/bbackupd.keys new file mode 100644 index 00000000..d9135b97 Binary files /dev/null and b/test/bbackupd/testfiles/bbackupd.keys differ diff --git a/test/bbackupd/testfiles/bbstored.conf b/test/bbackupd/testfiles/bbstored.conf new file mode 100644 index 00000000..87f4fe6b --- /dev/null +++ b/test/bbackupd/testfiles/bbstored.conf @@ -0,0 +1,17 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +ExtendedLogging = no + +TimeBetweenHousekeeping = 5 + +Server +{ + PidFile = testfiles/bbstored.pid + ListenAddresses = inet:localhost:22011 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/bbackupd/testfiles/clientCerts.pem b/test/bbackupd/testfiles/clientCerts.pem new file mode 100644 index 00000000..c1f14fa7 --- /dev/null +++ b/test/bbackupd/testfiles/clientCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w +MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 +OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A +LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k +Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU +knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx +hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o +EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/clientPrivKey.pem b/test/bbackupd/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..34b1af2a --- /dev/null +++ b/test/bbackupd/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev +ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G +9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB +AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N +eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq +iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp +81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 +qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU +LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ +V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B +BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 +eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 +Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= +-----END RSA PRIVATE KEY----- diff --git a/test/bbackupd/testfiles/clientTrustedCAs.pem b/test/bbackupd/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/bbackupd/testfiles/clientTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/extcheck1.pl.in b/test/bbackupd/testfiles/extcheck1.pl.in new file mode 100755 index 00000000..5b70c677 --- /dev/null +++ b/test/bbackupd/testfiles/extcheck1.pl.in @@ -0,0 +1,58 @@ +#!@PERL@ +use strict; + +my $flags = $ARGV[0] or ""; + +unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "-l testfiles/query4.log " . + "\"compare -ac$flags\" quit 2>&1 |") +{ + print "FAIL: opening compare utility\n"; + exit 2; +} + +my $ret = 1; +my $seen = 0; + +while() +{ + next unless m/\S/; + print "READ: $_"; + + if (m/continousupdate/) + { + unless (/exists/) + { + print "FAIL: continousupdate line does not match\n"; + $ret = 2; + } + $seen = 1; + } + elsif (m/^No entry for terminal type/ or + m/^using dumb terminal settings/) + { + # skip these lines, may happen in Debian buildd + # with no terminal. + } + else + { + unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ + or /probably due to file mod/) + { + print "FAIL: Summary line does not match\n"; + $ret = 2; + } + } +} + +close IN; + +unless ($seen) +{ + print "FAIL: missing line matching continousupdate\n"; + $ret = 2; +} + +exit $ret; + diff --git a/test/bbackupd/testfiles/extcheck2.pl.in b/test/bbackupd/testfiles/extcheck2.pl.in new file mode 100755 index 00000000..3671ad93 --- /dev/null +++ b/test/bbackupd/testfiles/extcheck2.pl.in @@ -0,0 +1,50 @@ +#!@PERL@ +use strict; + +my $flags = $ARGV[0] or ""; + +unless(open IN,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "-l testfiles/query4.log " . + "\"compare -ac$flags\" quit 2>&1 |") +{ + print "Couldn't open compare utility\n"; + exit 2; +} + +my $ret = 1; + +while() +{ + next unless m/\S/; + print "READ: $_"; + + if (m/continousupdate/) + { + unless (m/contents/ or m/attributes/) + { + print "FAIL: continuousupdate line does not match\n"; + $ret = 2; + } + } + elsif (m/^No entry for terminal type/ or + m/^using dumb terminal settings/) + { + # skip these lines, may happen in Debian buildd + # with no terminal. + } + else + { + unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ + or /probably due to file mod/) + { + print "FAIL: summary line does not match\n"; + $ret = 2; + } + } +} + +close IN; + +exit $ret; + diff --git a/test/bbackupd/testfiles/notifyscript.pl.in b/test/bbackupd/testfiles/notifyscript.pl.in new file mode 100755 index 00000000..d3e324e9 --- /dev/null +++ b/test/bbackupd/testfiles/notifyscript.pl.in @@ -0,0 +1,24 @@ +#!@TARGET_PERL@ + +my $f = 'testfiles/notifyran.'.$ARGV[0].'.'; + +if (-e 'testfiles/notifyscript.tag') +{ + open FILE, '< testfiles/notifyscript.tag' or die $!; + my $tag = ; + chomp $tag; + $f .= "$tag."; + close FILE; +} + +my $n = 1; + +while(-e $f.$n) +{ + $n ++; +} + +open FL,'>'.$f.$n; +print FL localtime(); +close FL; + diff --git a/test/bbackupd/testfiles/raidfile.conf b/test/bbackupd/testfiles/raidfile.conf new file mode 100644 index 00000000..641872b0 --- /dev/null +++ b/test/bbackupd/testfiles/raidfile.conf @@ -0,0 +1,10 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + diff --git a/test/bbackupd/testfiles/serverCerts.pem b/test/bbackupd/testfiles/serverCerts.pem new file mode 100644 index 00000000..92467618 --- /dev/null +++ b/test/bbackupd/testfiles/serverCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw +MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV +I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 +cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH +RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ +myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 +SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 +HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/serverPrivKey.pem b/test/bbackupd/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..fd87607d --- /dev/null +++ b/test/bbackupd/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP +cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 +bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB +AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX +P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT +LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l +4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN +8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t +Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX +I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl +XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 +q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE +xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn +-----END RSA PRIVATE KEY----- diff --git a/test/bbackupd/testfiles/serverTrustedCAs.pem b/test/bbackupd/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/bbackupd/testfiles/serverTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/spacetest1.tgz b/test/bbackupd/testfiles/spacetest1.tgz new file mode 100644 index 00000000..c653c0ae Binary files /dev/null and b/test/bbackupd/testfiles/spacetest1.tgz differ diff --git a/test/bbackupd/testfiles/spacetest2.tgz b/test/bbackupd/testfiles/spacetest2.tgz new file mode 100644 index 00000000..aa47312d Binary files /dev/null and b/test/bbackupd/testfiles/spacetest2.tgz differ diff --git a/test/bbackupd/testfiles/syncallowscript.pl.in b/test/bbackupd/testfiles/syncallowscript.pl.in new file mode 100755 index 00000000..f2ef1171 --- /dev/null +++ b/test/bbackupd/testfiles/syncallowscript.pl.in @@ -0,0 +1,33 @@ +#!@TARGET_PERL@ + +use strict; +use warnings; + +my $control_file = 'testfiles/syncallowscript.control'; +if (! -r $control_file) +{ + print "now\n"; + exit 0; +} + +my $control_state; +open CONTROL, "< $control_file" or die "$control_file: $!"; +$control_state = ; +defined $control_state or die "$control_file: read failed: $!"; +close CONTROL; + +my $marker_file_root = 'testfiles/syncallowscript.notifyran.'; +my $n = 1; +my $marker_file; + +while($marker_file = $marker_file_root.$n and -e $marker_file) +{ + $n ++; +} + +open FL,'>'.$marker_file or die "$marker_file: $!"; +print FL localtime(); +close FL; + +print $control_state; +exit 0; diff --git a/test/bbackupd/testfiles/test2.tgz b/test/bbackupd/testfiles/test2.tgz new file mode 100644 index 00000000..ac7f18af Binary files /dev/null and b/test/bbackupd/testfiles/test2.tgz differ diff --git a/test/bbackupd/testfiles/test3.tgz b/test/bbackupd/testfiles/test3.tgz new file mode 100644 index 00000000..c7d60cd7 Binary files /dev/null and b/test/bbackupd/testfiles/test3.tgz differ diff --git a/test/bbackupd/testfiles/test_base.tgz b/test/bbackupd/testfiles/test_base.tgz new file mode 100644 index 00000000..9c8ddfc0 Binary files /dev/null and b/test/bbackupd/testfiles/test_base.tgz differ diff --git a/test/bbackupd/testfiles/testexclude.tgz b/test/bbackupd/testfiles/testexclude.tgz new file mode 100644 index 00000000..ac7329d8 Binary files /dev/null and b/test/bbackupd/testfiles/testexclude.tgz differ diff --git a/test/common/testcommon.cpp b/test/common/testcommon.cpp new file mode 100644 index 00000000..e633969b --- /dev/null +++ b/test/common/testcommon.cpp @@ -0,0 +1,882 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcommon.cpp +// Purpose: Tests for the code in lib/common +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include "Test.h" +#include "Configuration.h" +#include "FdGetLine.h" +#include "Guards.h" +#include "FileStream.h" +#include "InvisibleTempFileStream.h" +#include "IOStreamGetLine.h" +#include "NamedLock.h" +#include "ReadGatherStream.h" +#include "MemBlockStream.h" +#include "ExcludeList.h" +#include "CommonException.h" +#include "Conversion.h" +#include "autogen_ConversionException.h" +#include "CollectInBufferStream.h" +#include "Archive.h" +#include "Timer.h" +#include "Logging.h" +#include "ZeroStream.h" +#include "PartialReadStream.h" + +#include "MemLeakFindOn.h" + +using namespace BoxConvert; + +void test_conversions() +{ + TEST_THAT((Convert(std::string("32"))) == 32); + TEST_THAT((Convert("42")) == 42); + TEST_THAT((Convert("-42")) == -42); + TEST_CHECK_THROWS((Convert("500")), ConversionException, IntOverflowInConvertFromString); + TEST_CHECK_THROWS((Convert("pants")), ConversionException, BadStringRepresentationOfInt); + TEST_CHECK_THROWS((Convert("")), ConversionException, CannotConvertEmptyStringToInt); + + std::string a(Convert(63)); + TEST_THAT(a == "63"); + std::string b(Convert(-3473463)); + TEST_THAT(b == "-3473463"); + std::string c(Convert(344)); + TEST_THAT(c == "344"); +} + +ConfigurationVerifyKey verifykeys1_1_1[] = +{ + ConfigurationVerifyKey("bing", ConfigTest_Exists), + ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("terrible", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerifyKey verifykeys1_1_2[] = +{ + ConfigurationVerifyKey("fish", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) +}; + + +ConfigurationVerify verifysub1_1[] = +{ + { + "*", + 0, + verifykeys1_1_1, + ConfigTest_Exists, + 0 + }, + { + "otherthing", + 0, + verifykeys1_1_2, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +ConfigurationVerifyKey verifykeys1_1[] = +{ + ConfigurationVerifyKey("value", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string1", ConfigTest_Exists), + ConfigurationVerifyKey("string2", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerifyKey verifykeys1_2[] = +{ + ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerify verifysub1[] = +{ + { + "test1", + verifysub1_1, + verifykeys1_1, + ConfigTest_Exists, + 0 + }, + { + "ping", + 0, + verifykeys1_2, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +ConfigurationVerifyKey verifykeys1[] = +{ + ConfigurationVerifyKey("notExpected", 0), + ConfigurationVerifyKey("HasDefaultValue", 0, "Lovely default value"), + ConfigurationVerifyKey("MultiValue", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("BoolTrue1", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolTrue2", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolFalse1", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolFalse2", ConfigTest_IsBool), + ConfigurationVerifyKey("TOPlevel", + ConfigTest_LastEntry | ConfigTest_Exists) +}; + +ConfigurationVerify verify = +{ + "root", + verifysub1, + verifykeys1, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; + +class TestLogger : public Logger +{ + private: + bool mTriggered; + Log::Level mTargetLevel; + + public: + TestLogger(Log::Level targetLevel) + : mTriggered(false), mTargetLevel(targetLevel) + { + Logging::Add(this); + } + ~TestLogger() + { + Logging::Remove(this); + } + + bool IsTriggered() { return mTriggered; } + void Reset() { mTriggered = false; } + + virtual bool Log(Log::Level level, const std::string& rFile, + int line, std::string& rMessage) + { + if (level == mTargetLevel) + { + mTriggered = true; + } + return true; + } + + virtual const char* GetType() { return "Test"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +int test(int argc, const char *argv[]) +{ + // Test PartialReadStream and ReadGatherStream handling of files + // over 2GB (refs #2) + { + char buffer[8]; + + ZeroStream zero(0x80000003); + zero.Seek(0x7ffffffe, IOStream::SeekType_Absolute); + TEST_THAT(zero.GetPosition() == 0x7ffffffe); + TEST_THAT(zero.Read(buffer, 8) == 5); + TEST_THAT(zero.GetPosition() == 0x80000003); + TEST_THAT(zero.Read(buffer, 8) == 0); + zero.Seek(0, IOStream::SeekType_Absolute); + TEST_THAT(zero.GetPosition() == 0); + + char* buffer2 = new char [0x1000000]; + TEST_THAT(buffer2 != NULL); + + PartialReadStream part(zero, 0x80000002); + for (int i = 0; i < 0x80; i++) + { + int read = part.Read(buffer2, 0x1000000); + TEST_THAT(read == 0x1000000); + } + TEST_THAT(part.Read(buffer, 8) == 2); + TEST_THAT(part.Read(buffer, 8) == 0); + + delete [] buffer2; + + ReadGatherStream gather(false); + zero.Seek(0, IOStream::SeekType_Absolute); + int component = gather.AddComponent(&zero); + gather.AddBlock(component, 0x80000002); + TEST_THAT(gather.Read(buffer, 8) == 8); + } + + // Test self-deleting temporary file streams + { + std::string tempfile("testfiles/tempfile"); + TEST_CHECK_THROWS(InvisibleTempFileStream fs(tempfile.c_str()), + CommonException, OSFileOpenError); + InvisibleTempFileStream fs(tempfile.c_str(), O_CREAT); + + #ifdef WIN32 + // file is still visible under Windows + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it again should work + InvisibleTempFileStream fs2(tempfile.c_str()); + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it to create should work + InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it to create exclusively should fail + TEST_CHECK_THROWS(InvisibleTempFileStream fs4(tempfile.c_str(), + O_CREAT | O_EXCL), CommonException, OSFileOpenError); + + fs2.Close(); + #else + // file is not visible under Unix + TEST_THAT(!TestFileExists(tempfile.c_str())); + + // opening it again should fail + TEST_CHECK_THROWS(InvisibleTempFileStream fs2(tempfile.c_str()), + CommonException, OSFileOpenError); + + // opening it to create should work + InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); + TEST_THAT(!TestFileExists(tempfile.c_str())); + + // opening it to create exclusively should work + InvisibleTempFileStream fs4(tempfile.c_str(), O_CREAT | O_EXCL); + TEST_THAT(!TestFileExists(tempfile.c_str())); + + fs4.Close(); + #endif + + fs.Close(); + fs3.Close(); + + // now that it's closed, it should be invisible on all platforms + TEST_THAT(!TestFileExists(tempfile.c_str())); + } + + // Test that memory leak detection doesn't crash + { + char *test = new char[1024]; + delete [] test; + MemBlockStream *s = new MemBlockStream(test,12); + delete s; + } + +#ifdef BOX_MEMORY_LEAK_TESTING + { + Timers::Cleanup(); + + TEST_THAT(memleakfinder_numleaks() == 0); + void *block = ::malloc(12); + TEST_THAT(memleakfinder_numleaks() == 1); + void *b2 = ::realloc(block, 128*1024); + TEST_THAT(memleakfinder_numleaks() == 1); + ::free(b2); + TEST_THAT(memleakfinder_numleaks() == 0); + char *test = new char[1024]; + TEST_THAT(memleakfinder_numleaks() == 1); + MemBlockStream *s = new MemBlockStream(test,12); + TEST_THAT(memleakfinder_numleaks() == 2); + delete s; + TEST_THAT(memleakfinder_numleaks() == 1); + delete [] test; + TEST_THAT(memleakfinder_numleaks() == 0); + + Timers::Init(); + } +#endif // BOX_MEMORY_LEAK_TESTING + + // test main() initialises timers for us, so uninitialise them + Timers::Cleanup(); + + // Check that using timer methods without initialisation + // throws an assertion failure. Can only do this in debug mode + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Add(*(Timer*)NULL), + CommonException, AssertFailed); + TEST_CHECK_THROWS(Timers::Remove(*(Timer*)NULL), + CommonException, AssertFailed); + #endif + + // TEST_CHECK_THROWS(Timers::Signal(), CommonException, AssertFailed); + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, + AssertFailed); + #endif + + // Check that we can initialise the timers + Timers::Init(); + + // Check that double initialisation throws an exception + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Init(), CommonException, + AssertFailed); + #endif + + // Check that we can clean up the timers + Timers::Cleanup(); + + // Check that double cleanup throws an exception + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, + AssertFailed); + #endif + + Timers::Init(); + + Timer t0(0, "t0"); // should never expire + Timer t1(1, "t1"); + Timer t2(2, "t2"); + Timer t3(3, "t3"); + + TEST_THAT(!t0.HasExpired()); + TEST_THAT(!t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(!t3.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(!t3.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(t2.HasExpired()); + TEST_THAT(!t3.HasExpired()); + + t1 = Timer(1, "t1a"); + t2 = Timer(2, "t2a"); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(!t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(t3.HasExpired()); + + // Leave timers initialised for rest of test. + // Test main() will cleanup after test finishes. + + static const char *testfilelines[] = + { + "First line", + "Second line", + "Third", + "", + "", + "", + "sdf hjjk", + "", + "test", + "test#not comment", + "test#not comment", + "", + "nice line", + "fish", + "", + "ping", + "", + "", + "Nothing", + "Nothing", + 0 + }; + + // First, test the FdGetLine class -- rather important this works! + { + FileHandleGuard file("testfiles" + DIRECTORY_SEPARATOR "fdgetlinetest.txt"); + FdGetLine getline(file); + + int l = 0; + while(testfilelines[l] != 0) + { + TEST_THAT(!getline.IsEOF()); + std::string line = getline.GetLine(true); + //printf("expected |%s| got |%s|\n", lines[l], line.c_str()); + TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0); + l++; + } + TEST_THAT(getline.IsEOF()); + TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); + } + // and again without pre-processing + { + FileHandleGuard file("testfiles" + DIRECTORY_SEPARATOR "fdgetlinetest.txt"); + FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", "r"); + TEST_THAT_ABORTONFAIL(file2 != 0); + FdGetLine getline(file); + char ll[512]; + + while(!feof(file2)) + { + fgets(ll, sizeof(ll), file2); + int e = strlen(ll); + while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) + { + e--; + } + ll[e] = '\0'; + + TEST_THAT(!getline.IsEOF()); + std::string line = getline.GetLine(false); + //printf("expected |%s| got |%s|\n", ll, line.c_str()); + TEST_THAT(strcmp(ll, line.c_str()) == 0); + } + TEST_THAT(getline.IsEOF()); + TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); + + fclose(file2); + } + + // Then the IOStream version of get line, seeing as we're here... + { + FileStream file("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", O_RDONLY); + IOStreamGetLine getline(file); + + int l = 0; + while(testfilelines[l] != 0) + { + TEST_THAT(!getline.IsEOF()); + std::string line; + while(!getline.GetLine(line, true)) + ; + //printf("expected |%s| got |%s|\n", lines[l], line.c_str()); + TEST_THAT(strcmp(testfilelines[l], line.c_str()) == 0); + l++; + } + TEST_THAT(getline.IsEOF()); + std::string dummy; + TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); + } + // and again without pre-processing + { + FileStream file("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", O_RDONLY); + IOStreamGetLine getline(file); + + FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", "r"); + TEST_THAT_ABORTONFAIL(file2 != 0); + char ll[512]; + + while(!feof(file2)) + { + fgets(ll, sizeof(ll), file2); + int e = strlen(ll); + while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) + { + e--; + } + ll[e] = '\0'; + + TEST_THAT(!getline.IsEOF()); + std::string line; + while(!getline.GetLine(line, false)) + ; + //printf("expected |%s| got |%s|\n", ll, line.c_str()); + TEST_THAT(strcmp(ll, line.c_str()) == 0); + } + TEST_THAT(getline.IsEOF()); + std::string dummy; + TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); + + fclose(file2); + } + + // Doesn't exist + { + std::string errMsg; + TEST_CHECK_THROWS(std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "DOESNTEXIST", + &verify, errMsg)), + CommonException, OSFileOpenError); + } + + // Basic configuration test + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config1.txt", + &verify, errMsg)); + if(!errMsg.empty()) + { + printf("UNEXPECTED error msg is:\n------\n%s------\n", errMsg.c_str()); + } + TEST_THAT_ABORTONFAIL(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->KeyExists("TOPlevel")); + TEST_THAT(pconfig->GetKeyValue("TOPlevel") == "value"); + TEST_THAT(pconfig->KeyExists("MultiValue")); + TEST_THAT(pconfig->GetKeyValue("MultiValue") == "single"); + TEST_THAT(!pconfig->KeyExists("not exist")); + TEST_THAT(pconfig->KeyExists("HasDefaultValue")); + TEST_THAT(pconfig->GetKeyValue("HasDefaultValue") == "Lovely default value"); + TEST_CHECK_THROWS(pconfig->GetKeyValue("not exist"), CommonException, ConfigNoKey); + // list of keys + std::vector keylist(pconfig->GetKeyNames()); + TEST_THAT(keylist.size() == 3); + // will be sorted alphanumerically + TEST_THAT(keylist[2] == "TOPlevel" && keylist[1] == "MultiValue" && keylist[0] == "HasDefaultValue"); + // list of sub configurations + std::vector sublist(pconfig->GetSubConfigurationNames()); + TEST_THAT(sublist.size() == 2); + TEST_THAT(sublist[0] == "test1"); + TEST_THAT(sublist[1] == "ping"); + TEST_THAT(pconfig->SubConfigurationExists("test1")); + TEST_THAT(pconfig->SubConfigurationExists("ping")); + TEST_CHECK_THROWS(pconfig->GetSubConfiguration("nosubconfig"), CommonException, ConfigNoSubConfig); + // Get a sub configuration + const Configuration &sub1 = pconfig->GetSubConfiguration("test1"); + TEST_THAT(sub1.GetKeyValueInt("value") == 12); + std::vector sublist2(sub1.GetSubConfigurationNames()); + TEST_THAT(sublist2.size() == 4); + // And the sub-sub configs + const Configuration &sub1_1 = sub1.GetSubConfiguration("subconfig"); + TEST_THAT(sub1_1.GetKeyValueInt("carrots") == 0x2356); + const Configuration &sub1_2 = sub1.GetSubConfiguration("subconfig2"); + TEST_THAT(sub1_2.GetKeyValueInt("carrots") == -243895); + const Configuration &sub1_3 = sub1.GetSubConfiguration("subconfig3"); + TEST_THAT(sub1_3.GetKeyValueInt("carrots") == 050); + TEST_THAT(sub1_3.GetKeyValue("terrible") == "absolutely"); + } + + static const char *file[] = + { + "testfiles" DIRECTORY_SEPARATOR "config2.txt", + // Value missing from root + "testfiles" DIRECTORY_SEPARATOR "config3.txt", + // Unexpected { + "testfiles" DIRECTORY_SEPARATOR "config4.txt", + // Missing } + "testfiles" DIRECTORY_SEPARATOR "config5.txt", + // { expected, but wasn't there + "testfiles" DIRECTORY_SEPARATOR "config6.txt", + // Duplicate key + "testfiles" DIRECTORY_SEPARATOR "config7.txt", + // Invalid key (no name) + "testfiles" DIRECTORY_SEPARATOR "config8.txt", + // Not all sub blocks terminated + "testfiles" DIRECTORY_SEPARATOR "config9.txt", + // Not valid integer + "testfiles" DIRECTORY_SEPARATOR "config9b.txt", + // Not valid integer + "testfiles" DIRECTORY_SEPARATOR "config9c.txt", + // Not valid integer + "testfiles" DIRECTORY_SEPARATOR "config9d.txt", + // Not valid integer + "testfiles" DIRECTORY_SEPARATOR "config10.txt", + // Missing key (in subblock) + "testfiles" DIRECTORY_SEPARATOR "config11.txt", + // Unknown key + "testfiles" DIRECTORY_SEPARATOR "config12.txt", + // Missing block + "testfiles" DIRECTORY_SEPARATOR "config13.txt", + // Subconfig (wildcarded) should exist, but missing (ie nothing present) + "testfiles" DIRECTORY_SEPARATOR "config16.txt", + // bad boolean value + 0 + }; + + for(int l = 0; file[l] != 0; ++l) + { + std::string errMsg; + std::auto_ptr pconfig(Configuration::LoadAndVerify(file[l], &verify, errMsg)); + TEST_THAT(pconfig.get() == 0); + TEST_THAT(!errMsg.empty()); + printf("(%s) Error msg is:\n------\n%s------\n", file[l], errMsg.c_str()); + } + + // Check that multivalues happen as expected + // (single value in a multivalue already checked) + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config14.txt", + &verify, errMsg)); + TEST_THAT(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->KeyExists("MultiValue")); + // values are separated by a specific character + std::string expectedvalue("value1"); + expectedvalue += Configuration::MultiValueSeparator; + expectedvalue += "secondvalue"; + TEST_THAT(pconfig->GetKeyValue("MultiValue") == expectedvalue); + } + + // Check boolean values + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config15.txt", + &verify, errMsg)); + TEST_THAT(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->GetKeyValueBool("BoolTrue1") == true); + TEST_THAT(pconfig->GetKeyValueBool("BoolTrue2") == true); + TEST_THAT(pconfig->GetKeyValueBool("BoolFalse1") == false); + TEST_THAT(pconfig->GetKeyValueBool("BoolFalse2") == false); + } + + // Test named locks + { + NamedLock lock1; + // Try and get a lock on a name in a directory which doesn't exist + TEST_CHECK_THROWS(lock1.TryAndGetLock( + "testfiles" + DIRECTORY_SEPARATOR "non-exist" + DIRECTORY_SEPARATOR "lock"), + CommonException, OSFileError); + + // And a more resonable request + TEST_THAT(lock1.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == true); + + // Try to lock something using the same lock + TEST_CHECK_THROWS( + lock1.TryAndGetLock( + "testfiles" + DIRECTORY_SEPARATOR "non-exist" + DIRECTORY_SEPARATOR "lock2"), + CommonException, NamedLockAlreadyLockingSomething); +#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK + // And again on that name + NamedLock lock2; + TEST_THAT(lock2.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == false); +#endif + } + { + // Check that it unlocked when it went out of scope + NamedLock lock3; + TEST_THAT(lock3.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == true); + } + { + // And unlocking works + NamedLock lock4; + TEST_CHECK_THROWS(lock4.ReleaseLock(), CommonException, + NamedLockNotHeld); + TEST_THAT(lock4.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock4") == true); + lock4.ReleaseLock(); + NamedLock lock5; + TEST_THAT(lock5.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock4") == true); + // And can reuse it + TEST_THAT(lock4.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock5") == true); + } + + // Test the ReadGatherStream + { + #define GATHER_DATA1 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + #define GATHER_DATA2 "ZYZWVUTSRQPOMNOLKJIHGFEDCBA9876543210zyxwvutsrqpomno" + + // Make two streams + MemBlockStream s1(GATHER_DATA1, sizeof(GATHER_DATA1)); + MemBlockStream s2(GATHER_DATA2, sizeof(GATHER_DATA2)); + + // And a gather stream + ReadGatherStream gather(false /* no deletion */); + + // Add the streams + int s1_c = gather.AddComponent(&s1); + int s2_c = gather.AddComponent(&s2); + TEST_THAT(s1_c == 0); + TEST_THAT(s2_c == 1); + + // Set up some blocks + gather.AddBlock(s1_c, 11); + gather.AddBlock(s1_c, 2); + gather.AddBlock(s1_c, 8, true, 2); + gather.AddBlock(s2_c, 20); + gather.AddBlock(s1_c, 20); + gather.AddBlock(s2_c, 25); + gather.AddBlock(s1_c, 10, true, 0); + #define GATHER_RESULT "0123456789abc23456789ZYZWVUTSRQPOMNOLKJIHabcdefghijklmnopqrstGFEDCBA9876543210zyxwvuts0123456789" + + // Read them in... + char buffer[1024]; + unsigned int r = 0; + while(r < sizeof(GATHER_RESULT) - 1) + { + int s = gather.Read(buffer + r, 7); + r += s; + + TEST_THAT(gather.GetPosition() == r); + if(r < sizeof(GATHER_RESULT) - 1) + { + TEST_THAT(gather.StreamDataLeft()); + TEST_THAT(static_cast(gather.BytesLeftToRead()) == sizeof(GATHER_RESULT) - 1 - r); + } + else + { + TEST_THAT(!gather.StreamDataLeft()); + TEST_THAT(gather.BytesLeftToRead() == 0); + } + } + TEST_THAT(r == sizeof(GATHER_RESULT) - 1); + TEST_THAT(::memcmp(buffer, GATHER_RESULT, sizeof(GATHER_RESULT) - 1) == 0); + } + + // Test ExcludeList + { + ExcludeList elist; + // Check assumption + TEST_THAT(Configuration::MultiValueSeparator == '\x01'); + // Add definite entries + elist.AddDefiniteEntries(std::string("\x01")); + elist.AddDefiniteEntries(std::string("")); + elist.AddDefiniteEntries(std::string("Definite1\x01/dir/DefNumberTwo\x01\x01ThingDefThree")); + elist.AddDefiniteEntries(std::string("AnotherDef")); + TEST_THAT(elist.SizeOfDefiniteList() == 4); + + // Add regex entries + #ifdef HAVE_REGEX_SUPPORT + elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")); + elist.AddRegexEntries(std::string("")); + TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[:not_valid")), CommonException, BadRegularExpression); + TEST_THAT(elist.SizeOfRegexList() == 3); + #else + TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")), CommonException, RegexNotSupportedOnThisPlatform); + TEST_THAT(elist.SizeOfRegexList() == 0); + #endif + + #ifdef WIN32 + #define CASE_SENSITIVE false + #else + #define CASE_SENSITIVE true + #endif + + // Try some matches! + TEST_THAT(elist.IsExcluded(std::string("Definite1")) == true); + TEST_THAT(elist.IsExcluded(std::string("/dir/DefNumberTwo")) == true); + TEST_THAT(elist.IsExcluded(std::string("ThingDefThree")) == true); + TEST_THAT(elist.IsExcluded(std::string("AnotherDef")) == true); + TEST_THAT(elist.IsExcluded(std::string("dir/DefNumberTwo")) == false); + + // Try some case insensitive matches, + // that should pass on Win32 and fail elsewhere + TEST_THAT(elist.IsExcluded("DEFINITe1") + == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded("/Dir/DefNumberTwo") + == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded("thingdefthree") + == !CASE_SENSITIVE); + + #ifdef HAVE_REGEX_SUPPORT + TEST_THAT(elist.IsExcluded(std::string("b.reg")) == true); + TEST_THAT(elist.IsExcluded(std::string("B.reg")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("b.Reg")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("e.reg")) == false); + TEST_THAT(elist.IsExcluded(std::string("e.Reg")) == false); + TEST_THAT(elist.IsExcluded(std::string("DEfinite1")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("DEXCLUDEfinite1")) == true); + TEST_THAT(elist.IsExcluded(std::string("DEfinitexclude1")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("exclude")) == true); + TEST_THAT(elist.IsExcluded(std::string("ExcludE")) == !CASE_SENSITIVE); + #endif + + #undef CASE_SENSITIVE + + TestLogger logger(Log::WARNING); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo")); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo/")); + TEST_THAT(logger.IsTriggered()); + logger.Reset(); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR)); + TEST_THAT(logger.IsTriggered()); + logger.Reset(); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR "bar\x01/foo")); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR "bar\x01/foo" + DIRECTORY_SEPARATOR)); + TEST_THAT(logger.IsTriggered()); + } + + test_conversions(); + + // test that we can use Archive and CollectInBufferStream + // to read and write arbitrary types to a memory buffer + + { + CollectInBufferStream buffer; + ASSERT(buffer.GetPosition() == 0); + + { + Archive archive(buffer, 0); + ASSERT(buffer.GetPosition() == 0); + + archive.Write((bool) true); + archive.Write((bool) false); + archive.Write((int) 0x12345678); + archive.Write((int) 0x87654321); + archive.Write((int64_t) 0x0badfeedcafebabeLL); + archive.Write((uint64_t) 0xfeedfacedeadf00dLL); + archive.Write((uint8_t) 0x01); + archive.Write((uint8_t) 0xfe); + archive.Write(std::string("hello world!")); + archive.Write(std::string("goodbye cruel world!")); + } + + CollectInBufferStream buf2; + buf2.Write(buffer.GetBuffer(), buffer.GetSize()); + TEST_THAT(buf2.GetPosition() == buffer.GetSize()); + + buf2.SetForReading(); + TEST_THAT(buf2.GetPosition() == 0); + + { + Archive archive(buf2, 0); + TEST_THAT(buf2.GetPosition() == 0); + + bool b; + archive.Read(b); TEST_THAT(b == true); + archive.Read(b); TEST_THAT(b == false); + + int i; + archive.Read(i); TEST_THAT(i == 0x12345678); + archive.Read(i); TEST_THAT((unsigned int)i == 0x87654321); + + uint64_t i64; + archive.Read(i64); TEST_THAT(i64 == 0x0badfeedcafebabeLL); + archive.Read(i64); TEST_THAT(i64 == 0xfeedfacedeadf00dLL); + + uint8_t i8; + archive.Read(i8); TEST_THAT(i8 == 0x01); + archive.Read(i8); TEST_THAT(i8 == 0xfe); + + std::string s; + archive.Read(s); TEST_THAT(s == "hello world!"); + archive.Read(s); TEST_THAT(s == "goodbye cruel world!"); + + TEST_THAT(!buf2.StreamDataLeft()); + } + } + + return 0; +} diff --git a/test/common/testfiles/config1.txt b/test/common/testfiles/config1.txt new file mode 100644 index 00000000..d000f759 --- /dev/null +++ b/test/common/testfiles/config1.txt @@ -0,0 +1,40 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config10.txt b/test/common/testfiles/config10.txt new file mode 100644 index 00000000..02aeec74 --- /dev/null +++ b/test/common/testfiles/config10.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config11.txt b/test/common/testfiles/config11.txt new file mode 100644 index 00000000..cafabe74 --- /dev/null +++ b/test/common/testfiles/config11.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + NOTEXPECTED= 34234 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config12.txt b/test/common/testfiles/config12.txt new file mode 100644 index 00000000..17ed34f1 --- /dev/null +++ b/test/common/testfiles/config12.txt @@ -0,0 +1,33 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config13.txt b/test/common/testfiles/config13.txt new file mode 100644 index 00000000..8de8ea5b --- /dev/null +++ b/test/common/testfiles/config13.txt @@ -0,0 +1,15 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config14.txt b/test/common/testfiles/config14.txt new file mode 100644 index 00000000..d409beaa --- /dev/null +++ b/test/common/testfiles/config14.txt @@ -0,0 +1,41 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = value1 +MultiValue = secondvalue + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config15.txt b/test/common/testfiles/config15.txt new file mode 100644 index 00000000..bfa7b022 --- /dev/null +++ b/test/common/testfiles/config15.txt @@ -0,0 +1,45 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + +BoolTrue1 = true +BoolTrue2 = yes +BoolFalse1 = fAlse +BoolFalse2 = nO + diff --git a/test/common/testfiles/config16.txt b/test/common/testfiles/config16.txt new file mode 100644 index 00000000..566070f2 --- /dev/null +++ b/test/common/testfiles/config16.txt @@ -0,0 +1,42 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + +BoolTrue1 = not a valid value + diff --git a/test/common/testfiles/config2.txt b/test/common/testfiles/config2.txt new file mode 100644 index 00000000..724c911a --- /dev/null +++ b/test/common/testfiles/config2.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +# make this value missing +# TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config3.txt b/test/common/testfiles/config3.txt new file mode 100644 index 00000000..688675a4 --- /dev/null +++ b/test/common/testfiles/config3.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + { + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config4.txt b/test/common/testfiles/config4.txt new file mode 100644 index 00000000..1563d8aa --- /dev/null +++ b/test/common/testfiles/config4.txt @@ -0,0 +1,40 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} +} + + diff --git a/test/common/testfiles/config5.txt b/test/common/testfiles/config5.txt new file mode 100644 index 00000000..d1821ecc --- /dev/null +++ b/test/common/testfiles/config5.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config6.txt b/test/common/testfiles/config6.txt new file mode 100644 index 00000000..d7381738 --- /dev/null +++ b/test/common/testfiles/config6.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + bing= something else + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config7.txt b/test/common/testfiles/config7.txt new file mode 100644 index 00000000..6a24d036 --- /dev/null +++ b/test/common/testfiles/config7.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + = invalid thing here! + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config8.txt b/test/common/testfiles/config8.txt new file mode 100644 index 00000000..3a66bbb3 --- /dev/null +++ b/test/common/testfiles/config8.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts + diff --git a/test/common/testfiles/config9.txt b/test/common/testfiles/config9.txt new file mode 100644 index 00000000..936ad6ce --- /dev/null +++ b/test/common/testfiles/config9.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050X + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9b.txt b/test/common/testfiles/config9b.txt new file mode 100644 index 00000000..65c44a19 --- /dev/null +++ b/test/common/testfiles/config9b.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=C-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9c.txt b/test/common/testfiles/config9c.txt new file mode 100644 index 00000000..d9be55ad --- /dev/null +++ b/test/common/testfiles/config9c.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=2430-895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9d.txt b/test/common/testfiles/config9d.txt new file mode 100644 index 00000000..28ea300e --- /dev/null +++ b/test/common/testfiles/config9d.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =090 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/fdgetlinetest.txt b/test/common/testfiles/fdgetlinetest.txt new file mode 100644 index 00000000..f7b2c829 --- /dev/null +++ b/test/common/testfiles/fdgetlinetest.txt @@ -0,0 +1,20 @@ +First line + Second line + Third +# comment + # comment + + sdf hjjk + + test #coment + test#not comment + test#not comment #comment + +nice line +fish + +ping +#comment + + Nothing + Nothing \ No newline at end of file diff --git a/test/compress/testcompress.cpp b/test/compress/testcompress.cpp new file mode 100644 index 00000000..4a522d31 --- /dev/null +++ b/test/compress/testcompress.cpp @@ -0,0 +1,261 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcompress.cpp +// Purpose: Test lib/compress +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "Compress.h" +#include "CompressStream.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +#define DATA_SIZE (1024*128+103) +#define CHUNK_SIZE 2561 +#define DECOMP_CHUNK_SIZE 3 + +// Stream for testing +class CopyInToOutStream : public IOStream +{ +public: + CopyInToOutStream() : currentBuffer(0) {buffers[currentBuffer].SetForReading();} + ~CopyInToOutStream() {} + int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) + { + if(buffers[currentBuffer].StreamDataLeft()) + { + return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); + } + + // Swap buffers? + if(buffers[(currentBuffer + 1) & 1].GetSize() > 0) + { + buffers[currentBuffer].Reset(); + currentBuffer = (currentBuffer + 1) & 1; + buffers[currentBuffer].SetForReading(); + return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); + } + + return 0; + } + void Write(const void *pBuffer, int NBytes) + { + buffers[(currentBuffer + 1) & 1].Write(pBuffer, NBytes); + } + bool StreamDataLeft() + { + return buffers[currentBuffer].StreamDataLeft() || buffers[(currentBuffer + 1) % 1].GetSize() > 0; + } + bool StreamClosed() + { + return false; + } + int currentBuffer; + CollectInBufferStream buffers[2]; +}; + +// Test stream based interface +int test_stream() +{ + // Make a load of compressible data to compress + CollectInBufferStream source; + uint16_t data[1024]; + for(int x = 0; x < 1024; ++x) + { + data[x] = x; + } + for(int x = 0; x < (32*1024); ++x) + { + source.Write(data, (x % 1024) * 2); + } + source.SetForReading(); + + // Straight compress from one stream to another + { + CollectInBufferStream *poutput = new CollectInBufferStream; + CompressStream compress(poutput, true /* take ownership */, false /* read */, true /* write */); + + source.CopyStreamTo(compress); + compress.Close(); + poutput->SetForReading(); + + // Check sizes + TEST_THAT(poutput->GetSize() < source.GetSize()); + BOX_TRACE("compressed size = " << poutput->GetSize() << + ", source size = " << source.GetSize()); + + // Decompress the data + { + CollectInBufferStream decompressed; + CompressStream decompress(poutput, false /* don't take ownership */, true /* read */, false /* write */); + decompress.CopyStreamTo(decompressed); + decompress.Close(); + + TEST_THAT(decompressed.GetSize() == source.GetSize()); + TEST_THAT(::memcmp(decompressed.GetBuffer(), source.GetBuffer(), decompressed.GetSize()) == 0); + } + + // Don't delete poutput, let mem leak testing ensure it's deleted. + } + + // Set source to the beginning + source.Seek(0, IOStream::SeekType_Absolute); + + // Test where the same stream compresses and decompresses, should be fun! + { + CollectInBufferStream output; + CopyInToOutStream copyer; + CompressStream compress(©er, false /* no ownership */, true, true); + + bool done = false; + int count = 0; + int written = 0; + while(!done) + { + ++count; + bool do_sync = (count % 256) == 0; + uint8_t buffer[4096]; + int r = source.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); + if(r == 0) + { + done = true; + compress.Close(); + } + else + { + compress.Write(buffer, r); + written += r; + if(do_sync) + { + compress.WriteAllBuffered(); + } + } + + int r2 = 0; + do + { + r2 = compress.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); + if(r2 > 0) + { + output.Write(buffer, r2); + } + } while(r2 > 0); + if(do_sync && r != 0) + { + // Check that everything is synced + TEST_THAT(output.GetSize() == written); + TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); + } + } + output.SetForReading(); + + // Test that it's the same + TEST_THAT(output.GetSize() == source.GetSize()); + TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); + } + + return 0; +} + +// Test basic interface +int test(int argc, const char *argv[]) +{ + // Bad data to compress! + char *data = (char *)malloc(DATA_SIZE); + for(int l = 0; l < DATA_SIZE; ++l) + { + data[l] = l*23; + } + + // parameters about compression + int maxOutput = Compress_MaxSizeForCompressedData(DATA_SIZE); + TEST_THAT(maxOutput >= DATA_SIZE); + + char *compressed = (char *)malloc(maxOutput); + int compressedSize = 0; + + // Do compression, in small chunks + { + Compress compress; + + int in_loc = 0; + while(!compress.OutputHasFinished()) + { + int ins = DATA_SIZE - in_loc; + if(ins > CHUNK_SIZE) ins = CHUNK_SIZE; + + if(ins == 0) + { + compress.FinishInput(); + } + else + { + compress.Input(data + in_loc, ins); + } + in_loc += ins; + + // Get output data + int s = 0; + do + { + TEST_THAT(compressedSize < maxOutput); + s = compress.Output(compressed + compressedSize, maxOutput - compressedSize); + compressedSize += s; + } while(s > 0); + } + } + + // a reasonable test, especially given the compressability of the input data. + TEST_THAT(compressedSize < DATA_SIZE); + + // decompression + char *decompressed = (char*)malloc(DATA_SIZE * 2); + int decomp_size = 0; + { + Compress decompress; + + int in_loc = 0; + while(!decompress.OutputHasFinished()) + { + int ins = compressedSize - in_loc; + if(ins > DECOMP_CHUNK_SIZE) ins = DECOMP_CHUNK_SIZE; + + if(ins == 0) + { + decompress.FinishInput(); + } + else + { + decompress.Input(compressed + in_loc, ins); + } + in_loc += ins; + + // Get output data + int s = 0; + do + { + TEST_THAT(decomp_size <= DATA_SIZE); + s = decompress.Output(decompressed + decomp_size, (DATA_SIZE*2) - decomp_size); + decomp_size += s; + } while(s > 0); + } + } + + TEST_THAT(decomp_size == DATA_SIZE); + TEST_THAT(::memcmp(data, decompressed, DATA_SIZE) == 0); + + ::free(data); + ::free(compressed); + ::free(decompressed); + + return test_stream(); +} diff --git a/test/crypto/testcrypto.cpp b/test/crypto/testcrypto.cpp new file mode 100644 index 00000000..6d90e5e7 --- /dev/null +++ b/test/crypto/testcrypto.cpp @@ -0,0 +1,314 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcrypto.cpp +// Purpose: test lib/crypto +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include "Test.h" +#include "CipherContext.h" +#include "CipherBlowfish.h" +#include "CipherAES.h" +#include "CipherException.h" +#include "RollingChecksum.h" +#include "Random.h" + +#include "MemLeakFindOn.h" + +#define STRING1 "Mary had a little lamb" +#define STRING2 "Skjdf sdjf sjksd fjkhsdfjk hsdfuiohcverfg sdfnj sdfgkljh sdfjb jlhdfvghsdip vjsdfv bsdfhjvg yuiosdvgpvj kvbn m,sdvb sdfuiovg sdfuivhsdfjkv" + +#define KEY "0123456701234567012345670123456" +#define KEY2 "1234567012345670123456A" + +#define CHECKSUM_DATA_SIZE (128*1024) +#define CHECKSUM_BLOCK_SIZE_BASE (65*1024) +#define CHECKSUM_BLOCK_SIZE_LAST (CHECKSUM_BLOCK_SIZE_BASE + 64) +#define CHECKSUM_ROLLS 16 + +void check_random_int(uint32_t max) +{ + for(int c = 0; c < 1024; ++c) + { + uint32_t v = Random::RandomInt(max); + TEST_THAT(v >= 0 && v <= max); + } +} + +#define ZERO_BUFFER(x) ::memset(x, 0, sizeof(x)); + +template +void test_cipher() +{ + { + // Make a couple of cipher contexts + CipherContext encrypt1; + encrypt1.Reset(); + encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + TEST_CHECK_THROWS(encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))), + CipherException, AlreadyInitialised); + // Encrpt something + char buf1[256]; + unsigned int buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); + TEST_THAT(buf1_used >= sizeof(STRING1)); + // Decrypt it + CipherContext decrypt1; + decrypt1.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + char buf1_de[256]; + unsigned int buf1_de_used = decrypt1.TransformBlock(buf1_de, sizeof(buf1_de), buf1, buf1_used); + TEST_THAT(buf1_de_used == sizeof(STRING1)); + TEST_THAT(memcmp(STRING1, buf1_de, sizeof(STRING1)) == 0); + + // Use them again... + char buf1_de2[256]; + unsigned int buf1_de2_used = decrypt1.TransformBlock(buf1_de2, sizeof(buf1_de2), buf1, buf1_used); + TEST_THAT(buf1_de2_used == sizeof(STRING1)); + TEST_THAT(memcmp(STRING1, buf1_de2, sizeof(STRING1)) == 0); + + // Test the interface + char buf2[256]; + TEST_CHECK_THROWS(encrypt1.Transform(buf2, sizeof(buf2), STRING1, sizeof(STRING1)), + CipherException, BeginNotCalled); + TEST_CHECK_THROWS(encrypt1.Final(buf2, sizeof(buf2)), + CipherException, BeginNotCalled); + encrypt1.Begin(); + int e = 0; + e = encrypt1.Transform(buf2, sizeof(buf2), STRING2, sizeof(STRING2) - 16); + e += encrypt1.Transform(buf2 + e, sizeof(buf2) - e, STRING2 + sizeof(STRING2) - 16, 16); + e += encrypt1.Final(buf2 + e, sizeof(buf2) - e); + TEST_THAT(e >= (int)sizeof(STRING2)); + + // Then decrypt + char buf2_de[256]; + decrypt1.Begin(); + TEST_CHECK_THROWS(decrypt1.Transform(buf2_de, 2, buf2, e), CipherException, OutputBufferTooSmall); + TEST_CHECK_THROWS(decrypt1.Final(buf2_de, 2), CipherException, OutputBufferTooSmall); + int d = decrypt1.Transform(buf2_de, sizeof(buf2_de), buf2, e - 48); + d += decrypt1.Transform(buf2_de + d, sizeof(buf2_de) - d, buf2 + e - 48, 48); + d += decrypt1.Final(buf2_de + d, sizeof(buf2_de) - d); + TEST_THAT(d == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf2_de, sizeof(STRING2)) == 0); + + // Try a reset and rekey + encrypt1.Reset(); + encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY2, sizeof(KEY2))); + buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); + } + + // Test initialisation vectors + { + // Init with random IV + CipherContext encrypt2; + encrypt2.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + int ivLen; + char iv2[BLOCKSIZE]; + const void *ivGen = encrypt2.SetRandomIV(ivLen); + TEST_THAT(ivLen == BLOCKSIZE); // block size + TEST_THAT(ivGen != 0); + memcpy(iv2, ivGen, ivLen); + + char buf3[256]; + unsigned int buf3_used = encrypt2.TransformBlock(buf3, sizeof(buf3), STRING2, sizeof(STRING2)); + + // Encrypt again with different IV + char iv3[BLOCKSIZE]; + int ivLen3; + const void *ivGen3 = encrypt2.SetRandomIV(ivLen3); + TEST_THAT(ivLen3 == BLOCKSIZE); // block size + TEST_THAT(ivGen3 != 0); + memcpy(iv3, ivGen3, ivLen3); + // Check the two generated IVs are different + TEST_THAT(memcmp(iv2, iv3, BLOCKSIZE) != 0); + + char buf4[256]; + unsigned int buf4_used = encrypt2.TransformBlock(buf4, sizeof(buf4), STRING2, sizeof(STRING2)); + + // check encryptions are different + TEST_THAT(buf3_used == buf4_used); + TEST_THAT(memcmp(buf3, buf4, buf3_used) != 0); + + // Test that decryption with the right IV works + CipherContext decrypt2; + decrypt2.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY), iv2)); + char buf3_de[256]; + unsigned int buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); + TEST_THAT(buf3_de_used == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) == 0); + + // And that using the wrong one doesn't + decrypt2.SetIV(iv3); + buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); + TEST_THAT(buf3_de_used == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) != 0); + } + + // Test with padding off. + { + CipherContext encrypt3; + encrypt3.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt3.UsePadding(false); + + // Should fail because the encrypted size is not a multiple of the block size + char buf4[256]; + encrypt3.Begin(); + ZERO_BUFFER(buf4); + int buf4_used = encrypt3.Transform(buf4, sizeof(buf4), STRING2, 6); + TEST_CHECK_THROWS(encrypt3.Final(buf4, sizeof(buf4)), CipherException, EVPFinalFailure); + + // Check a nice encryption with the correct block size + CipherContext encrypt4; + encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4.UsePadding(false); + encrypt4.Begin(); + ZERO_BUFFER(buf4); + buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, 16); + buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); + TEST_THAT(buf4_used == 16); + + // Check it's encrypted to the same thing as when there's padding on + CipherContext encrypt4b; + encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4b.Begin(); + char buf4b[256]; + ZERO_BUFFER(buf4b); + int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, 16); + buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); + TEST_THAT(buf4b_used == 16+BLOCKSIZE); + TEST_THAT(::memcmp(buf4, buf4b, 16) == 0); + + // Decrypt + char buf4_de[256]; + CipherContext decrypt4; + decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + decrypt4.UsePadding(false); + decrypt4.Begin(); + ZERO_BUFFER(buf4_de); + int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, 16); + buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); + TEST_THAT(buf4_de_used == 16); + TEST_THAT(::memcmp(buf4_de, STRING2, 16) == 0); + + // Test that the TransformBlock thing works as expected too with blocks the same size as the input + TEST_THAT(encrypt4.TransformBlock(buf4, 16, STRING2, 16) == 16); + // But that it exceptions if we try the trick with padding on + encrypt4.UsePadding(true); + TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, 16, STRING2, 16), CipherException, OutputBufferTooSmall); + } + + // And again, but with different string size + { + char buf4[256]; + int buf4_used; + + // Check a nice encryption with the correct block size + CipherContext encrypt4; + encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4.UsePadding(false); + encrypt4.Begin(); + ZERO_BUFFER(buf4); + buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, (BLOCKSIZE*3)); // do three blocks worth + buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); + TEST_THAT(buf4_used == (BLOCKSIZE*3)); + + // Check it's encrypted to the same thing as when there's padding on + CipherContext encrypt4b; + encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4b.Begin(); + char buf4b[256]; + ZERO_BUFFER(buf4b); + int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, (BLOCKSIZE*3)); + buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); + TEST_THAT(buf4b_used == (BLOCKSIZE*4)); + TEST_THAT(::memcmp(buf4, buf4b, (BLOCKSIZE*3)) == 0); + + // Decrypt + char buf4_de[256]; + CipherContext decrypt4; + decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + decrypt4.UsePadding(false); + decrypt4.Begin(); + ZERO_BUFFER(buf4_de); + int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, (BLOCKSIZE*3)); + buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); + TEST_THAT(buf4_de_used == (BLOCKSIZE*3)); + TEST_THAT(::memcmp(buf4_de, STRING2, (BLOCKSIZE*3)) == 0); + + // Test that the TransformBlock thing works as expected too with blocks the same size as the input + TEST_THAT(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)) == (BLOCKSIZE*3)); + // But that it exceptions if we try the trick with padding on + encrypt4.UsePadding(true); + TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)), CipherException, OutputBufferTooSmall); + } +} + +int test(int argc, const char *argv[]) +{ + Random::Initialise(); + + // Cipher type + ::printf("Blowfish...\n"); + test_cipher(); +#ifndef HAVE_OLD_SSL + ::printf("AES...\n"); + test_cipher(); +#else + ::printf("Skipping AES -- not supported by version of OpenSSL in use.\n"); +#endif + + ::printf("Misc...\n"); + // Check rolling checksums + uint8_t *checkdata_blk = (uint8_t *)malloc(CHECKSUM_DATA_SIZE); + uint8_t *checkdata = checkdata_blk; + RAND_pseudo_bytes(checkdata, CHECKSUM_DATA_SIZE); + for(int size = CHECKSUM_BLOCK_SIZE_BASE; size <= CHECKSUM_BLOCK_SIZE_LAST; ++size) + { + // Test skip-roll code + RollingChecksum rollFast(checkdata, size); + rollFast.RollForwardSeveral(checkdata, checkdata+size, size, CHECKSUM_ROLLS/2); + RollingChecksum calc(checkdata + (CHECKSUM_ROLLS/2), size); + TEST_THAT(calc.GetChecksum() == rollFast.GetChecksum()); + + //printf("size = %d\n", size); + // Checksum to roll + RollingChecksum roll(checkdata, size); + + // Roll forward + for(int l = 0; l < CHECKSUM_ROLLS; ++l) + { + // Calculate new one + RollingChecksum calc(checkdata, size); + + //printf("%08X %08X %d %d\n", roll.GetChecksum(), calc.GetChecksum(), checkdata[0], checkdata[size]); + + // Compare them! + TEST_THAT(calc.GetChecksum() == roll.GetChecksum()); + + // Roll it onwards + roll.RollForward(checkdata[0], checkdata[size], size); + + // increment + ++checkdata; + } + } + ::free(checkdata_blk); + + // Random integers + check_random_int(0); + check_random_int(1); + check_random_int(5); + check_random_int(15); // all 1's + check_random_int(1022); + + return 0; +} + + + diff --git a/test/httpserver/testfiles/httpserver.conf b/test/httpserver/testfiles/httpserver.conf new file mode 100644 index 00000000..1a1c4644 --- /dev/null +++ b/test/httpserver/testfiles/httpserver.conf @@ -0,0 +1,8 @@ + +AddressPrefix = http://localhost:1080 + +Server +{ + PidFile = testfiles/httpserver.pid + ListenAddresses = inet:localhost:1080 +} diff --git a/test/httpserver/testfiles/photos/puppy.jpg b/test/httpserver/testfiles/photos/puppy.jpg new file mode 100644 index 00000000..a326a6a7 --- /dev/null +++ b/test/httpserver/testfiles/photos/puppy.jpg @@ -0,0 +1 @@ +omgpuppies! diff --git a/test/httpserver/testfiles/s3simulator.conf b/test/httpserver/testfiles/s3simulator.conf new file mode 100644 index 00000000..07921560 --- /dev/null +++ b/test/httpserver/testfiles/s3simulator.conf @@ -0,0 +1,10 @@ +AccessKey = 0PN5J17HBGZHT7JJ3X82 +SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o +StoreDirectory = testfiles +AddressPrefix = http://localhost:1080 + +Server +{ + PidFile = testfiles/s3simulator.pid + ListenAddresses = inet:localhost:1080 +} diff --git a/test/httpserver/testfiles/testrequests.pl b/test/httpserver/testfiles/testrequests.pl new file mode 100755 index 00000000..85380ee0 --- /dev/null +++ b/test/httpserver/testfiles/testrequests.pl @@ -0,0 +1,143 @@ +#!/usr/bin/perl +use strict; +use LWP::UserAgent; + +my $url_base = 'http://localhost:1080'; + +my $ua = LWP::UserAgent->new(env_proxy => 0, keep_alive => 1, timeout => 30); + +print "GET request...\n"; + +my $response1 = $ua->get("$url_base/test-one/34/341s/234?p1=vOne&p2=vTwo"); +die $response1->content unless $response1->is_success(); + +my $content = $response1->content(); + +check_url($content, '/test-one/34/341s/234'); +check_params($content, 'p1'=>'vOne','p2'=>'vTwo'); + +print "POST request...\n"; + +my %post = ('sdfgksjhdfsd'=>'dfvsiufy2e3434','sxciuhwf8723e4'=>'238947829334', + '&sfsfsfskfhs'=>'?hdkfjhsjfds','fdsf=sdf2342'=>'3984sajhksda'); + +my $response2 = $ua->post("$url_base/tdskjhfsjdkhf2943734?p1=vOne&p2=vTwo", \%post); + +my $content2 = $response2->content(); + +check_url($content2, '/tdskjhfsjdkhf2943734'); +check_params($content2, %post); + +print "HEAD request...\n"; + +my $response3 = $ua->head("$url_base/tdskjhfsdfkjhs"); + +if($response3->content() ne '') +{ + print "Content not zero length\n"; + exit(1); +} + +if($response3->code() != 200) +{ + print "Wrong response code\n"; + exit(1); +} + +print "Redirected GET request...\n"; + +my $response4 = $ua->get("$url_base/redirect?key=value"); +exit 4 unless $response4->is_success(); + +my $content4 = $response4->content(); + +check_url($content4, '/redirected'); +check_params($content4); + +print "Cookie tests...\n"; + +# from examples in specs +test_cookies('CUSTOMER=WILE_E_COYOTE', 'CUSTOMER=WILE_E_COYOTE'); +test_cookies('CUSTOMER="WILE_E_COYOTE"; C2="pants"', 'CUSTOMER=WILE_E_COYOTE', 'C2=pants'); +test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001'); +test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001', 'SHIPPING=FEDEX'); +test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', 'Customer=WILE_E_COYOTE'); +test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" ', + 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001'); +test_cookies(qq!\$Version="1"; Customer="WILE_E_COYOTE"; \$Path="/acme"; Part_Number="Rocket_Launcher_0001"; \$Path="/acme"; Shipping="FedEx"; \t \$Path="/acme"!, + 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001', 'Shipping=FedEx'); + +# test the server setting cookies in the UA +require HTTP::Cookies; +$ua->cookie_jar(HTTP::Cookies->new()); +$ua->get("$url_base/set-cookie"); +test_cookies('', 'SetByServer=Value1'); + +sub test_cookies +{ + my ($c_str, @cookies) = @_; + test_cookies2($c_str, @cookies); + $c_str =~ s/;/,/g; + test_cookies2($c_str, @cookies); +} + +sub test_cookies2 +{ + my ($c_str, @cookies) = @_; + my $r; + if($c_str ne '') + { + $r = $ua->get("$url_base/cookie", 'Cookie' => $c_str); + } + else + { + $r = $ua->get("$url_base/cookie"); + } + my $c = $r->content(); + for(@cookies) + { + unless($c =~ m/COOKIE:$_
    /) + { + print "Cookie $_ not found\n"; + exit(1); + } + } +} + + +sub check_url +{ + my ($c,$url) = @_; + unless($c =~ m~URI: (.+?)

    ~) + { + print "URI not found\n"; + exit(1); + } + if($url ne $1) + { + print "Wrong URI in content\n"; + exit(1); + } +} + +sub check_params +{ + my ($c,%params) = @_; + + while($c =~ m/^PARAM:(.+)=(.+?)
    /mg) + { + if($params{$1} ne $2) + { + print "$1=$2 not found in response\n"; + exit(1); + } + delete $params{$1} + } + + my @k = keys %params; + if($#k != -1) + { + print "Didn't find all params\n"; + exit(1); + } +} diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp new file mode 100644 index 00000000..160cb32f --- /dev/null +++ b/test/httpserver/testhttpserver.cpp @@ -0,0 +1,480 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testhttpserver.cpp +// Purpose: Test code for HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include +#include + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#include + +#include "autogen_HTTPException.h" +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "IOStreamGetLine.h" +#include "S3Client.h" +#include "S3Simulator.h" +#include "ServerControl.h" +#include "Test.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +class TestWebServer : public HTTPServer +{ +public: + TestWebServer(); + ~TestWebServer(); + + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); + +}; + +// Build a nice HTML response, so this can also be tested neatly in a browser +void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + // Test redirection mechanism + if(rRequest.GetRequestURI() == "/redirect") + { + rResponse.SetAsRedirect("/redirected"); + return; + } + + // Set a cookie? + if(rRequest.GetRequestURI() == "/set-cookie") + { + rResponse.SetCookie("SetByServer", "Value1"); + } + + #define DEFAULT_RESPONSE_1 "\nTEST SERVER RESPONSE\n

    Test response

    \n

    URI: " + #define DEFAULT_RESPONSE_3 "

    \n

    Query string: " + #define DEFAULT_RESPONSE_4 "

    \n

    Method: " + #define DEFAULT_RESPONSE_5 "

    \n

    Decoded query:
    " + #define DEFAULT_RESPONSE_6 "

    \n

    Content type: " + #define DEFAULT_RESPONSE_7 "

    \n

    Content length: " + #define DEFAULT_RESPONSE_8 "

    \n

    Cookies:
    \n" + #define DEFAULT_RESPONSE_2 "

    \n\n\n" + + rResponse.SetResponseCode(HTTPResponse::Code_OK); + rResponse.SetContentType("text/html"); + rResponse.Write(DEFAULT_RESPONSE_1, sizeof(DEFAULT_RESPONSE_1) - 1); + const std::string &ruri(rRequest.GetRequestURI()); + rResponse.Write(ruri.c_str(), ruri.size()); + rResponse.Write(DEFAULT_RESPONSE_3, sizeof(DEFAULT_RESPONSE_3) - 1); + const std::string &rquery(rRequest.GetQueryString()); + rResponse.Write(rquery.c_str(), rquery.size()); + rResponse.Write(DEFAULT_RESPONSE_4, sizeof(DEFAULT_RESPONSE_4) - 1); + { + const char *m = "????"; + switch(rRequest.GetMethod()) + { + case HTTPRequest::Method_GET: m = "GET "; break; + case HTTPRequest::Method_HEAD: m = "HEAD"; break; + case HTTPRequest::Method_POST: m = "POST"; break; + default: m = "UNKNOWN"; + } + rResponse.Write(m, 4); + } + rResponse.Write(DEFAULT_RESPONSE_5, sizeof(DEFAULT_RESPONSE_5) - 1); + { + const HTTPRequest::Query_t &rquery(rRequest.GetQuery()); + for(HTTPRequest::Query_t::const_iterator i(rquery.begin()); i != rquery.end(); ++i) + { + rResponse.Write("\nPARAM:", 7); + rResponse.Write(i->first.c_str(), i->first.size()); + rResponse.Write("=", 1); + rResponse.Write(i->second.c_str(), i->second.size()); + rResponse.Write("
    \n", 4); + } + } + rResponse.Write(DEFAULT_RESPONSE_6, sizeof(DEFAULT_RESPONSE_6) - 1); + const std::string &rctype(rRequest.GetContentType()); + rResponse.Write(rctype.c_str(), rctype.size()); + rResponse.Write(DEFAULT_RESPONSE_7, sizeof(DEFAULT_RESPONSE_7) - 1); + { + char l[32]; + rResponse.Write(l, ::sprintf(l, "%d", rRequest.GetContentLength())); + } + rResponse.Write(DEFAULT_RESPONSE_8, sizeof(DEFAULT_RESPONSE_8) - 1); + const HTTPRequest::CookieJar_t *pcookies = rRequest.GetCookies(); + if(pcookies != 0) + { + HTTPRequest::CookieJar_t::const_iterator i(pcookies->begin()); + for(; i != pcookies->end(); ++i) + { + char t[512]; + rResponse.Write(t, ::sprintf(t, "COOKIE:%s=%s
    \n", i->first.c_str(), i->second.c_str())); + } + } + rResponse.Write(DEFAULT_RESPONSE_2, sizeof(DEFAULT_RESPONSE_2) - 1); +} + +TestWebServer::TestWebServer() {} +TestWebServer::~TestWebServer() {} + +int test(int argc, const char *argv[]) +{ + if(argc >= 2 && ::strcmp(argv[1], "server") == 0) + { + // Run a server + TestWebServer server; + return server.Main("doesnotexist", argc - 1, argv + 1); + } + + if(argc >= 2 && ::strcmp(argv[1], "s3server") == 0) + { + // Run a server + S3Simulator server; + return server.Main("doesnotexist", argc - 1, argv + 1); + } + + // Start the server + int pid = LaunchServer("./test server testfiles/httpserver.conf", "testfiles/httpserver.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid <= 0) + { + return 0; + } + + // Run the request script + TEST_THAT(::system("perl testfiles/testrequests.pl") == 0); + + #ifndef WIN32 + signal(SIGPIPE, SIG_IGN); + #endif + + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + for (int i = 0; i < 4; i++) + { + HTTPRequest request(HTTPRequest::Method_GET, + "/test-one/34/341s/234?p1=vOne&p2=vTwo"); + + if (i < 2) + { + // first set of passes has keepalive off by default, + // so when i == 1 the socket has already been closed + // by the server, and we'll get -EPIPE when we try + // to send the request. + request.SetClientKeepAliveRequested(false); + } + else + { + request.SetClientKeepAliveRequested(true); + } + + if (i == 1) + { + sleep(1); // need time for our process to realise + // that the peer has died, otherwise no SIGPIPE :( + TEST_CHECK_THROWS(request.Send(sock, + IOStream::TimeOutInfinite), + ConnectionException, SocketWriteError); + sock.Close(); + sock.Open(Socket::TypeINET, "localhost", 1080); + continue; + } + else + { + request.Send(sock, IOStream::TimeOutInfinite); + } + + HTTPResponse response; + response.Receive(sock); + + TEST_THAT(response.GetResponseCode() == HTTPResponse::Code_OK); + TEST_THAT(response.GetContentType() == "text/html"); + + IOStreamGetLine getline(response); + std::string line; + + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("TEST SERVER RESPONSE", + line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Test response

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    URI: /test-one/34/341s/234

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Query string: p1=vOne&p2=vTwo

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Method: GET

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Decoded query:
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("PARAM:p1=vOne
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("PARAM:p2=vTwo

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Content type:

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Content length: -1

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Cookies:
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + } + + // Kill it + TEST_THAT(KillServer(pid)); + + #ifdef WIN32 + TEST_THAT(unlink("testfiles/httpserver.pid") == 0); + #else + TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); + #endif + + // correct, official signature should succeed, with lower-case header + { + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html + HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); + request.SetHostName("johnsmith.s3.amazonaws.com"); + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", + "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + simulator.Handle(request, response); + TEST_EQUAL(200, response.GetResponseCode()); + + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + } + + // modified signature should fail + { + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html + HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); + request.SetHostName("johnsmith.s3.amazonaws.com"); + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", + "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB="); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + simulator.Handle(request, response); + TEST_EQUAL(401, response.GetResponseCode()); + + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("" + "Internal Server Error\n" + "

    Internal Server Error

    \n" + "

    An error, type Authentication Failed occured " + "when processing the request.

    " + "

    Please try again later.

    \n" + "\n", response_data); + } + + // S3Client tests + { + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + S3Client client(&simulator, "johnsmith.s3.amazonaws.com", + "0PN5J17HBGZHT7JJ3X82", + "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); + + HTTPResponse response = client.GetObject("/photos/puppy.jpg"); + TEST_EQUAL(200, response.GetResponseCode()); + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + + // make sure that assigning to HTTPResponse does clear stream + response = client.GetObject("/photos/puppy.jpg"); + TEST_EQUAL(200, response.GetResponseCode()); + response_data = std::string((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + + response = client.GetObject("/nonexist"); + TEST_EQUAL(404, response.GetResponseCode()); + + FileStream fs("testfiles/testrequests.pl"); + response = client.PutObject("/newfile", fs); + TEST_EQUAL(200, response.GetResponseCode()); + + response = client.GetObject("/newfile"); + TEST_EQUAL(200, response.GetResponseCode()); + TEST_THAT(fs.CompareWith(response)); + TEST_EQUAL(0, ::unlink("testfiles/newfile")); + } + + { + HTTPRequest request(HTTPRequest::Method_PUT, + "/newfile"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("authorization", "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw="); + request.AddHeader("Content-Type", "text/plain"); + + FileStream fs("testfiles/testrequests.pl"); + fs.CopyStreamTo(request); + request.SetForReading(); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + simulator.Handle(request, response); + + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + TEST_EQUAL(0, response.GetSize()); + + FileStream f1("testfiles/testrequests.pl"); + FileStream f2("testfiles/newfile"); + TEST_THAT(f1.CompareWith(f2)); + TEST_EQUAL(0, ::unlink("testfiles/newfile")); + } + + // Start the S3Simulator server + pid = LaunchServer("./test s3server testfiles/s3simulator.conf", + "testfiles/s3simulator.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid <= 0) + { + return 0; + } + + sock.Close(); + sock.Open(Socket::TypeINET, "localhost", 1080); + + { + HTTPRequest request(HTTPRequest::Method_GET, "/nonexist"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, IOStream::TimeOutInfinite); + + HTTPResponse response; + response.Receive(sock); + std::string value; + TEST_EQUAL(404, response.GetResponseCode()); + } + + #ifndef WIN32 // much harder to make files inaccessible on WIN32 + // Make file inaccessible, should cause server to return a 403 error, + // unless of course the test is run as root :) + { + TEST_THAT(chmod("testfiles/testrequests.pl", 0) == 0); + HTTPRequest request(HTTPRequest::Method_GET, + "/testrequests.pl"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, IOStream::TimeOutInfinite); + + HTTPResponse response; + response.Receive(sock); + std::string value; + TEST_EQUAL(403, response.GetResponseCode()); + TEST_THAT(chmod("testfiles/testrequests.pl", 0755) == 0); + } + #endif + + { + HTTPRequest request(HTTPRequest::Method_GET, + "/testrequests.pl"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, IOStream::TimeOutInfinite); + + HTTPResponse response; + response.Receive(sock); + std::string value; + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY", response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("text/plain", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + + FileStream file("testfiles/testrequests.pl"); + TEST_THAT(file.CompareWith(response)); + } + + { + HTTPRequest request(HTTPRequest::Method_PUT, + "/newfile"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:kfY1m6V3zTufRy2kj92FpQGKz4M="); + request.AddHeader("Content-Type", "text/plain"); + FileStream fs("testfiles/testrequests.pl"); + HTTPResponse response; + request.SendWithStream(sock, + IOStream::TimeOutInfinite /* or 10000 milliseconds */, + &fs, response); + std::string value; + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + TEST_EQUAL(0, response.GetSize()); + + FileStream f1("testfiles/testrequests.pl"); + FileStream f2("testfiles/newfile"); + TEST_THAT(f1.CompareWith(f2)); + } + + // Kill it + TEST_THAT(KillServer(pid)); + + #ifdef WIN32 + TEST_THAT(unlink("testfiles/s3simulator.pid") == 0); + #else + TestRemoteProcessMemLeaks("generic-httpserver.memleaks"); + #endif + + return 0; +} + diff --git a/test/raidfile/testextra b/test/raidfile/testextra new file mode 100644 index 00000000..1f4fbcb2 --- /dev/null +++ b/test/raidfile/testextra @@ -0,0 +1,7 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/1_0 +mkdir testfiles/1_1 +mkdir testfiles/1_2 +mkdir testfiles/2 diff --git a/test/raidfile/testfiles/raidfile.conf b/test/raidfile/testfiles/raidfile.conf new file mode 100644 index 00000000..6c6d02f9 --- /dev/null +++ b/test/raidfile/testfiles/raidfile.conf @@ -0,0 +1,30 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + +disc1 +{ + SetNumber = 1 + BlockSize = 2048 + Dir0 = testfiles/1_0 + Dir1 = testfiles/1_1 + Dir2 = testfiles/1_2 +} + +disc2 +{ + SetNumber = 2 + BlockSize = 2048 + Dir0 = testfiles/2 + Dir1 = testfiles/2 + Dir2 = testfiles/2 +} + + + diff --git a/test/raidfile/testraidfile.cpp b/test/raidfile/testraidfile.cpp new file mode 100644 index 00000000..160de5c9 --- /dev/null +++ b/test/raidfile/testraidfile.cpp @@ -0,0 +1,981 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: test/raidfile/test.cpp +// Purpose: Test RaidFile system +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include +#include + +#ifdef HAVE_SYSCALL +#include +#endif + +#include + +#include "Test.h" +#include "RaidFileController.h" +#include "RaidFileWrite.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "Guards.h" + +#include "MemLeakFindOn.h" + +#define RAID_BLOCK_SIZE 2048 +#define RAID_NUMBER_DISCS 3 + +#define TEST_DATA_SIZE (8*1024 + 173) + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + #define TRF_CAN_INTERCEPT +#endif + + +#ifdef TRF_CAN_INTERCEPT +// function in intercept.cpp for setting up errors +void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror); +bool intercept_triggered(); +void intercept_clear_setup(); +#endif + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +void testReadingFileContents(int set, const char *filename, void *data, int datasize, bool TestRAIDProperties, int UsageInBlocks = -1) +{ + // Work out which disc is the "start" disc. + int h = 0; + int n = 0; + while(filename[n] != 0) + { + h += filename[n]; + n++; + } + int startDisc = h % RAID_NUMBER_DISCS; + +//printf("UsageInBlocks = %d\n", UsageInBlocks); + + // sizes of data to read + static int readsizes[] = {2047, 1, 1, 2047, 12, 1, 1, RAID_BLOCK_SIZE - (12+1+1), RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 246, (RAID_BLOCK_SIZE * 3) + 3, 243}; + + // read the data in to test it + char testbuff[(RAID_BLOCK_SIZE * 3) + 128]; // bigger than the max request above! + std::auto_ptr pread = RaidFileRead::Open(set, filename); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + //printf("%d, %d\n", pread->GetFileSize(), datasize); + TEST_THAT(pread->GetFileSize() == datasize); + IOStream &readstream1 = *(pread.get()); + int dataread = 0; + int r; + int readsize = readsizes[0]; + int bsc = 1; + while((r = readstream1.Read(testbuff, readsize)) > 0) + { + //printf("== read, asked: %d actual: %d\n", readsize, r); + TEST_THAT(((dataread+r) == datasize) || r == readsize); + TEST_THAT(r > 0); + TEST_THAT(readstream1.StreamDataLeft()); // check IOStream interface is correct + for(int z = 0; z < r; ++z) + { + TEST_THAT(((char*)data)[dataread+z] == testbuff[z]); + /*if(((char*)data)[dataread+z] != testbuff[z]) + { + printf("z = %d\n", z); + }*/ + } + // Next size... + if(bsc <= (int)((sizeof(readsizes) / sizeof(readsizes[0])) - 1)) + { + readsize = readsizes[bsc++]; + } + dataread += r; + } + TEST_THAT(dataread == datasize); + pread->Close(); + + // open and close it... + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream2 = *(pread.get()); + + // positions to try seeking too.. + static int seekpos[] = {0, 1, 2, 887, 887+256 /* no seek required */, RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 1, RAID_BLOCK_SIZE - 1, RAID_BLOCK_SIZE*3, RAID_BLOCK_SIZE + 23, RAID_BLOCK_SIZE * 4, RAID_BLOCK_SIZE * 4 + 1}; + + for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) + { + //printf("== seekpos = %d\n", seekpos[p]); + // only try if test file size is big enough + if(seekpos[p]+256 > datasize) continue; + + readstream2.Seek(seekpos[p], IOStream::SeekType_Absolute); + TEST_THAT(readstream2.Read(testbuff, 256) == 256); + TEST_THAT(readstream2.GetPosition() == seekpos[p] + 256); + TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); + } + + // open and close it... + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream3 = *(pread.get()); + + int pos = 0; + for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) + { + // only try if test file size is big enough + if(seekpos[p]+256 > datasize) continue; + + //printf("pos %d, seekpos %d, p %d\n", pos, seekpos[p], p); + + readstream3.Seek(seekpos[p] - pos, IOStream::SeekType_Relative); + TEST_THAT(readstream3.Read(testbuff, 256) == 256); + pos = seekpos[p] + 256; + TEST_THAT(readstream3.GetPosition() == pos); + TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); + } + + // Straight read of file + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream4 = *(pread.get()); + pos = 0; + int bytesread = 0; + while((r = readstream4.Read(testbuff, 988)) != 0) + { + TEST_THAT(readstream4.StreamDataLeft()); // check IOStream interface is behaving as expected + + // check contents + TEST_THAT(::memcmp(((char*)data) + pos, testbuff, r) == 0); + + // move on + pos += r; + bytesread += r; + } + TEST_THAT(!readstream4.StreamDataLeft()); // check IOStream interface is correct + pread.reset(); + + // Be nasty, and create some errors for the RAID stuff to recover from... + if(TestRAIDProperties) + { + char stripe1fn[256], stripe1fnRename[256]; + sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); + sprintf(stripe1fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, startDisc, + filename); + char stripe2fn[256], stripe2fnRename[256]; + sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + sprintf(stripe2fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + + // Read with stripe1 + parity + TEST_THAT(::rename(stripe2fn, stripe2fnRename) == 0); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(::rename(stripe2fnRename, stripe2fn) == 0); + + // Read with stripe2 + parity + TEST_THAT(::rename(stripe1fn, stripe1fnRename) == 0); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(::rename(stripe1fnRename, stripe1fn) == 0); + + // Munged filename for avoidance + char mungefilename[256]; + char filenamepart[256]; + sprintf(filenamepart, "%s.rf", filename); + int m = 0, s = 0; + while(filenamepart[s] != '\0') + { + if(filenamepart[s] == '/') + { + mungefilename[m++] = '_'; + } + else if(filenamepart[s] == '_') + { + mungefilename[m++] = '_'; + mungefilename[m++] = '_'; + } + else + { + mungefilename[m++] = filenamepart[s]; + } + s++; + } + mungefilename[m++] = '\0'; + char stripe1munge[256]; + sprintf(stripe1munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR ".raidfile-unreadable" + DIRECTORY_SEPARATOR "%s", set, startDisc, + mungefilename); + char stripe2munge[256]; + sprintf(stripe2munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR ".raidfile-unreadable" + DIRECTORY_SEPARATOR "%s", set, + (startDisc + 1) % RAID_NUMBER_DISCS, mungefilename); + + +#ifdef TRF_CAN_INTERCEPT + // Test I/O errors on opening + // stripe 1 + intercept_setup_error(stripe1fn, 0, EIO, SYS_open); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + + // Test error in reading stripe 2 + intercept_setup_error(stripe2fn, 0, EIO, SYS_open); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + + // Test I/O errors on seeking + // stripe 1, if the file is bigger than the minimum thing that it'll get seeked for + if(datasize > 257) + { + intercept_setup_error(stripe1fn, 1, EIO, SYS_lseek); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + } + + // Stripe 2, only if the file is big enough to merit this + if(datasize > (RAID_BLOCK_SIZE + 4)) + { + intercept_setup_error(stripe2fn, 1, EIO, SYS_lseek); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + } + + // Test I/O errors on read, but only if the file is size greater than 0 + if(datasize > 0) + { + // Where shall we error after? + int errafter = datasize / 4; + + // Test error in reading stripe 1 + intercept_setup_error(stripe1fn, errafter, EIO, SYS_readv); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + + // Can only test error if file size > RAID_BLOCK_SIZE, as otherwise stripe2 has nothing in it + if(datasize > RAID_BLOCK_SIZE) + { + // Test error in reading stripe 2 + intercept_setup_error(stripe2fn, errafter, EIO, SYS_readv); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + } + } +#endif // TRF_CAN_INTERCEPT + } +} + + +void testReadWriteFileDo(int set, const char *filename, void *data, int datasize, bool DoTransform) +{ + // Work out which disc is the "start" disc. + int h = 0; + int n = 0; + while(filename[n] != 0) + { + h += filename[n]; + n++; + } + int startDisc = h % RAID_NUMBER_DISCS; + + // Another to test the transform works OK... + RaidFileWrite write4(set, filename); + write4.Open(); + write4.Write(data, datasize); + // This time, don't discard and transform it to a RAID File + char writefnPre[256]; + sprintf(writefnPre, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rfwX", set, startDisc, filename); + TEST_THAT(TestFileExists(writefnPre)); + char writefn[256]; + sprintf(writefn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rfw", set, startDisc, filename); + int usageInBlocks = write4.GetDiscUsageInBlocks(); + write4.Commit(DoTransform); + // Check that files are nicely done... + if(!DoTransform) + { + TEST_THAT(TestFileExists(writefn)); + TEST_THAT(!TestFileExists(writefnPre)); + } + else + { + TEST_THAT(!TestFileExists(writefn)); + TEST_THAT(!TestFileExists(writefnPre)); + // Stripe file sizes + int fullblocks = datasize / RAID_BLOCK_SIZE; + int leftover = datasize - (fullblocks * RAID_BLOCK_SIZE); + int fs1 = -2; + if((fullblocks & 1) == 0) + { + // last block of data will be on the first stripe + fs1 = ((fullblocks / 2) * RAID_BLOCK_SIZE) + leftover; + } + else + { + // last block is on second stripe + fs1 = ((fullblocks / 2)+1) * RAID_BLOCK_SIZE; + } + char stripe1fn[256]; + sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); + TEST_THAT(TestGetFileSize(stripe1fn) == fs1); + char stripe2fn[256]; + sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + TEST_THAT(TestGetFileSize(stripe2fn) == (int)(datasize - fs1)); + // Parity file size + char parityfn[256]; + sprintf(parityfn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 2) % RAID_NUMBER_DISCS, filename); + // Mildly complex calculation + unsigned int blocks = datasize / RAID_BLOCK_SIZE; + unsigned int bytesOver = datasize % RAID_BLOCK_SIZE; + int paritysize = (blocks / 2) * RAID_BLOCK_SIZE; + // Then add in stuff for the last couple of blocks + if((blocks & 1) == 0) + { + if(bytesOver == 0) + { + paritysize += sizeof(RaidFileRead::FileSizeType); + } + else + { + paritysize += (bytesOver == sizeof(RaidFileRead::FileSizeType))?(RAID_BLOCK_SIZE+sizeof(RaidFileRead::FileSizeType)):bytesOver; + } + } + else + { + paritysize += RAID_BLOCK_SIZE; + if(bytesOver == 0 || bytesOver >= (RAID_BLOCK_SIZE-sizeof(RaidFileRead::FileSizeType))) + { + paritysize += sizeof(RaidFileRead::FileSizeType); + } + } + //printf("datasize = %d, calc paritysize = %d, actual size of file = %d\n", datasize, paritysize, TestGetFileSize(parityfn)); + TEST_THAT(TestGetFileSize(parityfn) == paritysize); + //printf("stripe1 size = %d, stripe2 size = %d, parity size = %d\n", TestGetFileSize(stripe1fn), TestGetFileSize(stripe2fn), TestGetFileSize(parityfn)); + + // Check that block calculation is correct + //printf("filesize = %d\n", datasize); + #define TO_BLOCKS_ROUND_UP(x) (((x) + (RAID_BLOCK_SIZE-1)) / RAID_BLOCK_SIZE) + TEST_THAT(usageInBlocks == (TO_BLOCKS_ROUND_UP(paritysize) + TO_BLOCKS_ROUND_UP(fs1) + TO_BLOCKS_ROUND_UP(datasize - fs1))); + + // See about whether or not the files look correct + char testblock[1024]; // compiler bug? This can't go in the block below without corrupting stripe2fn... + if(datasize > (3*1024)) + { + int f; + TEST_THAT((f = ::open(stripe1fn, O_RDONLY | O_BINARY, + 0)) != -1); + TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); + for(unsigned int q = 0; q < sizeof(testblock); ++q) + { + TEST_THAT(testblock[q] == ((char*)data)[q]); + } + ::close(f); + TEST_THAT((f = ::open(stripe2fn, O_RDONLY | O_BINARY, + 0)) != -1); + TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); + for(unsigned int q = 0; q < sizeof(testblock); ++q) + { + TEST_THAT(testblock[q] == ((char*)data)[q+RAID_BLOCK_SIZE]); + } + ::close(f); + } + } + + // See if the contents look right + testReadingFileContents(set, filename, data, datasize, DoTransform /* only test RAID stuff if it has been transformed to RAID */, usageInBlocks); +} + +void testReadWriteFile(int set, const char *filename, void *data, int datasize) +{ + // Test once, transforming it... + testReadWriteFileDo(set, filename, data, datasize, true); + + // And then again, not transforming it + std::string fn(filename); + fn += "NT"; + testReadWriteFileDo(set, fn.c_str(), data, datasize, false); +} + +bool list_matches(const std::vector &rList, const char *compareto[]) +{ + // count in compare to + int count = 0; + while(compareto[count] != 0) + count++; + + if((int)rList.size() != count) + { + return false; + } + + // Space for bools + bool *found = new bool[count]; + + for(int c = 0; c < count; ++c) + { + found[c] = false; + } + + for(int c = 0; c < count; ++c) + { + bool f = false; + for(int l = 0; l < (int)rList.size(); ++l) + { + if(rList[l] == compareto[c]) + { + f = true; + break; + } + } + found[c] = f; + } + + bool ret = true; + for(int c = 0; c < count; ++c) + { + if(found[c] == false) + { + ret = false; + } + } + + delete [] found; + + return ret; +} + +void test_overwrites() +{ + // Opening twice is bad + { + RaidFileWrite writeA(0, "overwrite_A"); + writeA.Open(); + writeA.Write("TESTTEST", 8); + + { +#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK + RaidFileWrite writeA2(0, "overwrite_A"); + TEST_CHECK_THROWS(writeA2.Open(), RaidFileException, FileIsCurrentlyOpenForWriting); +#endif + } + } + + // But opening a file which has previously been open, but isn't now, is OK. + + // Generate a random pre-existing write file (and ensure that it doesn't exist already) + int f; + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "overwrite_B.rfwX", + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0755)) != -1); + TEST_THAT(::write(f, "TESTTEST", 8) == 8); + ::close(f); + + // Attempt to overwrite it, which should work nicely. + RaidFileWrite writeB(0, "overwrite_B"); + writeB.Open(); + writeB.Write("TEST", 4); + TEST_THAT(writeB.GetFileSize() == 4); + writeB.Commit(); +} + + +int test(int argc, const char *argv[]) +{ + #ifndef TRF_CAN_INTERCEPT + printf("NOTE: Skipping intercept based tests on this platform.\n\n"); + #endif + + // Initialise the controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles" DIRECTORY_SEPARATOR "raidfile.conf"); + + // some data + char data[TEST_DATA_SIZE]; + R250 random(619); + for(unsigned int l = 0; l < sizeof(data); ++l) + { + data[l] = random.next() & 0xff; + } + char data2[57]; + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + data2[l] = l; + } + + // Try creating a directory + RaidFileWrite::CreateDirectory(0, "test-dir"); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_1" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(RaidFileRead::DirectoryExists(0, "test-dir")); + TEST_THAT(!RaidFileRead::DirectoryExists(0, "test-dir-not")); + + + // Test converting to disc set names + { + std::string n1(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 0)); + std::string n2(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 1)); + std::string n3(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 2)); + std::string n4(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 3)); + TEST_THAT(n1 != n2); + TEST_THAT(n2 != n3); + TEST_THAT(n1 != n3); + TEST_THAT(n1 == n4); // ie wraps around + BOX_TRACE("Gen paths = '" << n1 << "', '" << n2 << + "', '" << n3); + } + + // Test that creating and deleting a RaidFile with the wrong + // reference counts throws the expected errors. + { + RaidFileWrite write1(0, "write1", 1); + write1.Open(); + write1.Commit(); + TEST_CHECK_THROWS(write1.Delete(), RaidFileException, + RequestedDeleteReferencedFile); + } + + { + RaidFileWrite write1(0, "write1", 0); + write1.Open(true); + TEST_CHECK_THROWS(write1.Commit(), RaidFileException, + RequestedModifyUnreferencedFile); + write1.Delete(); + } + + { + TEST_CHECK_THROWS(RaidFileWrite write1(0, "write1", 2), + RaidFileException, + RequestedModifyMultiplyReferencedFile); + } + + // Create a RaidFile + RaidFileWrite write1(0, "test1"); + IOStream &write1stream = write1; // use the stream interface where possible + write1.Open(); + write1stream.Write(data, sizeof(data)); + write1stream.Seek(1024, IOStream::SeekType_Absolute); + write1stream.Write(data2, sizeof(data2)); + write1stream.Seek(1024, IOStream::SeekType_Relative); + write1stream.Write(data2, sizeof(data2)); + write1stream.Seek(0, IOStream::SeekType_End); + write1stream.Write(data, sizeof(data)); + + // Before it's deleted, check to see the contents are as expected + int f; + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfwX", O_RDONLY | O_BINARY, 0)) + >= 0); + char buffer[sizeof(data)]; + int bytes_read = ::read(f, buffer, sizeof(buffer)); + TEST_THAT(bytes_read == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+1024] == data2[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); + } + TEST_THAT(::lseek(f, sizeof(data), SEEK_SET) == sizeof(buffer)); + bytes_read = ::read(f, buffer, sizeof(buffer)); + TEST_THAT(bytes_read == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + // make sure that's the end of the file + TEST_THAT(::read(f, buffer, sizeof(buffer)) == 0); + ::close(f); + + // Commit the data + write1.Commit(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + >= 0); + ::close(f); + + // Now try and read it + { + std::auto_ptr pread = RaidFileRead::Open(0, "test1"); + TEST_THAT(pread->GetFileSize() == sizeof(buffer)*2); + + char buffer[sizeof(data)]; + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+1024] == data2[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); + } + pread->Seek(sizeof(data), IOStream::SeekType_Absolute); + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + // make sure that's the end of the file + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + // Seek backwards a bit + pread->Seek(-1024, IOStream::SeekType_Relative); + TEST_THAT(pread->Read(buffer, 1024) == 1024); + // make sure that's the end of the file + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + // Test seeking to end works + pread->Seek(-1024, IOStream::SeekType_Relative); + TEST_THAT(pread->Read(buffer, 512) == 512); + pread->Seek(0, IOStream::SeekType_End); + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + } + + // Delete it + RaidFileWrite writeDel(0, "test1"); + writeDel.Delete(); + + // And again... + RaidFileWrite write2(0, "test1"); + write2.Open(); + write2.Write(data, sizeof(data)); + // This time, discard it + write2.Discard(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + == -1); + + // And leaving it there... + RaidFileWrite writeLeave(0, "test1"); + writeLeave.Open(); + writeLeave.Write(data, sizeof(data)); + // This time, commit it + writeLeave.Commit(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + != -1); + ::close(f); + + // Then check that the thing will refuse to open it again. + RaidFileWrite write3(0, "test1"); + TEST_CHECK_THROWS(write3.Open(), RaidFileException, CannotOverwriteExistingFile); + + // Test overwrite behaviour + test_overwrites(); + + // Then... open it again allowing overwrites + RaidFileWrite write3b(0, "test1"); + write3b.Open(true); + // Write something + write3b.Write(data + 3, sizeof(data) - 3); + write3b.Commit(); + // Test it + testReadingFileContents(0, "test1", data+3, sizeof(data) - 3, false + /* TestRAIDProperties */); + + // And once again, but this time making it a raid file + RaidFileWrite write3c(0, "test1"); + write3c.Open(true); + // Write something + write3c.Write(data + 7, sizeof(data) - 7); + write3c.Commit(true); // make RAID + // Test it + testReadingFileContents(0, "test1", data+7, sizeof(data) - 7, false + /*TestRAIDProperties*/); + + // Test opening a file which doesn't exist + TEST_CHECK_THROWS( + std::auto_ptr preadnotexist = RaidFileRead::Open(1, "doesnt-exist"), + RaidFileException, RaidFileDoesntExist); + + { + // Test unrecoverable damage + RaidFileWrite w(0, "damage"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + + // Try removing the parity file + TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "damage.rf", + "testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "damage.rf-NT") == 0); + { + std::auto_ptr pr0 = RaidFileRead::Open(0, "damage"); + pr0->Read(buffer, sizeof(data)); + } + TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf-NT", "testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); + + // Delete one of the files + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "damage.rf") == 0); // stripe 1 + +#ifdef TRF_CAN_INTERCEPT + // Open it and read... + { + intercept_setup_error("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "damage.rf", 0, EIO, SYS_read); // stripe 2 + std::auto_ptr pr1 = RaidFileRead::Open(0, "damage"); + TEST_CHECK_THROWS( + pr1->Read(buffer, sizeof(data)), + RaidFileException, OSError); + + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + } +#endif //TRF_CAN_INTERCEPT + + // Delete another + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); // parity + + TEST_CHECK_THROWS( + std::auto_ptr pread2 = RaidFileRead::Open(0, "damage"), + RaidFileException, FileIsDamagedNotRecoverable); + } + + // Test reading a directory + { + RaidFileWrite::CreateDirectory(0, "dirread"); + // Make some contents + RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "dfsdf1"); + RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "ponwq2"); + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "sdf9873241"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + } + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "fsdcxjni3242"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + } + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "cskjnds3"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(false); + } + + const static char *dir_list1[] = {"dfsdf1", "ponwq2", 0}; + const static char *file_list1[] = {"sdf9873241", "fsdcxjni3242", "cskjnds3", 0}; + const static char *file_list2[] = {"fsdcxjni3242", "cskjnds3", 0}; + + std::vector names; + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_DirsOnly, names)); + TEST_THAT(list_matches(names, dir_list1)); + // Delete things + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + // Delete something else so that it's not recoverable + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(false == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + // And finally... + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list2)); + } + + // Check that sizes are reported correctly for non-raid discs + { + int sizeInBlocks = (sizeof(data) + RAID_BLOCK_SIZE - 1) / RAID_BLOCK_SIZE; + // for writing + { + RaidFileWrite write(2, "testS"); + write.Open(); + write.Write(data, sizeof(data)); + TEST_THAT(write.GetDiscUsageInBlocks() == sizeInBlocks); + write.Commit(); + } + // for reading + { + std::auto_ptr pread(RaidFileRead::Open(2, "testS")); + TEST_THAT(pread->GetDiscUsageInBlocks() == sizeInBlocks); + } + } + +//printf("SKIPPING tests ------------------\n"); +//return 0; + + // Test a load of transformed things + #define BIG_BLOCK_SIZE (25*1024 + 19) + MemoryBlockGuard bigblock(BIG_BLOCK_SIZE); + R250 randomX2(2165); + for(unsigned int l = 0; l < BIG_BLOCK_SIZE; ++l) + { + ((char*)(void*)bigblock)[l] = randomX2.next() & 0xff; + } + + // First on one size of data, on different discs + testReadWriteFile(0, "testdd", data, sizeof(data)); + testReadWriteFile(0, "test2", bigblock, BIG_BLOCK_SIZE); + testReadWriteFile(1, "testThree", bigblock, BIG_BLOCK_SIZE - 2048); + testReadWriteFile(1, "testX", bigblock, BIG_BLOCK_SIZE - 2289); + testReadWriteFile(1, "testSmall0", data, 0); + testReadWriteFile(1, "testSmall1", data, 1); + testReadWriteFile(1, "testSmall2", data, 2); + testReadWriteFile(1, "testSmall3", data, 3); + testReadWriteFile(1, "testSmall4", data, 4); + testReadWriteFile(0, "testSmall5", data, 5); + testReadWriteFile(0, "testSmall6", data, 6); + testReadWriteFile(1, "testSmall7", data, 7); + testReadWriteFile(1, "testSmall8", data, 8); + testReadWriteFile(1, "testSmall9", data, 9); + testReadWriteFile(1, "testSmall10", data, 10); + // See about a file which is one block bigger than the previous tests + { + char dataonemoreblock[TEST_DATA_SIZE + RAID_BLOCK_SIZE]; + R250 random(715); + for(unsigned int l = 0; l < sizeof(dataonemoreblock); ++l) + { + dataonemoreblock[l] = random.next() & 0xff; + } + testReadWriteFile(0, "testfour", dataonemoreblock, sizeof(dataonemoreblock)); + } + + // Some more nasty sizes + static int nastysize[] = {0, 1, 2, 7, 8, 9, (RAID_BLOCK_SIZE/2)+3, + RAID_BLOCK_SIZE-9, RAID_BLOCK_SIZE-8, RAID_BLOCK_SIZE-7, RAID_BLOCK_SIZE-6, RAID_BLOCK_SIZE-5, + RAID_BLOCK_SIZE-4, RAID_BLOCK_SIZE-3, RAID_BLOCK_SIZE-2, RAID_BLOCK_SIZE-1}; + for(int o = 0; o <= 5; ++o) + { + for(unsigned int n = 0; n < (sizeof(nastysize)/sizeof(nastysize[0])); ++n) + { + int s = (o*RAID_BLOCK_SIZE)+nastysize[n]; + char fn[64]; + sprintf(fn, "testN%d", s); + testReadWriteFile(n&1, fn, bigblock, s); + } + } + + // Finally, a mega test (not necessary for every run, I would have thought) +/* unsigned int megamax = (1024*128) + 9; + MemoryBlockGuard megablock(megamax); + R250 randomX3(183); + for(unsigned int l = 0; l < megamax; ++l) + { + ((char*)(void*)megablock)[l] = randomX3.next() & 0xff; + } + for(unsigned int s = 0; s < megamax; ++s) + { + testReadWriteFile(s & 1, "mega", megablock, s); + RaidFileWrite deleter(s & 1, "mega"); + deleter.Delete(); + RaidFileWrite deleter2(s & 1, "megaNT"); + deleter2.Delete(); + }*/ + + return 0; +} diff --git a/test/win32/Makefile b/test/win32/Makefile new file mode 100644 index 00000000..1212bc6f --- /dev/null +++ b/test/win32/Makefile @@ -0,0 +1,5 @@ +timezone.exe: timezone.cpp Makefile + g++ -g -O0 -mno-cygwin -I../../lib/win32 -o timezone.exe timezone.cpp + +clean: + rm timezone.exe diff --git a/test/win32/testlibwin32.cpp b/test/win32/testlibwin32.cpp new file mode 100644 index 00000000..fb735c56 --- /dev/null +++ b/test/win32/testlibwin32.cpp @@ -0,0 +1,345 @@ +// win32test.cpp : Defines the entry point for the console application. +// + +//#include +#include "Box.h" + +#ifdef WIN32 + +#include +#include +#include + +#include "../../bin/bbackupd/BackupDaemon.h" +#include "BoxPortsAndFiles.h" +#include "emu.h" + +int main(int argc, char* argv[]) +{ + // ACL tests + char* exename = getenv("WINDIR"); + + PSID psidOwner; + PSID psidGroup; + PACL pDacl; + PSECURITY_DESCRIPTOR pSecurityDesc; + + DWORD result = GetNamedSecurityInfo( + exename, // pObjectName + SE_FILE_OBJECT, // ObjectType + DACL_SECURITY_INFORMATION | // SecurityInfo + GROUP_SECURITY_INFORMATION | + OWNER_SECURITY_INFORMATION, + &psidOwner, // ppsidOwner, + &psidGroup, // ppsidGroup, + &pDacl, // ppDacl, + NULL, // ppSacl, + &pSecurityDesc // ppSecurityDescriptor + ); + if (result != ERROR_SUCCESS) + { + printf("Error getting security info for '%s': error %d", + exename, result); + } + assert(result == ERROR_SUCCESS); + + char namebuf[1024]; + char domainbuf[1024]; + SID_NAME_USE nametype; + DWORD namelen = sizeof(namebuf); + DWORD domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, psidOwner, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("Owner:\n"); + printf("User name: %s\n", namebuf); + printf("Domain name: %s\n", domainbuf); + printf("Name type: %d\n", nametype); + printf("\n"); + + namelen = sizeof(namebuf); + domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, psidGroup, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("Group:\n"); + printf("User name: %s\n", namebuf); + printf("Domain name: %s\n", domainbuf); + printf("Name type: %d\n", nametype); + printf("\n"); + + ULONG numEntries; + PEXPLICIT_ACCESS pEntries; + result = GetExplicitEntriesFromAcl + ( + pDacl, // pAcl + &numEntries, // pcCountOfExplicitEntries, + &pEntries // pListOfExplicitEntries + ); + assert(result == ERROR_SUCCESS); + + printf("Found %lu explicit DACL entries for '%s'\n\n", + (unsigned long)numEntries, exename); + + for (ULONG i = 0; i < numEntries; i++) + { + EXPLICIT_ACCESS* pEntry = &(pEntries[i]); + printf("DACL entry %lu:\n", (unsigned long)i); + + DWORD perms = pEntry->grfAccessPermissions; + printf(" Access permissions: ", perms); + + #define PRINT_PERM(name) \ + if (perms & name) \ + { \ + printf(#name " "); \ + perms &= ~name; \ + } + + PRINT_PERM(FILE_ADD_FILE); + PRINT_PERM(FILE_ADD_SUBDIRECTORY); + PRINT_PERM(FILE_ALL_ACCESS); + PRINT_PERM(FILE_APPEND_DATA); + PRINT_PERM(FILE_CREATE_PIPE_INSTANCE); + PRINT_PERM(FILE_DELETE_CHILD); + PRINT_PERM(FILE_EXECUTE); + PRINT_PERM(FILE_LIST_DIRECTORY); + PRINT_PERM(FILE_READ_ATTRIBUTES); + PRINT_PERM(FILE_READ_DATA); + PRINT_PERM(FILE_READ_EA); + PRINT_PERM(FILE_TRAVERSE); + PRINT_PERM(FILE_WRITE_ATTRIBUTES); + PRINT_PERM(FILE_WRITE_DATA); + PRINT_PERM(FILE_WRITE_EA); + PRINT_PERM(STANDARD_RIGHTS_READ); + PRINT_PERM(STANDARD_RIGHTS_WRITE); + PRINT_PERM(SYNCHRONIZE); + PRINT_PERM(DELETE); + PRINT_PERM(READ_CONTROL); + PRINT_PERM(WRITE_DAC); + PRINT_PERM(WRITE_OWNER); + PRINT_PERM(MAXIMUM_ALLOWED); + PRINT_PERM(GENERIC_ALL); + PRINT_PERM(GENERIC_EXECUTE); + PRINT_PERM(GENERIC_WRITE); + PRINT_PERM(GENERIC_READ); + printf("\n"); + + if (perms) + { + printf(" Bits left over: %08x\n", perms); + } + assert(!perms); + + printf(" Access mode: "); + switch(pEntry->grfAccessMode) + { + case NOT_USED_ACCESS: + printf("NOT_USED_ACCESS\n"); break; + case GRANT_ACCESS: + printf("GRANT_ACCESS\n"); break; + case DENY_ACCESS: + printf("DENY_ACCESS\n"); break; + case REVOKE_ACCESS: + printf("REVOKE_ACCESS\n"); break; + case SET_AUDIT_SUCCESS: + printf("SET_AUDIT_SUCCESS\n"); break; + case SET_AUDIT_FAILURE: + printf("SET_AUDIT_FAILURE\n"); break; + default: + printf("Unknown (%08x)\n", pEntry->grfAccessMode); + } + + printf(" Trustee: "); + assert(pEntry->Trustee.pMultipleTrustee == NULL); + assert(pEntry->Trustee.MultipleTrusteeOperation == NO_MULTIPLE_TRUSTEE); + switch(pEntry->Trustee.TrusteeForm) + { + case TRUSTEE_IS_SID: + { + PSID trusteeSid = (PSID)(pEntry->Trustee.ptstrName); + + namelen = sizeof(namebuf); + domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, trusteeSid, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("SID of %s\\%s (%d)\n", domainbuf, namebuf, nametype); + } + break; + case TRUSTEE_IS_NAME: + printf("Name\n"); break; + case TRUSTEE_BAD_FORM: + printf("Bad form\n"); assert(0); + case TRUSTEE_IS_OBJECTS_AND_SID: + printf("Objects and SID\n"); break; + case TRUSTEE_IS_OBJECTS_AND_NAME: + printf("Objects and name\n"); break; + default: + printf("Unknown form\n"); assert(0); + } + + printf(" Trustee type: "); + switch(pEntry->Trustee.TrusteeType) + { + case TRUSTEE_IS_UNKNOWN: + printf("Unknown type.\n"); break; + case TRUSTEE_IS_USER: + printf("User\n"); break; + case TRUSTEE_IS_GROUP: + printf("Group\n"); break; + case TRUSTEE_IS_DOMAIN: + printf("Domain\n"); break; + case TRUSTEE_IS_ALIAS: + printf("Alias\n"); break; + case TRUSTEE_IS_WELL_KNOWN_GROUP: + printf("Well-known group\n"); break; + case TRUSTEE_IS_DELETED: + printf("Deleted account\n"); break; + case TRUSTEE_IS_INVALID: + printf("Invalid trustee type\n"); break; + case TRUSTEE_IS_COMPUTER: + printf("Computer\n"); break; + default: + printf("Unknown type %d\n", pEntry->Trustee.TrusteeType); + assert(0); + } + + printf("\n"); + } + + assert(LocalFree((HLOCAL)pEntries) == 0); + assert(LocalFree((HLOCAL)pSecurityDesc) == 0); + + chdir("c:\\tmp"); + openfile("test", O_CREAT, 0); + struct stat ourfs; + //test our opendir, readdir and closedir + //functions + DIR *ourDir = opendir("C:"); + + if ( ourDir != NULL ) + { + struct dirent *info; + do + { + info = readdir(ourDir); + if (info) printf("File/Dir name is : %s\r\n", info->d_name); + } + while (info != NULL); + + closedir(ourDir); + } + + std::string diry("C:\\Projects\\boxbuild\\testfiles\\"); + ourDir = opendir(diry.c_str()); + if ( ourDir != NULL ) + { + struct dirent *info; + do + { + info = readdir(ourDir); + if (info == NULL) break; + std::string file(diry + info->d_name); + stat(file.c_str(), &ourfs); + if (info) printf("File/Dir name is : %s\r\n", info->d_name); + } + while ( info != NULL ); + + closedir(ourDir); + + } + + stat("c:\\windows", &ourfs); + stat("c:\\autoexec.bat", &ourfs); + printf("Finished dir read\n"); + + //test our getopt function + char * test_argv[] = + { + "foobar.exe", + "-qwc", + "-", + "-c", + "fgfgfg", + "-f", + "-l", + "hello", + "-", + "force-sync", + NULL + }; + int test_argc; + for (test_argc = 0; test_argv[test_argc]; test_argc++) { } + const char* opts = "qwc:l:"; + + assert(getopt(test_argc, test_argv, opts) == 'q'); + assert(getopt(test_argc, test_argv, opts) == 'w'); + assert(getopt(test_argc, test_argv, opts) == 'c'); + assert(strcmp(optarg, "-") == 0); + assert(getopt(test_argc, test_argv, opts) == 'c'); + assert(strcmp(optarg, "fgfgfg") == 0); + assert(getopt(test_argc, test_argv, opts) == '?'); + assert(optopt == 'f'); + assert(getopt(test_argc, test_argv, opts) == 'l'); + assert(strcmp(optarg, "hello") == 0); + assert(getopt(test_argc, test_argv, opts) == -1); + // assert(optopt == 0); // no more options + assert(strcmp(test_argv[optind], "-") == 0); + assert(strcmp(test_argv[optind+1], "force-sync") == 0); + //end of getopt test + + //now test our statfs funct + stat("c:\\cert.cer", &ourfs); + + char *timee; + + timee = ctime(&ourfs.st_mtime); + + if (S_ISREG(ourfs.st_mode)) + { + printf("is a normal file\n"); + } + else + { + printf("is a directory?\n"); + exit(1); + } + + lstat(getenv("WINDIR"), &ourfs); + + if ( S_ISDIR(ourfs.st_mode)) + { + printf("is a directory\n"); + } + else + { + printf("is a file?\n"); + exit(1); + } + + //test the syslog functions + openlog("Box Backup", 0,0); + //the old ones are the best... + syslog(LOG_ERR, "Hello World"); + syslog(LOG_ERR, "Value of int is: %i", 6); + + closelog(); + + /* + //first off get the path name for the default + char buf[MAX_PATH]; + + GetModuleFileName(NULL, buf, sizeof(buf)); + std::string buffer(buf); + std::string conf("-c " + buffer.substr(0,(buffer.find("win32test.exe"))) + "bbackupd.conf"); + //std::string conf( "-c " + buffer.substr(0,(buffer.find("bbackupd.exe"))) + "bbackupd.conf"); + */ + + return 0; +} + +#endif // WIN32 diff --git a/test/win32/timezone.cpp b/test/win32/timezone.cpp new file mode 100644 index 00000000..1d81bcb3 --- /dev/null +++ b/test/win32/timezone.cpp @@ -0,0 +1,87 @@ +#include +#include + +typedef int uid_t; +typedef int gid_t; +typedef int u_int32_t; + +#include "emu.h" + +int main(int argc, char** argv) +{ + time_t time_now = time(NULL); + char* time_str = strdup(asctime(gmtime(&time_now))); + time_str[24] = 0; + + printf("Time now is %d (%s)\n", time_now, time_str); + + char testfile[80]; + snprintf(testfile, sizeof(testfile), "test.%d", time_now); + printf("Test file is: %s\n", testfile); + + _unlink(testfile); + + /* + int fd = open(testfile, O_RDWR | O_CREAT | O_EXCL); + if (fd < 0) + { + perror("open"); + exit(1); + } + close(fd); + */ + + HANDLE fh = CreateFileA(testfile, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + + if (!fh) + { + fprintf(stderr, "Failed to open file '%s': error %d\n", + testfile, GetLastError()); + exit(1); + } + + BY_HANDLE_FILE_INFORMATION fi; + + if (!GetFileInformationByHandle(fh, &fi)) + { + fprintf(stderr, "Failed to get file information for '%s': " + "error %d\n", testfile, GetLastError()); + exit(1); + } + + if (!CloseHandle(fh)) + { + fprintf(stderr, "Failed to close file: error %d\n", + GetLastError()); + exit(1); + } + + time_t created_time = ConvertFileTimeToTime_t(&fi.ftCreationTime); + time_str = strdup(asctime(gmtime(&created_time))); + time_str[24] = 0; + + printf("File created time: %d (%s)\n", created_time, time_str); + + printf("Difference is: %d\n", created_time - time_now); + + if (abs(created_time - time_now) > 30) + { + fprintf(stderr, "Error: time difference too big: " + "bug in emu.h?\n"); + exit(1); + } + + /* + sleep(1); + + if (_unlink(testfile) != 0) + { + perror("Failed to delete test file"); + exit(1); + } + */ + + exit(0); +} diff --git a/win32.bat b/win32.bat new file mode 100644 index 00000000..744c31b3 --- /dev/null +++ b/win32.bat @@ -0,0 +1,33 @@ +@echo off + +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 + +cd .\bin\bbackupquery\ & perl ./../../bin/bbackupquery/makedocumentation.pl.in +cd ..\..\ + +cd .\lib\backupclient & perl ./../../lib/common/makeexception.pl.in BackupStoreException.txt & perl ./../../lib/server/makeprotocol.pl.in Client ./../../bin/bbstored/backupprotocol.txt +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 + +cd ..\..\ + +cd .\bin\bbackupd & perl ./../../lib/common/makeexception.pl.in ClientException.txt + +cd ..\..\ + +cd .\lib\crypto & perl ./../../lib/common/makeexception.pl.in CipherException.txt +cd ..\..\ + +echo server parts - which appears as though some of the clients rely on + +cd .\lib\server & perl ./../../lib/common/makeexception.pl.in ServerException.txt & perl ./../../lib/common/makeexception.pl.in ConnectionException.txt +cd ..\..\ + +perl -pe 's/@PERL@/perl/' ./test/bbackupd/testfiles/bbackupd.conf.in > .\test\bbackupd\testfiles\bbackupd.conf -- cgit v1.2.3 From c2d8fdb71c9dcdc367bc682f21fc86164e8fa33b Mon Sep 17 00:00:00 2001 From: Andreas Beckmann Date: Sat, 21 Jan 2017 21:29:52 -0500 Subject: Import boxbackup_0.11.1~r2837-4.debian.tar.xz [dgit import tarball boxbackup 0.11.1~r2837-4 boxbackup_0.11.1~r2837-4.debian.tar.xz] --- NEWS | 24 ++ NEWS.upstream | 124 ++++++++ README.Debian | 125 ++++++++ boxbackup-client.config | 117 +++++++ boxbackup-client.cron.d | 2 + boxbackup-client.dirs | 5 + boxbackup-client.docs | 3 + boxbackup-client.init | 69 +++++ boxbackup-client.install | 4 + boxbackup-client.manpages | 6 + boxbackup-client.postinst | 349 +++++++++++++++++++++ boxbackup-client.postrm | 15 + boxbackup-client.templates | 137 ++++++++ boxbackup-server.config | 110 +++++++ boxbackup-server.dirs | 6 + boxbackup-server.docs | 3 + boxbackup-server.init | 80 +++++ boxbackup-server.install | 8 + boxbackup-server.logcheck.ignore | 10 + boxbackup-server.manpages | 7 + boxbackup-server.postinst | 212 +++++++++++++ boxbackup-server.postrm | 22 ++ boxbackup-server.templates | 77 +++++ changelog | 332 ++++++++++++++++++++ clean | 146 +++++++++ clean.sh | 8 + compat | 1 + control | 57 ++++ copyright | 48 +++ get-orig-source.sh | 86 ++++++ patches/03-adjust-syslog-facility.diff | 50 +++ patches/05-dont_use_net_for_docs.diff | 12 + patches/06-gcc_4.4_fixes.diff | 17 + patches/series | 3 + po/POTFILES.in | 2 + po/cs.po | 512 ++++++++++++++++++++++++++++++ po/da.po | 515 +++++++++++++++++++++++++++++++ po/de.po | 526 +++++++++++++++++++++++++++++++ po/es.po | 549 +++++++++++++++++++++++++++++++++ po/eu.po | 527 +++++++++++++++++++++++++++++++ po/fi.po | 504 ++++++++++++++++++++++++++++++ po/fr.po | 526 +++++++++++++++++++++++++++++++ po/gl.po | 516 +++++++++++++++++++++++++++++++ po/it.po | 525 +++++++++++++++++++++++++++++++ po/ja.po | 509 ++++++++++++++++++++++++++++++ po/nl.po | 528 +++++++++++++++++++++++++++++++ po/pt.po | 519 +++++++++++++++++++++++++++++++ po/pt_BR.po | 522 +++++++++++++++++++++++++++++++ po/ru.po | 519 +++++++++++++++++++++++++++++++ po/sv.po | 515 +++++++++++++++++++++++++++++++ po/templates.pot | 446 ++++++++++++++++++++++++++ po/vi.po | 514 ++++++++++++++++++++++++++++++ rules | 102 ++++++ source/format | 1 + watch | 4 + 55 files changed, 11156 insertions(+) create mode 100644 NEWS create mode 100644 NEWS.upstream create mode 100644 README.Debian create mode 100644 boxbackup-client.config create mode 100644 boxbackup-client.cron.d create mode 100644 boxbackup-client.dirs create mode 100644 boxbackup-client.docs create mode 100644 boxbackup-client.init create mode 100644 boxbackup-client.install create mode 100644 boxbackup-client.manpages create mode 100644 boxbackup-client.postinst create mode 100644 boxbackup-client.postrm create mode 100644 boxbackup-client.templates create mode 100644 boxbackup-server.config create mode 100644 boxbackup-server.dirs create mode 100644 boxbackup-server.docs create mode 100644 boxbackup-server.init create mode 100644 boxbackup-server.install create mode 100644 boxbackup-server.logcheck.ignore create mode 100644 boxbackup-server.manpages create mode 100644 boxbackup-server.postinst create mode 100644 boxbackup-server.postrm create mode 100644 boxbackup-server.templates create mode 100644 changelog create mode 100644 clean create mode 100644 clean.sh create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 get-orig-source.sh create mode 100644 patches/03-adjust-syslog-facility.diff create mode 100644 patches/05-dont_use_net_for_docs.diff create mode 100644 patches/06-gcc_4.4_fixes.diff create mode 100644 patches/series create mode 100644 po/POTFILES.in create mode 100644 po/cs.po create mode 100644 po/da.po create mode 100644 po/de.po create mode 100644 po/es.po create mode 100644 po/eu.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/gl.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/nl.po create mode 100644 po/pt.po create mode 100644 po/pt_BR.po create mode 100644 po/ru.po create mode 100644 po/sv.po create mode 100644 po/templates.pot create mode 100644 po/vi.po create mode 100755 rules create mode 100644 source/format create mode 100644 watch diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..2c3e7c25 --- /dev/null +++ b/NEWS @@ -0,0 +1,24 @@ +boxbackup (0.11~rc2+r2072-1) unstable; urgency=low + + * The upstream parts of this file have been renamed to a new file called + NEWS.upstream to make the process of updating it easier. + + -- Reinhard Tartler Wed, 01 Apr 2009 10:24:51 +0200 + +boxbackup (0.10-1) unstable; urgency=low + + * This Package has been initially prepared and mantained by Jérôme + Schell since 2004 in a private repository. I like the software, and + decided to take it over in order to have it in Debian. Please note + that I'm actively looking for co-maintainers, so do not hesitate to + get a copy of my bzr branch and share your commits with me. + + The only major change has been to drop the boxbackup-utils package. It + contained only one single command to manage certificates. It has been + moved to the boxbackup-server package. + + The complete debconf integration has been written by Jérôme. It works + for me quite well. If it doesn't for you, please file a bug and CC + Jérôme to that bugreport. Thanks. + + -- Reinhard Tartler Wed, 25 Apr 2007 18:06:04 +0200 diff --git a/NEWS.upstream b/NEWS.upstream new file mode 100644 index 00000000..2ae473dd --- /dev/null +++ b/NEWS.upstream @@ -0,0 +1,124 @@ +The following notes have been copied from the boxbackup trac wiki at +http://www.boxbackup.org/trac/wiki/011?format=txt + +== Changes in This Release == + + * Fixed some bugs with backing up, restoring and comparing [#2 files over 2GB] in compressed size. + * Added new logging infrastructure, allows more control over whether messages are sent to the console or system logs, and at what level of detail. + * Changed keepalive and diff timers to run in real time, not CPU time. + * Enable KeepAlive time by default on new installations, set to 120 seconds. + * Added bbackupctl commands for improved scripting of syncs. + * Fixed a bug with restoring symlinks to directories outside of the backed-up location (thanks to Hans-Joachim Baader) + * Ported unit tests for Windows. + * Added full unit tests for keep-alives and diff timer on most platforms. + * Fixed a number of bugs in the Windows port. + * Added option to send Extended Logs to a file instead of to system logs. + * Added option to log all file access, for debugging when a file is not backed up or causes the backup to fail mysteriously. + * Improved error messages to identify the causes of some errors which were difficult to track down before. + * Added bbackupd option to set the length of time before unused locations are deleted. + * Changed default location of bbackupd.conf on Windows to the same directory as bbackupd.exe. + * Fixed a bug where bbstoreaccounts could modify an account while it was locked by a running backup. + * Improved command-line option handling. + * Added command-line help (-h option) to bbackupd and bbstored. + * Add a new -F option for daemons, which runs in the foreground but still accepts multiple connections, which is what SINGLEPROCESS used to do. + * Fixed compare of timestamps on filesystems which cannot set them more accurately than 1 second. + * Added new backup-start and backup-finish events to the NotifyScript, which can be used to implement more advanced functionality such as snapshotting databases. + * Added a new sample !NotifySysAdmin script for Windows, written by James O'Gorman in VBscript. + * Added support for multiple Box Backup (bbackupd) services on Windows, with different service names and named pipe names, to implement redundancy. + * Fixed bbackupd mysteriously failing to back up if one of the location paths did not exist. + * Fixed entering of international characters into bbackupquery on Windows (instructions) and Unixes with editline. + * Improve Makefiles by reducing verbosity during build, so that any errors and warnings can be seen more easily. + * Added saving of the list of unused root directory entries to the !StoreObjectInfoFile, so that they will persist across restarts of bbackup (thanks to Gary Niemcewicz). + * Updated built-in documentation (program manuals, installation guide and administrator's guide). + * Improved build targets (thanks to James O'Gorman). + * On Unix platforms, all commands have moved from bin to sbin. + +== Source Code == + +The source code for all platforms can be downloaded +[http://www.boxbackup.org/svn/box/packages/boxbackup-0.11rc2.tgz here], although for Windows Native builds please read the [wiki:Installation#Windows Windows installation] notes. + +== Upgrading == + +Upgrade all clients and servers to [wiki:010 0.10] first. + +Remove any Windows services before upgrading (with `bbackupd -r`) and reinstall after upgrading (with `bbackupd -i`). + +New logging options (LogAllFileAccess, ExtendedLogging and [wiki:ManualPages command-line options]) are useful but not required. To use LogAllFileAccess you need to start the daemon with the `-V` option as well. + +The protocol is the same, so it shouldn't require the store server to be updated at the same time as the clients, or even fix the order of updating them. We would recommend that you upgrade the store server first, and then the clients one at a time. + +You might want to either regenerate their configs, or look at the difference between a fresh config and their current one, to enable some useful options like KeepAliveTime (which is enabled by default in new installations) and to think about enabling StoreObjectInfoFile. + +Most syslog messages have changed their format, so any scripts which parse syslog will have to be updated. + +Anyone using SINGLEPROCESS in anger (e.g. to run bbstored as a managed service under daemontools or similar) should shoot themselves quietly in the foot and prepare to change it to '''-F''' after the upgrade. (this was never a documented option, and now behaves a little differently). + +If you have problems with large files (over 2GB compressed) not being backed up, restored or compared, you will need to delete them from the store server to fix them properly. You can do this before or after upgrading, but if they are uploaded again by the 0.10 client then the problem will not be solved. + +You will probably get this error message every backup run: + +{{{ +BACKUP PROBLEM on host your.client.host (unknown) +}}} + +To fix this, edit the !NotifyScript (usually `/etc/box/bbackupd/NotifyScript.sh`), find the line that starts with the word `else`, and add these three lines immediately before it: + +{{{ +elif [ "$1" = backup-start -o "$1" = backup-finish -o "$1" = backup-ok ]; then + # do nothing by default + true +}}} + +== Known Issues == + +[[TicketQuery(status!=closed)]] + +== Credits == + + * '''Martin Ebourne''' reviewed code from Windows port for merge; + + * '''Pierre-Henri Lavigne''' created and started maintaining fink packages for MacOS X; + + * '''Stuart Hickinbottom''', '''Mark''', '''Nestor Arocha Rodriguez''' and '''James Stark''' contributed code; + + * '''Pete Jalajas''' contributed the Windows installer + + * '''Charles Lecklider''' reviewed code from Windows port for merge; + + * '''Kenny Millington''' provided the bbreporter.py Python script; + + * '''Gary Niemcewicz''' tested and fixed support for Microsoft Visual Studio as a compiler; + + * '''James O'Gorman''' hosts our website, wrote a lot of documentation, bought us a real SSL certificate and fixed autoconf problems; + + * '''Ben Summers''' contributed ideas and reviewed code from Windows port for merge; + + * '''Reinhard Tartler''' created and started maintaining Debian packages; + + * '''Per Thomsen''' wrote the Docbook documentation; + + * '''Chris Wilson''' implemented most of the new features, tested Solaris, FreeBSD and MacOS X and helped out on the mailing list; + + * Testing and bug reports (in alphabetical order) + * Tom Albers + * Tobias Balle-Petersen + * Damien B + * Dave Bamford + * Torsten Boob + * Matt Brown (who also lent us a MacOS X laptop for testing) + * Eric Cronin + * Johann Glaser + * Alex Harper + * Guno Heitman + * Stuart Hickinbottom + * Richard Hurt + * Pete Jalajas + * David Kaufman + * kiru + * Paul MacKenzie + * Mitja Muzenic + * Tuukka Pasanen + * Phil Shelley + + * Please let us know if we've missed you out! diff --git a/README.Debian b/README.Debian new file mode 100644 index 00000000..63d47a10 --- /dev/null +++ b/README.Debian @@ -0,0 +1,125 @@ +Quick setup guide for boxbackup system +-------------------------------------- + +NOTE: The debian package should handle most of the configuration +for you via debconf. + +However this is a quick guide if you prefer to do this by +yourself. + +If you want to use debconf to configure Boxbackup, do NOT follow +those explanations. Jump directly to the section on Managing certificates + +Boxbackup-server configuration +------------------------------ + +You need to create the server configuration files contained in +/etc/boxbackup. + +For this you must first use the raidfile-config script. + +raidfile-config /etc/boxbackup 2048 /raid/0.0 /raid/0.1 /raid/0.2 + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default) +- 2048 is the block size of the RAID system, this should be set to + the block size of the underlying filesystem +- the three following path names are the location of the 3 RAID partitions + used by boxbackup to store the backup. They should be on 3 different + physical drive. You can disable the use of userland RAID by specifying + only one path name. + +You should now have a file /etc/boxbackup/raidfile.conf that you can +customize to add another set of disc. + +Now run the bbstored-config script: + +bbstored-config /etc/boxbackup serverhostname bbstored + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default). +- serverhostname is the fqdn name of the server you are installing on, + this is used to determine on wich interface the daemon will listen on. +- bbstored is the user the server will run under, this user is automatically + created by the Debian package. + +Now you have to manage your certificate. See below for this. + +To manage the client accounts use the bbstoreaccounts utility. +To add an account: +bbstoreaccounts create ACCOUNT_NUMBER DISC_SET SOFT_QUOTA HARD_QUOTA + +where: +- ACCOUNT_NUMBER is the account number to create, a 8 digits hexadecimal number. +- DISC_SET is a disc set number defined in /etc/boxbackup/raidfile.conf where the + files for that account will go into. +- SOFT_QUOTA is the soft storage quota size, the client will avoid to upload files + when reaching that limit +- HARD_QUOTA is the hard storage quota size, the server will not store files when + reaching that limit. + +An exemple of invocation: +bbstoreaccounts create 1EF235CA 0 1024M 1250M +(suffixes M, G and B are accepted for quota size meaning respectively Megabytes, +Gigabytes and Blocks) + +Boxbackup-client configuration +------------------------------ + +You need to create the client configuration files contained in +/etc/boxbackup. + +For this you must use the bbackupd-config script. + +bbackupd-config /etc/boxbackup lazy ACCOUNT_NUMBER SERVER_NAME /var/lib/bbackupd BACKUP_DIR [[BACKUP_DIR]...] + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default). +- lazy: backup mode, could be lazy (continuous scan of filesystem) or + snapshot (backup launch by a cron script, see /etc/cron.d/boxbackup-client) +- ACCOUT_NUMBER: your account number provided by the backup server administrator +- SERVER_NAME is the fqdn name of the server you will connect to. +- /var/lib/bbackupd: location of working directory (don't change) +- BACKUP_DIR: a list of directories to backup (they must not contain another + mounted filesystem) + + +Managing certificates +--------------------- + +For this you need to use the bbstored-certs script contained in the +boxbackup-server package. + +To initialise your CA (creates a "ca" directory with private key and certificate in it) launch: +bbstored-certs ca init + +To sign a server certificate: +bbstored-certs ca sign-server server-csr.pem + +To sign a client certificate: +bbstored-certs ca sign clientaccount-csr.pem + +You will find a more detailled documentation on the boxbackup Web site: +http://www.boxbackup.org + + +"Upstream" tarball +------------------ + +Althogh upstream indeed publishes tarballs, the debian package is no +longer based on these tarballs. The reason for this is that the +procedure for creating these tarballs makes package maintenance and +interaction with upstream unnecessary hard. + +Moreover, there seem to be different types of tarballs. Official ones +used for a release and "unofficial" ones that are created for snapshot +releases, causing further confusion. + +Since upstream releases rather seldomly, but we want to distribute +pre-releases and integrate patches from the upstream svn, the package +ships a script in the source that is used to create the orig.tar.gz. + + -- Reinhard Tartler , Wed, 1 Apr 2009 16:24:42 +0200 diff --git a/boxbackup-client.config b/boxbackup-client.config new file mode 100644 index 00000000..a2ed866d --- /dev/null +++ b/boxbackup-client.config @@ -0,0 +1,117 @@ +#!/bin/bash -e + +# Source debconf library +. /usr/share/debconf/confmodule + +# This conf script is capable of backing up +#db_version 2.0 +#db_capb backup + +#db_metaget debconf/priority value +#CONFPRIO=$RET + +# Handle with debconf or not? +db_input medium boxbackup-client/debconf || true +db_go +db_get boxbackup-client/debconf +if [ "$RET" = "false" ]; then + exit 0 +fi + +# Backup mode +db_get boxbackup-client/backupMode +OLDMODE=$RET + +db_input medium boxbackup-client/backupMode || true +db_go + + +# accountNumber +ANOK=0 +while [ $ANOK = 0 ]; do + db_input critical boxbackup-client/accountNumber || true + db_go + + db_get boxbackup-client/accountNumber + + if [ -z `echo $RET | sed 's/[[:xdigit:]]//g'` ]; then + ANOK=1 + fi + + if [ $ANOK = 0 ]; then + db_input critical boxbackup-client/incorrectAccountNumber || true + db_go + fi +done + +# backupServer +db_input critical boxbackup-client/backupServer || true +db_go + +# backupDirs +DIRSOK=0 +while [ $DIRSOK = 0 ]; do + db_input critical boxbackup-client/backupDirs || true + db_go + + db_get boxbackup-client/backupDirs + + if [ ! -z "$RET" ]; then + DIRSOK=1 + for dir in $RET; do + if [ ! -z `echo $dir | sed 's/^[[:space:]]*\/[[:alnum:]\.\_-]*\/*\([[:alnum:]\.\_-]*\/*\)*[[:space:]]*$//g'` ]; then + DIRSOK=0; + fi + done + fi + + if [ $DIRSOK = 0 ]; then + db_input critical boxbackup-client/incorrectDirectories || true + db_go + fi +done + +# UpdateStoreInterval MinimumFileAge MaxUploadWait +#db_get boxbackup-client/backupMode + +# This is a way to get back to the default values when switching the backup mode +#if [ ! -z $OLDMODE ]; then +# if [ $OLDMODE != $RET ]; then +# db_set boxbackup-client/UpdateStoreInterval "3600" +# db_set boxbackup-client/MinimumFileAge "21600" +# db_set boxbackup-client/MaxUploadWait "86400" +# fi +#fi + +db_get boxbackup-client/backupMode +if [ "$RET" = "lazy" ]; then + for param in UpdateStoreInterval MinimumFileAge MaxUploadWait; do + NUMOK=0 + while [ $NUMOK = 0 ]; do + db_input medium boxbackup-client/$param || true + db_go + + db_get boxbackup-client/$param + + if [ -z `echo $RET | sed 's/[[:digit:]]//g'` ]; then + NUMOK=1 + fi + + if [ $NUMOK = 0 ]; then + db_input critical boxbackup-client/IncorrectNumber || true + db_go + fi + done + done +fi + + +# NotifyMail +db_input medium boxbackup-client/notifyMail || true +db_go + +# x509 and private key +db_input medium boxbackup-client/generateCertificate || true +db_go + +exit 0 diff --git a/boxbackup-client.cron.d b/boxbackup-client.cron.d new file mode 100644 index 00000000..486e4be3 --- /dev/null +++ b/boxbackup-client.cron.d @@ -0,0 +1,2 @@ +# Launch the boxbackup client/server synchronization when in snapshot mode +#0 0 * * * root /usr/sbin/bbackupctl -q sync diff --git a/boxbackup-client.dirs b/boxbackup-client.dirs new file mode 100644 index 00000000..96e786f8 --- /dev/null +++ b/boxbackup-client.dirs @@ -0,0 +1,5 @@ +usr/sbin +var/lib/bbackupd +etc/boxbackup +etc/boxbackup/bbackupd + diff --git a/boxbackup-client.docs b/boxbackup-client.docs new file mode 100644 index 00000000..ccd1de71 --- /dev/null +++ b/boxbackup-client.docs @@ -0,0 +1,3 @@ +BUGS.txt +distribution/boxbackup/LINUX.txt +distribution/boxbackup/THANKS.txt diff --git a/boxbackup-client.init b/boxbackup-client.init new file mode 100644 index 00000000..e6ceced7 --- /dev/null +++ b/boxbackup-client.init @@ -0,0 +1,69 @@ +#! /bin/sh +# +### BEGIN INIT INFO +# Provides: boxbackup-client +# Required-Start: $syslog $remote_fs $network +# Required-Stop: $syslog $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: boxbackup client +# Description: Init script to start and stop the boxbackup client +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/bbackupd +NAME=bbackupd +DESC=boxbackup-client +CONF=/etc/boxbackup/bbackupd.conf + +test -f $DAEMON || exit 0 + +test -f $CONF || exit 0 + +PIDFILE=`grep 'PidFile' $CONF | sed 's/[[:space:]]*PidFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +CERTFILE=`grep 'CertificateFile' $CONF | sed 's/[[:space:]]*CertificateFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +ACCNUM=`grep 'AccountNumber' $CONF | sed 's/[[:space:]]*AccountNumber[[:space:]]*=[[:space:]]*\([A-Za-z0-9/]*\)/\1/'` + +[ -z $PIDFILE ] && PIDFILE="/var/run/bbackupd.pid" + +# Don't start if certificate file or account number are not present +[ ! -e $CERTFILE -o -z $ACCNUM ] && exit 0 + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $CONF + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --oknodo --retry 5 --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + echo "$NAME." + ;; + reload|force-reload) + echo "Reloading $DESC configuration files." + start-stop-daemon --stop --signal 1 --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + ;; + restart) + echo -n "Restarting $DESC: " + start-stop-daemon --oknodo --retry 5 --stop --quiet --pidfile \ + $PIDFILE --exec $DAEMON + sleep 1 + start-stop-daemon --start --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + #echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/boxbackup-client.install b/boxbackup-client.install new file mode 100644 index 00000000..5dbf00bf --- /dev/null +++ b/boxbackup-client.install @@ -0,0 +1,4 @@ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupctl /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupd /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupd-config /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupquery /usr/sbin/ diff --git a/boxbackup-client.manpages b/boxbackup-client.manpages new file mode 100644 index 00000000..562b2f7e --- /dev/null +++ b/boxbackup-client.manpages @@ -0,0 +1,6 @@ +docs/man/bbackupd.8.gz +docs/man/bbackupd.conf.5.gz +docs/man/bbackupd-config.8.gz +docs/man/bbackupctl.8.gz +docs/man/bbackupquery.8.gz + diff --git a/boxbackup-client.postinst b/boxbackup-client.postinst new file mode 100644 index 00000000..aa58f3b8 --- /dev/null +++ b/boxbackup-client.postinst @@ -0,0 +1,349 @@ +#! /bin/bash +# postinst script for boxbackup-client +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package +# +# quoting from the policy: +# Any necessary prompting should almost always be confined to the +# post-installation script, and should be protected with a conditional +# so that unnecessary prompting doesn't happen if a package's +# installation fails and the `postinst' is called with `abort-upgrade', +# `abort-remove' or `abort-deconfigure'. + +#loading debconf module +. /usr/share/debconf/confmodule + +CONFDIR=/etc/boxbackup +DEBCONFBB=$CONFDIR/bbackupd.debconf +BBCONF=$CONFDIR/bbackupd.conf +BBKEY=$CONFDIR/bbackupd/boxbackup-client-encrypt-key.raw +BBPRIVKEY=$CONFDIR/bbackupd/boxbackup-client-priv-key.pem +BBCERTREQ=$CONFDIR/bbackupd/boxbackup-client-cert-req.pem +BBCERT=$CONFDIR/bbackupd/boxbackup-client-cert.pem +BBCACERT=$CONFDIR/bbackupd/boxbackup-server-ca-cert.pem +DEBCONFNOTIFY=$CONFDIR/bbackupd/notifyadmin.debconf +NOTIFYSCRIPT=$CONFDIR/bbackupd/notifyadmin + +case "$1" in + configure) + db_get boxbackup-client/debconf + if [ "$RET" = "true" ]; then + # Generate configuration files + # backupd.conf + echo "#To reconfigure boxbackup-client run #dpkg-reconfigure boxbackup-client" >> $DEBCONFBB + + db_get boxbackup-client/backupServer + echo "StoreHostname = $RET" >> $DEBCONFBB + + db_get boxbackup-client/accountNumber + ACCOUNT=$RET + echo "AccountNumber = 0x$ACCOUNT" >> $DEBCONFBB + echo "KeysFile = $BBKEY" >> $DEBCONFBB + echo "" >> $DEBCONFBB + echo "CertificateFile = $BBCERT" >> $DEBCONFBB + echo "PrivateKeyFile = $BBPRIVKEY" >> $DEBCONFBB + echo "TrustedCAsFile = $BBCACERT" >> $DEBCONFBB + echo "" >> $DEBCONFBB + echo "DataDirectory = /var/lib/bbackupd" >> $DEBCONFBB + + cat >>$DEBCONFBB <<__EOF + +# This script is run whenever bbackupd 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. +# The default script emails the system administrator. +NotifyScript = $NOTIFYSCRIPT + +__EOF + + db_get boxbackup-client/backupMode + if [ "$RET" = "lazy" ]; then + db_get boxbackup-client/UpdateStoreInterval + UPDATE=$RET + [ -z "$UPDATE" ] && UPDATE="3600" + + db_get boxbackup-client/MinimumFileAge + FILEAGE=$RET + [ -z "$FILEAGE" ] && FILEAGE="21600" + + db_get boxbackup-client/MaxUploadWait + UPWAIT=$RET + [ -z "$UPWAIT" ] && UPWAIT="86400" + + AUTO=yes + else + AUTO=no + UPDATE=0 + FILEAGE=0 + UPWAIT=0 + fi + + cat >>$DEBCONFBB <<__EOF +# Backup mode specification +# With snapshot mode, you will need to run bbackupctl to instruct the daemon to upload files. +# Set to no for snapshot mode and yes for lazy mode +AutomaticBackup = $AUTO + +# A scan of the local discs will be made once an hour (approximately). +# To avoid cycles of load on the server, this time is randomly adjusted by a small +# percentage as the daemon runs. +# Defaults: 3600 for lazy mode - 0 for snapshot mode +UpdateStoreInterval = $UPDATE + +# A file must have been modified at least 6 hours ago before it will be uploaded. +# Defaults: 21600 for lazy mode - 0 for snapshot mode +MinimumFileAge = $FILEAGE + +# If a file is modified repeated, it won't be uploaded immediately in case it's modified again. +# However, it should be uploaded eventually. This is how long we should wait after first noticing +# a change. (1 day) +# Defaults: 86400 for lazy mode - 0 for snapshot mode +MaxUploadWait = $UPWAIT + +# 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. 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 want +# to spend more time searching for unchanged blocks. +MaximumDiffingTime = 20 + +# Uncomment this line to see exactly what the daemon is going when it's connected to the server. +# ExtendedLogging = yes + +# Use this to temporarily stop bbackupd from syncronising or connecting to the store. +# 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 prints 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 the script will +# be asked again in that number of seconds. +# For example, you could use this on a laptop to only backup when on a specific network. + +# SyncAllowScript = /path/to/intepreter/or/exe script-name parameters etc + +# Where the command socket is created in the filesystem. +CommandSocket = /var/run/bbackupd.sock + +Server +{ + PidFile = /var/run/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. +# +# 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. +# + +BackupLocations +{ +__EOF + + db_get boxbackup-client/backupDirs + + for dir in $RET; do + NAME=`echo $dir | sed 's/\//-/g' | sed 's/^-//'` + + # TODO : exclude encrypt key file from the backup + + echo " $NAME" >> $DEBCONFBB + echo " {" >> $DEBCONFBB + echo " Path = $dir" >> $DEBCONFBB + echo " }" >> $DEBCONFBB + done + + echo "}" >> $DEBCONFBB + + # Encryption key + if [ ! -e $BBKEY ]; then + if ! openssl rand -out $BBKEY 1024 >&2; then + echo "Can't generate encryption key. Check why." >&2 + fi + fi + + chmod 600 $BBKEY || true + + # SSL stuff + if [ ! -z "$ACCOUNT" ]; then + if [ ! -e $BBPRIVKEY -a ! -e $BBCERT ]; then + db_get boxbackup-client/generateCertificate + + if [ "$RET" = "true" ]; then + if ! openssl genrsa -out $BBPRIVKEY 2048 >&2; then + echo "Private key generation failed! Check why." >&2 + else + chmod 600 $BBPRIVKEY || true + fi + + + if ! openssl req -new -key $BBPRIVKEY -sha1 -out $BBCERTREQ >&2 <<__EOF +. +. +. +. +. +BACKUP-$ACCOUNT +. +. +. +__EOF + then + echo "Certificate request generation failed ! Check why." >&2 + fi + fi + fi + fi + + # Generate notify script + CLIENTNAME=`hostname --fqdn` + + db_get boxbackup-client/notifyMail + MAILTO=$RET + + cat >>$DEBCONFNOTIFY <<__EOF +#!/bin/sh +#To reconfigure boxbackup-client run #dpkg-reconfigure boxbackup-client +SUBJECT="BACKUP PROBLEM on host $CLIENTNAME" +SENDTO="$MAILTO" + +if [ \$1 = store-full ] +then +sendmail \$SENDTO <&2 + exit 1 + ;; +esac + +db_stop + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/boxbackup-client.postrm b/boxbackup-client.postrm new file mode 100644 index 00000000..5272f93e --- /dev/null +++ b/boxbackup-client.postrm @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +if [ "$1" = "purge" ]; then + for i in /etc/boxbackup/bbackupd.conf /etc/boxbackup/bbackupd/notifyadmin; do + if [ -e $i ]; then + rm -f $i + fi + if which ucf >/dev/null; then + ucf -p $i + fi + done +fi + +#DEBHELPER# diff --git a/boxbackup-client.templates b/boxbackup-client.templates new file mode 100644 index 00000000..cf0d41c8 --- /dev/null +++ b/boxbackup-client.templates @@ -0,0 +1,137 @@ +# These templates have been reviewed by the debian-l10n-english +# team +# +# If modifications/additions/rewording are needed, please ask +# for an advice to debian-l10n-english@lists.debian.org +# +# Even minor modifications require translation updates and such +# changes should be coordinated with translators and reviewers. + +Template: boxbackup-client/debconf +Type: boolean +Default: false +_Description: Should the BoxBackup client be configured automatically? + The package configuration scripts can create the configuration files + for the BoxBackup client. + . + You should choose this option if you are not familiar with BoxBackup's + configuration options. + . + Please read the /usr/share/doc/boxbackup-client/README.Debian for details + about the configuration of the BoxBackup client. + +Template: boxbackup-client/backupMode +Type: select +Choices: lazy, snapshot +#flag:comment:3 +# Translators, please keep reference to 'lazy' and 'snapshot' as +# these options are written as is in the software documentation +_Description: Run mode for the BoxBackup client: + The BoxBackup client supports two modes of backup: + . + In the 'lazy' mode, the backup daemon will regularly scan the file system + searching for modified files. It will then upload the files older than a + specified age to the backup server. + . + In the 'snapshot' mode the backup will be explicitly run at regular intervals. + A cron file (/etc/cron.d/boxbackup-client) is provided with the + package and should be adapted to suit your needs. + +Template: boxbackup-client/accountNumber +Type: string +_Description: Account number for this node on the backup server: + The administrator of the BoxBackup server should have assigned this client + a hexadecimal account number. + . + If no account number has been assigned yet, leave this field blank and + configure it later by running 'dpkg-reconfigure boxbackup-client' as root. + +Template: boxbackup-client/incorrectAccountNumber +Type: error +_Description: Invalid account number + The account number must be a hexadecimal number (such as 1F04 or 4500). + +Template: boxbackup-client/backupServer +Type: string +_Description: Fully qualified domain name of the backup server: + Please enter the fully qualified domain name of the BoxBackup server which + your client will use. + . + The client will connect to the server on TCP port 2201. + +Template: boxbackup-client/backupDirs +Type: string +_Description: List of directories to backup: + Please give a space-separated list of directories to be backed up onto + the remote server. + . + Those directories should not contain mounted file systems at any level + in their subdirectories. + +Template: boxbackup-client/incorrectDirectories +Type: error +_Description: Invalid path name + The path names to the directories must be absolute path names, separated + by spaces. + . + For example: /home/myaccount /etc/ + +Template: boxbackup-client/UpdateStoreInterval +Type: string +Default: 3600 +_Description: Interval (in seconds) between directory scans: + BoxBackup regularly scans the selected directories, looking for modified + files. + . + Please choose the scan interval in seconds. + +Template: boxbackup-client/MinimumFileAge +Type: string +Default: 21600 +_Description: Minimum time to wait (in seconds) before uploading a file: + A file will be uploaded to the server only after a certain time after its + last modification. + . + Low interval values will trigger frequent uploads to the server and more + revisions being created with older revisions being removed earlier. + +Template: boxbackup-client/MaxUploadWait +Type: string +Default: 86400 +_Description: Maximum time to wait (in seconds) before uploading a file: + Frequently modified files are likely to never get uploaded if they + never reach the minimum wait time. + . + Please enter the maximum time to reach before the upload of a modified + file to the server is enforced. + +Template: boxbackup-client/IncorrectNumber +Type: error +_Description: Invalid time entered + Please enter an integer value greater null. + +Template: boxbackup-client/notifyMail +Type: string +Default: root +_Description: Recipient for alert notifications: + The BoxBackup client sends alert notifications when a problem occurs + during the backup. + . + Please enter either a local user name (for example 'root') or an email + address (for example 'admin@example.org'). + +Template: boxbackup-client/generateCertificate +Type: boolean +Default: true +_Description: Generate the client private key and X.509 certificate request? + The BoxBackup client needs an RSA private key and the corresponding X.509 + certificate to authenticate itself with the server. + . + Both can be generated automatically. You will need to send the + certificate request to the BoxBackup server administrator who will + sign it and send it back to you along with the server's Certification + Authority certificate. + . + These files should be copied into BoxBackup's configuration + directory. The file names to use are given in the + /etc/boxbackup/bbackupd.conf file. diff --git a/boxbackup-server.config b/boxbackup-server.config new file mode 100644 index 00000000..8f5d321c --- /dev/null +++ b/boxbackup-server.config @@ -0,0 +1,110 @@ +#!/bin/bash -e + +# Source debconf library +. /usr/share/debconf/confmodule + +# This conf script is capable of backing up +#db_version 2.0 +#db_capb backup + +#db_metaget debconf/priority value +#CONFPRIO=$RET + +# Handle with debconf or not? +db_input medium boxbackup-server/debconf || true +db_go +db_get boxbackup-server/debconf +if [ "$RET" = "false" ]; then + exit 0 +fi + +# RAID directories +db_get boxbackup-server/raidDirectories +OLDRAIDDIR=$RET +RAIDOK=0 +while [ $RAIDOK = 0 ]; do + db_input critical boxbackup-server/raidDirectories || true + db_go + + db_get boxbackup-server/raidDirectories + + DIR1=`echo "$RET" | awk '{ print $1 }'` + DIR2=`echo "$RET" | awk '{ print $2 }'` + DIR3=`echo "$RET" | awk '{ print $3 }'` + + if [ -n $DIR1 ]; then + if [ -z "$DIR2" -o -z "$DIR3" ]; then + DIR2=$DIR1 + DIR3=$DIR1 + fi + + PATHOK=1 + for i in $DIR1 $DIR2 $DIR3; do + if [ `echo $i | awk '{ if (/^\/[A-Za-z0-9\.\-_]+\/?([A-Za-z0-9\.\-_]+\/?)*$/) { print 1 } else { print 0 } }'` = 0 ]; then + PATHOK=0 + fi + done + + if [ $PATHOK = 1 ]; then + RAIDOK=1; + fi + fi + + if [ $RAIDOK = 0 ]; then + db_input critical boxbackup-server/incorrectDirectories || true + db_go + fi +done + +# RAID block size +# Try to figure out the block size of the first partition given +db_get boxbackup-server/raidDirectories +if [ "$OLDRAIDDIR" != "$RET" ]; then # Directories have been changed so we can try to guess the block size + TMPDIR=`echo $DIR1 | sed 's/\/$//'` + + while [ "$TMPDIR" != "" ]; do + DEV=`df -P | grep "$TMPDIR$" | awk '{ print $1 }'` + + if [ -z "$DEV" ]; then + TMPDIR=`echo $TMPDIR | sed 's/\/[^\/]*$//'` + else + TMPDIR="" + fi + done + + if [ "$DEV" != "" ]; then + if [ -x /sbin/tune2fs ]; then + BS=`tune2fs -l $DEV 2>/dev/null | grep 'Block size' | awk '{print $3 }'` + + if [ $? = 0 -a $BS != "" ]; then + db_set boxbackup-server/raidBlockSize "$BS" + fi + fi + fi +fi + +BSOK=0 +while [ $BSOK = 0 ]; do + db_input critical boxbackup-server/raidBlockSize || true + db_go + + db_get boxbackup-server/raidBlockSize + + if [ `echo $RET | awk '{ if (/^[0-9]+$/) { print 1 } else { print 0 } }'` = 1 ]; then + if [ `echo $RET | awk '{ bs=sqrt($1); if (bs ~ /^[0-9]+$/) { print 1 } else { print 0 } }'` = 1 ]; then + BSOK=1 + fi + fi + + if [ $BSOK = 0 ]; then + db_input critical boxbackup-server/incorrectBlocksize || true + db_go + fi +done + +# x509 and private key +db_input medium boxbackup-server/generateCertificate || true +db_go + +exit 0 + diff --git a/boxbackup-server.dirs b/boxbackup-server.dirs new file mode 100644 index 00000000..a6c52ea6 --- /dev/null +++ b/boxbackup-server.dirs @@ -0,0 +1,6 @@ +usr/sbin +etc/boxbackup +etc/boxbackup/bbstored +etc/logcheck/ignore.d.workstation +etc/logcheck/ignore.d.server + diff --git a/boxbackup-server.docs b/boxbackup-server.docs new file mode 100644 index 00000000..ccd1de71 --- /dev/null +++ b/boxbackup-server.docs @@ -0,0 +1,3 @@ +BUGS.txt +distribution/boxbackup/LINUX.txt +distribution/boxbackup/THANKS.txt diff --git a/boxbackup-server.init b/boxbackup-server.init new file mode 100644 index 00000000..11529b4d --- /dev/null +++ b/boxbackup-server.init @@ -0,0 +1,80 @@ +#! /bin/sh +# +### BEGIN INIT INFO +# Provides: boxbackup-server +# Required-Start: $syslog $remote_fs $network +# Required-Stop: $syslog $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: boxbackup server +# Description: Init script to start and stop the boxbackup server +### END INIT INFO + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/bbstored +NAME=bbstored +DESC=boxbackup-server +CONF=/etc/boxbackup/bbstored.conf + +test -f $DAEMON || exit 0 + +test -f $CONF || exit 0 + +PIDFILE=`grep 'PidFile' $CONF | sed 's/[[:space:]]*PidFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +CERTFILE=`grep 'CertificateFile' $CONF | sed 's/[[:space:]]*CertificateFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` + +[ -z $PIDFILE ] && PIDFILE="/var/run/bbstored.pid" + +# Don't start if certificate file is not present +[ ! -e $CERTFILE ] && exit 0 + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $CONF + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --oknodo --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + echo "$NAME." + ;; + #reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + # echo "Reloading $DESC configuration files." + # start-stop-daemon --stop --signal 1 --quiet --pidfile \ + # /var/run/$NAME.pid --exec $DAEMON + #;; + restart|force-reload) + # + # If the "reload" option is implemented, move the "force-reload" + # option to the "reload" entry above. If not, "force-reload" is + # just the same as "restart". + # + echo -n "Restarting $DESC: " + start-stop-daemon --oknodo --stop --quiet --pidfile \ + $PIDFILE --exec $DAEMON + sleep 1 + start-stop-daemon --start --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/boxbackup-server.install b/boxbackup-server.install new file mode 100644 index 00000000..24809064 --- /dev/null +++ b/boxbackup-server.install @@ -0,0 +1,8 @@ + +debian/tmp/etc/logcheck/ignore.d.server/boxbackup-server /etc/logcheck/ignore.d.server +debian/tmp/etc/logcheck/ignore.d.workstation/boxbackup-server /etc/logcheck/ignore.d.workstation +parcels/boxbackup-*-backup-server-*-gnu*/bbstoreaccounts /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored-certs /usr/bin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored-config /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/raidfile-config /usr/sbin/ diff --git a/boxbackup-server.logcheck.ignore b/boxbackup-server.logcheck.ignore new file mode 100644 index 00000000..c19b43db --- /dev/null +++ b/boxbackup-server.logcheck.ignore @@ -0,0 +1,10 @@ +bbstored/hk\[.*\]: Starting housekeeping +bbstored/hk\[.*\]: Finished housekeeping +bbstored/hk\[.*\]: Housekeeping process started +bbstored\[.*\]: Starting daemon +bbstored\[.*\]: Terminating daemon +bbstored\[.*\]: Incoming connection from.*port.*\(handling in child.*\) +bbstored\[.*\]: Certificate CN: +bbstored\[.*\]: Login: Client ID +bbstored\[.*\]: Session finished + diff --git a/boxbackup-server.manpages b/boxbackup-server.manpages new file mode 100644 index 00000000..09a62b31 --- /dev/null +++ b/boxbackup-server.manpages @@ -0,0 +1,7 @@ +docs/man/bbstoreaccounts.8.gz +docs/man/bbstored-certs.8.gz +docs/man/bbstored.conf.5.gz +docs/man/bbstored-config.8.gz +docs/man/bbstored.8.gz +docs/man/raidfile-config.8.gz +docs/man/raidfile.conf.5.gz diff --git a/boxbackup-server.postinst b/boxbackup-server.postinst new file mode 100644 index 00000000..1b19de97 --- /dev/null +++ b/boxbackup-server.postinst @@ -0,0 +1,212 @@ +#! /bin/sh +# postinst script for boxbackup-server +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package +# +# quoting from the policy: +# Any necessary prompting should almost always be confined to the +# post-installation script, and should be protected with a conditional +# so that unnecessary prompting doesn't happen if a package's +# installation fails and the `postinst' is called with `abort-upgrade', +# `abort-remove' or `abort-deconfigure'. + +#loading debconf module +. /usr/share/debconf/confmodule + +CONFDIR=/etc/boxbackup +DEBCONFRAID=$CONFDIR/raidfile.debconf +DEBCONFBB=$CONFDIR/bbstored.debconf +RAIDCONF=$CONFDIR/raidfile.conf +BBCONF=$CONFDIR/bbstored.conf +BBACCOUNTS=$CONFDIR/bbstored/boxbackup-server-accounts.txt +BBUSER=bbstored +BBPRIVKEY=$CONFDIR/bbstored/boxbackup-server-key.pem +BBCERTREQ=$CONFDIR/bbstored/boxbackup-server-cert-req.pem +BBCERT=$CONFDIR/bbstored/boxbackup-server-cert.pem +BBCACERT=$CONFDIR/bbstored/boxbackup-client-ca-cert.pem + +case "$1" in + configure) + + # Set up the bbstored user + if [ -z "`getent passwd $BBUSER`" ]; then + echo "Creating $BBUSER user." >&2 + adduser --system --no-create-home \ + --disabled-password --disabled-login \ + --shell /bin/false --group --home /var $BBUSER + else + echo "User $BBUSER already exists." >&2 + fi + + db_get boxbackup-server/debconf + if [ "$RET" = "true" ]; then + # Generate configuration files + # raidfile.conf + echo "#To reconfigure boxbackup-server run #dpkg-reconfigure boxbackup-server" >> $DEBCONFRAID + + echo "disc0" >> $DEBCONFRAID + echo "{" >> $DEBCONFRAID + echo " SetNumber = 0" >> $DEBCONFRAID + + db_get boxbackup-server/raidBlockSize + echo " BlockSize = $RET" >> $DEBCONFRAID + + db_get boxbackup-server/raidDirectories + + DIR1=`echo "$RET" | awk '{ print $1 }'` + DIR2=`echo "$RET" | awk '{ print $2 }'` + DIR3=`echo "$RET" | awk '{ print $3 }'` + + if [ -n $DIR1 ]; then + if [ -z "$DIR2" -o -z "$DIR3" ]; then + DIR2=$DIR1 + DIR3=$DIR1 + fi + fi + + echo " Dir0 = $DIR1" >> $DEBCONFRAID + echo " Dir1 = $DIR2" >> $DEBCONFRAID + echo " Dir2 = $DIR3" >> $DEBCONFRAID + + echo "}" >> $DEBCONFRAID + + # Handle backup directories creation/permissions + for dir in "$DIR1" "$DIR2" "$DIR3"; do + if [ -d "$dir/backup" ]; then + # need stat package on Woody + #if (`stat -c %U $dir/backup` != $BBUSER); then + if [ `ls -ld $dir/backup | awk '{ print $3 }'` != "$BBUSER" ]; then + echo "Incorrect owner of backup directory. Changing it to $BBUSER..." >&2 + chown $BBUSER:$BBUSER $dir/backup + fi + + #if [ `stat -c %a $dir/backup` != "700" ]; then + if [ `ls -ld $dir/backup | awk '{ print $1 }'` != "drwx------" ]; then + chmod 700 $dir/backup + fi + else + echo "Creating $dir/backup directory..." >&2 + mkdir -p $dir/backup + chown $BBUSER:$BBUSER $dir/backup + chmod 700 $dir/backup + fi + done + + if ! dpkg-statoverride --list $CONFDIR/bbstored > /dev/null; then + dpkg-statoverride --update --add $BBUSER $BBUSER 700 $CONFDIR/bbstored + fi + + # Accounts file + if [ ! -e $BBACCOUNTS ]; then + touch $BBACCOUNTS + fi + + #if [ `stat -c %U $BBACCOUNTS` != $BBUSER ]; then + if [ `ls -ld $BBACCOUNTS | awk '{ print $3 }'` != "$BBUSER" ]; then + chown $BBUSER:$BBUSER $BBACCOUNTS + fi + + #if [ `stat -c %a $BBACCOUNTS` != "600" ]; then + if [ `ls -ld $BBACCOUNTS | awk '{ print $1 }'` != "drw-------" ]; then + chmod 600 $BBACCOUNTS + fi + + SERVNAME=`hostname --fqdn` + + # SSL stuff + if [ ! -e $BBPRIVKEY -a ! -e $BBCERT ]; then + db_get boxbackup-server/generateCertificate + + if [ "$RET" = "true" ]; then + if ! openssl genrsa -out $BBPRIVKEY 2048 >&2; then + echo "Private key generation failed! Check why." >&2 + else + chown $BBUSER: $BBPRIVKEY + chmod 600 $BBPRIVKEY || true + fi + + if ! openssl req -new -key $BBPRIVKEY -sha1 -out $BBCERTREQ >&2 <&2 + fi + fi + fi + + # Generate bbstored.conf + echo "#To reconfigure boxbackup-server run #dpkg-reconfigure boxbackup-server" >> $DEBCONFBB + echo "RaidFileConf = $RAIDCONF" >> $DEBCONFBB + echo "AccountDatabase = $BBACCOUNTS" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "# Uncomment this line to see exactly what commands are being received from clients." >> $DEBCONFBB + echo "# ExtendedLogging = yes" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "# scan all accounts for files which need deleting every 15 minutes." >> $DEBCONFBB + echo "TimeBetweenHousekeeping = 900" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "Server" >> $DEBCONFBB + echo "{" >> $DEBCONFBB + echo " PidFile = /var/run/bbstored.pid" >> $DEBCONFBB + echo " User = bbstored" >> $DEBCONFBB + echo " ListenAddresses = inet:$SERVNAME" >> $DEBCONFBB + echo " CertificateFile = $BBCERT" >> $DEBCONFBB + echo " PrivateKeyFile = $BBPRIVKEY" >> $DEBCONFBB + echo " TrustedCAsFile = $BBCACERT" >> $DEBCONFBB + echo "}" >> $DEBCONFBB + + if [ -x "`which ucf`" ]; then + ucf --three-way --debconf-ok $DEBCONFRAID $RAIDCONF + fi + rm -f $DEBCONFRAID + chmod 644 $RAIDCONF || true + chown root:root $RAIDCONF || true + + if [ -x "`which ucf`" ]; then + ucf --three-way --debconf-ok $DEBCONFBB $BBCONF + fi + rm -f $DEBCONFBB + chmod 644 $BBCONF || true + chown root:root $BBCONF || true + fi + db_stop + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + db_stop + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + db_stop + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/boxbackup-server.postrm b/boxbackup-server.postrm new file mode 100644 index 00000000..7e6ac797 --- /dev/null +++ b/boxbackup-server.postrm @@ -0,0 +1,22 @@ +#! /bin/sh +set -e + +if [ "$1" = "purge" ]; then + for i in /etc/boxbackup/raidfile.conf /etc/boxbackup/bbstored.conf; do + if [ -e $i ]; then + rm -f $i + fi + if which ucf >/dev/null; then + ucf -p $i + fi + done + + dpkg-statoverride --remove /etc/boxbackup/bbstored || true + + if which deluser >/dev/null; then + getent passwd bbstored >/dev/null && deluser bbstored + getent group bbstored >/dev/null && delgroup bbstored + fi +fi + +#DEBHELPER# diff --git a/boxbackup-server.templates b/boxbackup-server.templates new file mode 100644 index 00000000..3c6016e2 --- /dev/null +++ b/boxbackup-server.templates @@ -0,0 +1,77 @@ +# These templates have been reviewed by the debian-l10n-english +# team +# +# If modifications/additions/rewording are needed, please ask +# for an advice to debian-l10n-english@lists.debian.org +# +# Even minor modifications require translation updates and such +# changes should be coordinated with translators and reviewers. + +Template: boxbackup-server/debconf +Type: boolean +Default: false +_Description: Should BoxBackup be configured automatically? + The package configuration scripts can create the configuration files + for the BoxBackup server. + . + You should choose this option if you are not familiar with BoxBackup's + configuration options. The configuration can be done manually with the + 'raidfile-config' and 'bbstored-config' scripts. + . + The server will not start if it is not configured. In all cases, + reading the /usr/share/doc/boxbackup-server/README.Debian is + recommended. + +Template: boxbackup-server/raidDirectories +Type: string +_Description: Location of the RAID directories: + Please choose the location for the three RAID file directories. + . + To enable RAID, the directory names should be a space-separated list of + three partitions, each on different physical hard drives (for example: + '/raid/0.0 /raid/0.1 /raid/0.2'). + . + If you don't want to enable RAID, just specify the path to one directory + where the backups will be stored (for example, /usr/local/lib/boxbackup). + . + These directories will be created if they do not exist. + +Template: boxbackup-server/incorrectDirectories +Type: error +_Description: Invalid path names + The path names to the directories must be absolute path names, + separated by spaces. + . + For example: /raid/0.0 /raid/0.1 /raid/0.2 + +Template: boxbackup-server/raidBlockSize +Type: string +Default: 4096 +_Description: Block size for the userland RAID system: + BoxBackup uses userland RAID techniques. + . + Please choose the block size to use for the storage. + For maximum efficiency, you should choose the block size of the underlying + file system (which can be displayed for ext2 filesystems with the 'tune2fs -l' + command). + . + This value should be set even if you don't plan to use RAID. + +Template: boxbackup-server/generateCertificate +Type: boolean +Default: true +_Description: Generate a server private key and X.509 certificate request? + The BoxBackup server needs an RSA private key and the corresponding X.509 + certificate to perform client-server authentication and communication + encryption. + . + Both can be generated automatically. You will need to sign the + certificate with your root CA (see the boxbackup-server package) and + put this signed certificate and the root CA certificate in the + configuration folder. + +Template: boxbackup-server/incorrectBlocksize +Type: error +_Description: Invalid block size + The block size must be a power of two (e.g. 1024 or 4096). + diff --git a/changelog b/changelog new file mode 100644 index 00000000..0037604a --- /dev/null +++ b/changelog @@ -0,0 +1,332 @@ +boxbackup (0.11.1~r2837-4) unstable; urgency=medium + + * QA upload. + * Check for ucf and deluser availability before calling them during postrm + purge. (Closes: #688373, #667535) + * Add Italian (it) debconf translations by Beatrice Torracca. + (Closes: #658724) + * Add Dutch (nl) debconf translations by Frans Spiesschaert. + (Closes: #766532) + * Build with dh_autotools-dev_*. + * Switch to source format 3.0 (quilt). + + -- Andreas Beckmann Sun, 22 Jan 2017 03:29:52 +0100 + +boxbackup (0.11.1~r2837-3) unstable; urgency=medium + + * QA upload. + * Build with openssl 1.0. Thanks to Adrian Bunk. (Closes: #828253) + * Bump Standards-Version to 3.9.8. + + -- Chris Lamb Sat, 12 Nov 2016 14:15:06 +0000 + +boxbackup (0.11.1~r2837-2) unstable; urgency=medium + + * Ensure that LWP/UserAgent.pm is available (Closes: #816517) + * Orphaning package, cf. #817078 + + -- Reinhard Tartler Mon, 07 Mar 2016 20:19:45 -0500 + +boxbackup (0.11.1~r2837-1) unstable; urgency=low + + * New upstream release. + - Bug fix: "Handling of "get" command broken due to bug in + temp file name creation.", thanks to Matto Marjanovic + (Closes: #606559). + * Bug fix: "[INTL:pt_BR] Brazilian Portuguese debconf templates + translation", thanks to Adriano Rafael Gomes (Closes: #610416). + * Bug fix: "[INTL:da] Fix Danish translation of the debconf templates + BoxBackup", thanks to Joe Dalton (Closes: #619303). + * Bug fix: "Documentation errors regarding bbstored-certs", thanks to + Clint Adams (Closes: #601882). + * Build in verbose mode. + * Bump standards version to 3.9.2, no changes needed. + * Normalize fields in debian/control with wrap-and-sort(1). + * implement build-arch and build-indep targets + + -- Reinhard Tartler Fri, 28 Oct 2011 01:53:48 +0200 + +boxbackup (0.11~rc8~r2714-1) unstable; urgency=low + + * New upstream release. + * New Upstream fixes Bug: "boxbackup-client complains about + /home/*/.gvfs", thanks for reporting to Sune Mølgaard. + Closes: #593401, LP: #496334 + * Enhancement: "Please use newer bdb", thanks to Clint Adams + Closes: #548475 + * bin/bbstored/bbstored-certs: reduce root CA expiration date to avoid + Y2k38 overflow. Thanks to Clint Adams for + reporting it. Closes: #601506, LP: #641112 + * By default, do not manage boxbackup-client configuration via debconf + * Allow dpkg-reconfigure to work even when the debconf level is set to + 'high'. + * Various debconf severity fixes in boxbackup-*.conf + + -- Reinhard Tartler Tue, 09 Nov 2010 18:14:58 +0100 + +boxbackup (0.11~rc3~r2502-4) unstable; urgency=low + + * use more globs in install paths to avoid failures on kFreeBSD + * Bump standards version, no changes needed + + -- Reinhard Tartler Wed, 13 Oct 2010 20:51:39 +0200 + +boxbackup (0.11~rc3~r2502-3) unstable; urgency=low + + * Update Vitnamese debconf translations. Closes: #598581 + + -- Reinhard Tartler Tue, 12 Oct 2010 10:50:37 +0200 + +boxbackup (0.11~rc3~r2502-2.1ubuntu1) maverick; urgency=low + + * fix install paths for armel (LP: #616461) + + -- David Sugar Mon, 16 Aug 2010 23:49:22 +0200 + +boxbackup (0.11~rc3~r2502-2.1) unstable; urgency=low + + * Non-maintainer upload. + * No longer hardcode path to tune2fs in config script + * Fix pending l10n issues. Debconf translations: + - Spanish (Omar Campagne). Closes: #537589 + - Japanese (Hideki Yamane (Debian-JP)). Closes: #558074 + - Danish (Joe Hansen). Closes: #582174 + + -- Christian Perrier Mon, 24 May 2010 09:33:23 +0200 + +boxbackup (0.11~rc3~r2502-2) unstable; urgency=low + + * Bug fix: "tries to contact a server build-time", thanks to + Riku Voipio for reporting. Fix based on a patch contributed + by peter green, thanks as well! (Closes: #525277). + * Bug fix: "missing #include", thanks to Martin Michlmayr + (Closes: #526152, LP: #371809) + + -- Reinhard Tartler Tue, 05 May 2009 07:42:40 +0200 + +boxbackup (0.11~rc3~r2502-1) unstable; urgency=low + + * New upstream release, built from svn revision 2502. + - silently ignores sockets and named pipes (Closes: #479145) + - syslog facility is now configurable (Closes: #521283) + + -- Reinhard Tartler Thu, 02 Apr 2009 13:58:17 +0200 + +boxbackup (0.11~rc2+r2072-1) unstable; urgency=low + + * New upstream release. (Closes: #503942) + * no longer use the upstream tarballs, but create them with the script + debian/get-orig-source.sh to create the orig.tar.gz. See README.Debian + for a rationale of this step. + * Therefore, run autoconf on the buildds. + * always use config.sub/config.guess from the autotools-dev package + * Fake a "VERSION.txt" instead of letting the build system try to figure + out the svn version by itself (which would fail) + * no longer include patches to the upstream source inline. This doesn't + work out too well with bzr. Port all patches to quilt instead. + * factor out distribution independent cleaning rules to a new shell script + called debian/clean.sh. + * make debian/NEWS actually parsable by dpkg-parsechangelog. This should + make apt-parsechangelog work. + * Update debian specific documentation to new URL: http://boxbackup.org + * Don't install the files DOCUMENTATION.txt and CONTACT.txt. Both contain + just (outdated) contact addresses and URLs. (Closes: #503659) + * add translation for swedish debconf messages. Translation provided by + Martin Bagge . (Closes: #513776) + * adjust configure.ac to only AC_SUBST single variables instead of 3 in + a row. Fixes FTBFS on sid, found at http://bugs.gentoo.org/205558 + + -- Reinhard Tartler Tue, 31 Mar 2009 15:58:23 +0200 + +boxbackup (0.11~rc2-6) unstable; urgency=low + + * Fix shell scripting in the debconf interaction code of the package's + postinst script. This should prevent problems like LP: #222999 + + -- Reinhard Tartler Wed, 11 Feb 2009 08:55:05 +0100 + +boxbackup (0.11~rc2-5) unstable; urgency=low + + * Bugfix: "Please build-depend on docbook-xml". Thanks to Luca Falavigna + for reporting. Closes: #507973 + * Add missing #includes in lib/common/Logging.cpp and + lib/common/DebugMemLeakFinder.cpp. Also use malloc, free, etc from the + namespace std:: instead of the global namespace in several other files + Closes: #505696, #512510 + + -- Reinhard Tartler Thu, 22 Jan 2009 08:26:24 +0100 + +boxbackup (0.11~rc2-4) unstable; urgency=low + + * add a note to documentation about adjusted syslog facility. + * build and install the administrator guide and the installation guide. + * By default do not configure boxbackup. Most users of this package need + to know what the postinst/configure scripts are doing anyway, and this + avoids piuparts to fail here. Closes: #502742, #502461 + + -- Reinhard Tartler Thu, 23 Oct 2008 20:25:10 +0200 + +boxbackup (0.11~rc2-3.1) unstable; urgency=low + + * Non-maintainer upload to fix pending l10n issues. + * Debconf translations: + - German. Closes: #477719 + - French. Closes: #478557 + - Portuguese. Closes: #483869 + - Czech. Closes: #483981 + - Galician. Closes: #483857 + - Finnish. Closes: #484353 + - Basque. Closes: #484563 + - Russian. Closes: #484829 + + -- Christian Perrier Tue, 20 May 2008 07:54:18 +0200 + +boxbackup (0.11~rc2-3) unstable; urgency=low + + * fix another miss '#include ' to fix compilation with g++-4.3. + + -- Reinhard Tartler Wed, 23 Apr 2008 13:10:17 +0200 + +boxbackup (0.11~rc2-2) unstable; urgency=low + + * merge forgotten changes from the unstable Branch. (sorry to the + translators for the confusion). + * The previous upload forgot to change some references to the default + configuration directory from /etc/box -> /etc/boxbackup. Now rectified. + * add an '#include ' in ./lib/common/Logging.cpp. Should fix + build failiures on mips, powerpc and sparc. + + -- Reinhard Tartler Mon, 21 Apr 2008 13:41:18 +0200 + +boxbackup (0.11~rc2-1) unstable; urgency=low + + * new upstream release. + * update watch file. + * bump standards version to 3.7.3 (No changes needed) + * update frensh debconf template translations. Thanks to + Christian Perrier (Closes: #476090) + + -- Reinhard Tartler Sun, 20 Apr 2008 14:01:27 +0200 + +boxbackup (0.11~rc1-1) experimental; urgency=low + + * New upstream release. + - should fix builds on kFreeBSD architectures: (Closes: #440156). + - build against libbd4.6 instead of libdb4.3. (Closes: #442640). + - the config file template has been updated to be more specific for + the AlwaysIncludeFile Option (Closes: #435860). + * remove all generated files in clean target of debian/rules. Allows + package to build twice in a row (Closes: #442515). + * Install file ExceptionCodes.txt to documentation directories. The file + contains a list of Error codes found in logfiles. + * Simplify debian/rules by removing code to build arch-independent + packages. There are none. + + -- Reinhard Tartler Sun, 20 Jan 2008 19:09:59 +0100 + +boxbackup (0.10+really0.10-2) unstable; urgency=medium + + * raise urgency because of RC bug! + * use rather 'nocheck' instead of 'notest'. Thanks to Michael Banck + for notifying me about this. + * Ack NMUs. Thanks Amaya for handling these! (Closes: #470060, #467628). + * Merge Ubuntu changes, fixing installations without tty. (Closes: #474587). + * build against libdb4.6 (Closes: #442640) + * don't ignores errors on clean. Thanks lintian. + * update Standards Version to 3.7.3, no changes needed. + * clarify debconf question "boxbackup-client/IncorrectNumber" (Closes: #467635). + * clarify debconf question "boxbackup-server/raidBlockSize" (Closes: #467636). + + -- Reinhard Tartler Sun, 13 Apr 2008 11:38:01 +0200 + +boxbackup (0.10+really0.10-1.2) unstable; urgency=low + + * Non-maintainer upload. + * Fix FTBFS introduced by my previous NMU. Apply patch from Cyril Brulebois + (Closes: #454862). + * Added debian debconf translation by Kai Wasserbäch (Closes: #467628). + + -- Amaya Rodrigo Sastre Sat, 05 Apr 2008 12:37:16 +0200 + +boxbackup (0.10+really0.10-1.1) unstable; urgency=low + + * Non-maintainer upload. + * iFix LSB header in init.d script (Closes: #470060). + + -- Amaya Rodrigo Sastre Mon, 31 Mar 2008 18:43:40 +0200 + +boxbackup (0.10+really0.10-1ubuntu3) hardy; urgency=low + + * Don't redirect ucf calls. This was forgotten in the previous upload, + but should have been there. + + -- Tollef Fog Heen Thu, 06 Mar 2008 07:17:35 +0100 + +boxbackup (0.10+really0.10-1ubuntu2) hardy; urgency=low + + * Fix up postinst so we don't call db_stop too early, and use + --debconf-ok to ucf. (No versioned dependency since even oldstable + has a new enough ucf.) Use db_stop near the end to make sure we don't + hang after starting the daemon though. + + -- Tollef Fog Heen Wed, 05 Mar 2008 22:33:57 +0100 + +boxbackup (0.10+really0.10-1ubuntu1) hardy; urgency=low + + * Rebuild for libdb4.3 -> libdb4.6 migration. + * Set MOTU to maintainer. + + -- Chuck Short Mon, 03 Mar 2008 12:37:35 -0500 + +boxbackup (0.10+really0.10-1) unstable; urgency=low + + * revert new upstream accidentally slipped into unstable. + * big apologies that I now need to upload this package earlier than + promised to the boxbackup translators :( + * add watchfile + * Bug fix: "boxbackup-server recommends boxbackup-utils (unavailable)", + thanks to Pelayo Gonzalez (Closes: #424992). + * Apply new debconf templates and debian/control review. Thanks to + Christan Perrier! (Closes: #429396) + * Bug fix: "boxbackup: French debconf templates translation", thanks to + Vincent Bernat (Closes: #430856). + * Bug fix: "[l10n] Czech translation of boxbackup debconf messages", + thanks to Miroslav Kure (Closes: #431463). + * Bug fix: "boxbackup: [INTL:vi] Vietnamese debconf templates + translation", thanks to Clytie Siddall (Closes: #430535). + * Bug fix: "depends on non-essential package ucf in postrm", thanks to + Michael Ablassmeier (Closes: #431518). + * Bug fix: "depends on non-essential package ucf in postrm", thanks to + Michael Ablassmeier (Closes: #431519). + * run testsuite on build, use 'notest' in $DEB_BUILD_OPTIONS to disable + + -- Reinhard Tartler Tue, 03 Jul 2007 12:30:49 +0200 + +boxbackup (0.10-1) unstable; urgency=low + + * upload to debian (Closes: #416605) + * Cleanups in debian/rules + * Apply patch from svn, commit #626, in order to fix FTBFS + see http://bbdev.fluffy.co.uk/trac/changeset/626 + * Drop the boxbackup-utils package, since it only shipped one single + script, which is generally used for CA maintenance, so move it to the + to the boxbackup-server package + * Bump standards version to 3.7.2 + * add missing manpages + * add README.Debian + * use debhelper 5 + * cleanup the versioned dependencies in debian/control + + [ Jérôme Schell ] + + * New upstream version + * Add LSB headers in init script + + -- Reinhard Tartler Mon, 18 Jun 2007 15:35:55 +0100 + +boxbackup (0.09-3) unstable; urgency=low + + * Added man pages for bbackupd, bbackupd-config, bbackupctl, bbackupquery + * Improve lintian compatibility of the packages + + -- Jérôme Schell Mon, 10 Oct 2005 14:16:20 +0200 diff --git a/clean b/clean new file mode 100644 index 00000000..0f10520d --- /dev/null +++ b/clean @@ -0,0 +1,146 @@ +# generated using bzr status +ExceptionCodes.txt +Makefile +aclocal.m4 +configure +runtest.pl +test-backupdiff.log +test-backupstore.log +test-backupstorefix.log +test-backupstorepatch.log +test-basicserver.log +test-bbackupd.log +test-common.log +test-compress.log +test-crypto.log +test-httpserver.log +test-raidfile.log +bin/bbackupctl/Makefile +bin/bbackupd/Makefile +bin/bbackupd/autogen_ClientException.cpp +bin/bbackupd/autogen_ClientException.h +bin/bbackupd/bbackupd-config +bin/bbackupobjdump/Makefile +bin/bbackupquery/Makefile +bin/bbackupquery/autogen_Documentation.cpp +bin/bbackupquery/makedocumentation.pl +bin/bbstoreaccounts/Makefile +bin/bbstored/Makefile +bin/bbstored/autogen_BackupProtocolServer.cpp +bin/bbstored/autogen_BackupProtocolServer.h +bin/bbstored/bbstored-certs +bin/bbstored/bbstored-config +bin/s3simulator/Makefile +contrib/debian/bbackupd +contrib/debian/bbstored +contrib/mac_osx/org.boxbackup.bbackupd.plist +contrib/mac_osx/org.boxbackup.bbstored.plist +contrib/redhat/bbackupd +contrib/redhat/bbstored +contrib/solaris/bbackupd-manifest.xml +contrib/solaris/bbackupd-smf-method +contrib/solaris/bbstored-manifest.xml +contrib/solaris/bbstored-smf-method +contrib/suse/bbackupd +contrib/suse/bbstored +contrib/windows/installer/boxbackup.mpi +docs/docbook/ExceptionCodes.xml +docs/docbook/adminguide.pdf +docs/docbook/instguide.pdf +infrastructure/BoxPlatform.pm +infrastructure/makebuildenv.pl +infrastructure/makedistribution.pl +infrastructure/makeparcels.pl +lib/backupclient/Makefile +lib/backupclient/autogen_BackupProtocolClient.cpp +lib/backupclient/autogen_BackupProtocolClient.h +lib/backupclient/autogen_BackupStoreException.cpp +lib/backupclient/autogen_BackupStoreException.h +lib/backupstore/Makefile +lib/common/BoxConfig.h +lib/common/BoxConfig.h.in +lib/common/BoxPortsAndFiles.h +lib/common/Makefile +lib/common/autogen_CommonException.cpp +lib/common/autogen_CommonException.h +lib/common/autogen_ConversionException.cpp +lib/common/autogen_ConversionException.h +lib/common/makeexception.pl +lib/compress/Makefile +lib/compress/autogen_CompressException.cpp +lib/compress/autogen_CompressException.h +lib/crypto/Makefile +lib/crypto/autogen_CipherException.cpp +lib/crypto/autogen_CipherException.h +lib/httpserver/Makefile +lib/httpserver/autogen_HTTPException.cpp +lib/httpserver/autogen_HTTPException.h +lib/intercept/Makefile +lib/raidfile/Makefile +lib/raidfile/autogen_RaidFileException.cpp +lib/raidfile/autogen_RaidFileException.h +lib/raidfile/raidfile-config +lib/server/Makefile +lib/server/autogen_ConnectionException.cpp +lib/server/autogen_ConnectionException.h +lib/server/autogen_ServerException.cpp +lib/server/autogen_ServerException.h +lib/server/makeprotocol.pl +lib/win32/Makefile +test/backupdiff/Makefile +test/backupdiff/_main.cpp +test/backupdiff/_t +test/backupdiff/_t-gdb +test/backupstore/Makefile +test/backupstore/_main.cpp +test/backupstore/_t +test/backupstore/_t-gdb +test/backupstorefix/Makefile +test/backupstorefix/_main.cpp +test/backupstorefix/_t +test/backupstorefix/_t-gdb +test/backupstorefix/testfiles/testbackupstorefix.pl +test/backupstorepatch/Makefile +test/backupstorepatch/_main.cpp +test/backupstorepatch/_t +test/backupstorepatch/_t-gdb +test/basicserver/Makefile +test/basicserver/_main.cpp +test/basicserver/_t +test/basicserver/_t-gdb +test/basicserver/autogen_TestProtocolClient.cpp +test/basicserver/autogen_TestProtocolClient.h +test/basicserver/autogen_TestProtocolServer.cpp +test/basicserver/autogen_TestProtocolServer.h +test/bbackupd/Makefile +test/bbackupd/_main.cpp +test/bbackupd/_t +test/bbackupd/_t-gdb +test/bbackupd/testfiles/bbackupd-exclude.conf +test/bbackupd/testfiles/bbackupd-snapshot.conf +test/bbackupd/testfiles/bbackupd-symlink.conf +test/bbackupd/testfiles/bbackupd.conf +test/bbackupd/testfiles/extcheck1.pl +test/bbackupd/testfiles/extcheck2.pl +test/bbackupd/testfiles/notifyscript.pl +test/bbackupd/testfiles/syncallowscript.pl +test/common/Makefile +test/common/_main.cpp +test/common/_t +test/common/_t-gdb +test/compress/Makefile +test/compress/_main.cpp +test/compress/_t +test/compress/_t-gdb +test/crypto/Makefile +test/crypto/_main.cpp +test/crypto/_t +test/crypto/_t-gdb +test/httpserver/Makefile +test/httpserver/_main.cpp +test/httpserver/_t +test/httpserver/_t-gdb +test/raidfile/Makefile +test/raidfile/_main.cpp +test/raidfile/_t +test/raidfile/_t-gdb diff --git a/clean.sh b/clean.sh new file mode 100644 index 00000000..90a30513 --- /dev/null +++ b/clean.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +rm -rf debug/ +rm -rf local/ +rm -rf parcels/ +rm -rf release/ +rm -rf docs/htmlguide/ +rm -rf docs/man/ diff --git a/compat b/compat new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/compat @@ -0,0 +1 @@ +7 diff --git a/control b/control new file mode 100644 index 00000000..5cbdba67 --- /dev/null +++ b/control @@ -0,0 +1,57 @@ +Source: boxbackup +Section: utils +Priority: optional +Maintainer: Debian QA Group +Build-Depends: + autoconf, + automake, + autotools-dev, + debhelper (>> 7.0.0), + docbook-utils, + docbook-xml, + docbook-xsl, + libdb-dev (>= 4.7), + libedit-dev, + libssl1.0-dev, + libtest-lwp-useragent-perl, + xsltproc, + zlib1g-dev +Standards-Version: 3.9.8 +Homepage: http://boxbackup.org +Vcs-Git: git://anonscm.debian.org/collab-maint/boxbackup.git +Vcs-Browser: https://anonscm.debian.org/git/collab-maint/boxbackup.git + +Package: boxbackup-server +Architecture: any +Depends: + adduser, + debconf | debconf-2.0, + gawk, + openssl, + perl, + ucf, + ${misc:Depends}, + ${shlibs:Depends} +Description: server for the BoxBackup remote backup system + BoxBackup is an automatic on-line backup system. + The server waits for connections from remote clients, + authenticates them via X.509 certificates and stores the + encrypted data on hard drives with optional RAID techniques. + It also supports versions historization and per-user quotas. + +Package: boxbackup-client +Architecture: any +Depends: + debconf | debconf-2.0, + openssl, + perl, + ucf, + ${misc:Depends}, + ${shlibs:Depends} +Description: client for the BoxBackup remote backup system + BoxBackup is an automatic on-line backup system. + The client watches for changes on the local file system, + connects to a BoxBackup server and sends the changes via a + secure channel. All data is encrypted before being sent to + the server. A command-line tool is provided for restoration + of backups including deleted files and old versions. diff --git a/copyright b/copyright new file mode 100644 index 00000000..ffb3eead --- /dev/null +++ b/copyright @@ -0,0 +1,48 @@ +This package was debianized by Jérôme Schell on +Tue, 1 Jun 2004 07:51:24 +0000. + +It was downloaded from http://boxbackup.org + +Upstream Author: Ben Summers + +Copyright: + +Copyright (c) 2003, 2004 + Ben Summers. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All use of this software and associated advertising materials must + display the following acknowledgement: + This product includes software developed by Ben Summers. +4. The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +[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. + +Commentary + +This license is based on a standard BSD license. Some minor changes in wording have been made to fit in with English law. + +© Ben Summers, 2003, 2004 diff --git a/get-orig-source.sh b/get-orig-source.sh new file mode 100644 index 00000000..d56ab929 --- /dev/null +++ b/get-orig-source.sh @@ -0,0 +1,86 @@ +#!/bin/sh +# +# Script to create a 'pristine' tarball for the debian boxbackup source +# package. Copyright (C) 2009, Reinhard Tartler +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +set -eu + +usage() { + cat >&2 <&2 +} + +error () { + echo "$1" >&2 + exit 1; +} + +set +e +PARAMS=`getopt hr: "$@"` +if test $? -ne 0; then usage; exit 1; fi; +set -e + +eval set -- "$PARAMS" + +DEBUG=false +SVNREVISION= + +while test $# -gt 0 +do + case $1 in + -h) usage; exit 1 ;; + -r) SVNREVISION=$2; shift ;; + --) shift ; break ;; + *) echo "Internal error!" ; exit 1 ;; + esac + shift +done + +# sanity checks now +dh_testdir + +if [ -z $SVNREVISION ]; then + error "you need to specify an svn revision." +fi + +PACKAGENAME=boxbackup +baseurl="https://www.boxbackup.org/svn/box/RELEASE/0.11.1" +TARBALL=../${PACKAGENAME}_0.11.1~r${SVNREVISION}.orig.tar.gz + +TMPDIR=`mktemp -d` +trap 'rm -rf ${TMPDIR}' EXIT + + +svn export -r${SVNREVISION} \ + --ignore-externals \ + ${baseurl} \ + ${TMPDIR}/${PACKAGENAME} + +svn info -r${SVNREVISION} \ + ${baseurl} \ + | awk '/^Revision/ {print $2}' \ + > ${TMPDIR}/${PACKAGENAME}/.svnrevision + +tar czf ${TARBALL} -C ${TMPDIR} ${PACKAGENAME} diff --git a/patches/03-adjust-syslog-facility.diff b/patches/03-adjust-syslog-facility.diff new file mode 100644 index 00000000..e3033573 --- /dev/null +++ b/patches/03-adjust-syslog-facility.diff @@ -0,0 +1,50 @@ +change default syslog facility from LOG_LOCAL6 to LOG_DAEMON + +--- a/bin/bbstored/BackupStoreDaemon.cpp ++++ b/bin/bbstored/BackupStoreDaemon.cpp +@@ -203,7 +203,7 @@ void BackupStoreDaemon::Run() + SetProcessTitle("housekeeping, idle"); + whichSocket = 1; + // Change the log name +- ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); ++ ::openlog("bbstored/hk", LOG_PID, LOG_DAEMON); + // Log that housekeeping started + BOX_INFO("Housekeeping process started"); + // Ignore term and hup +--- a/lib/common/Logging.cpp ++++ b/lib/common/Logging.cpp +@@ -401,7 +401,7 @@ bool Syslog::Log(Log::Level level, const + return true; + } + +-Syslog::Syslog() : mFacility(LOG_LOCAL6) ++Syslog::Syslog() : mFacility(LOG_DAEMON) + { + ::openlog("Box Backup", LOG_PID, mFacility); + } +@@ -439,8 +439,8 @@ int Syslog::GetNamedFacility(const std:: + #undef CASE_RETURN + + BOX_ERROR("Unknown log facility '" << rFacility << "', " +- "using default LOCAL6"); +- return LOG_LOCAL6; ++ "using default DAEMON"); ++ return LOG_DAEMON; + } + + bool FileLogger::Log(Log::Level Level, const std::string& rFile, +--- a/docs/docbook/adminguide.xml ++++ b/docs/docbook/adminguide.xml +@@ -286,6 +286,12 @@ local5.info /var + Note: Separators must be tabs, + otherwise these entries will be ignored. + ++ Note2: The packaged ++ debian and ubuntu versions of boxbackup do not log to local6, ++ but to the more standard 'daemon' facility. This means you ++ should not have anything to do to your syslog configuration, ++ since it is configured to be logged by default. ++ + touch /var/log/box + touch /var/log/raidfile + diff --git a/patches/05-dont_use_net_for_docs.diff b/patches/05-dont_use_net_for_docs.diff new file mode 100644 index 00000000..71cb6c25 --- /dev/null +++ b/patches/05-dont_use_net_for_docs.diff @@ -0,0 +1,12 @@ +=== modified file 'docs/Makefile' +--- a/docs/Makefile ++++ b/docs/Makefile +@@ -10,7 +10,7 @@ + + all: docs + +-DBPROC_COMMAND = xsltproc ++DBPROC_COMMAND = xsltproc --nonet + MKDIR_COMMAND = mkdir + CP_COMMAND = cp + PERL_COMMAND = perl diff --git a/patches/06-gcc_4.4_fixes.diff b/patches/06-gcc_4.4_fixes.diff new file mode 100644 index 00000000..46ec64cf --- /dev/null +++ b/patches/06-gcc_4.4_fixes.diff @@ -0,0 +1,17 @@ +references: + +http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=526152 +https://bugs.launchpad.net/bugs/371809 + +Impact: FTBFS with gcc 4.4 + +=== modified file 'lib/server/ServerControl.cpp' +--- a/lib/server/ServerControl.cpp ++++ b/lib/server/ServerControl.cpp +@@ -1,5 +1,6 @@ + #include "Box.h" + ++#include + #include + #include + diff --git a/patches/series b/patches/series new file mode 100644 index 00000000..ab768cfb --- /dev/null +++ b/patches/series @@ -0,0 +1,3 @@ +03-adjust-syslog-facility.diff +05-dont_use_net_for_docs.diff +06-gcc_4.4_fixes.diff diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 00000000..6cd99807 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +[type: gettext/rfc822deb] boxbackup-client.templates +[type: gettext/rfc822deb] boxbackup-server.templates diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 00000000..195059dd --- /dev/null +++ b/po/cs.po @@ -0,0 +1,512 @@ +# Czech translation of boxbackup debconf messages. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Miroslav Kure , 2007,2008. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-01 11:36+0200\n" +"Last-Translator: Miroslav Kure \n" +"Language-Team: Czech \n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Má se klient BoxBackupu nastavit automaticky?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Konfigurační skript balíku může vytvořit konfigurační soubory pro klienta " +"BoxBackupu." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Pokud zrovna nekamarádíte s konfiguračními volbami BoxBackupu, měli byste " +"tuto možnost povolit." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Podrobnosti o nastavení klienta BoxBackupu naleznete v souboru /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Režim spouštění klienta BoxBackupu:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Klient BoxBackupu podporuje dva režimy zálohování:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"V „líném“ režimu bude zálohovací daemon pravidelně prohledávat souborový " +"systém a hledat změněné soubory. Poté nahraje soubory starší než zadaný věk " +"na zálohovací server." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Ve „snímkovém“ režimu se bude zálohování spouštět v pravidelných " +"intervalech. S balíkem se dodává cronový soubor /etc/cron.d/boxbackup-" +"client, který byste měli upravit dle svých potřeb." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Číslo účtu tohoto uzlu na zálohovacím serveru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Správce serveru BoxBackup by měl tomuto klientovi přidělit hexadecimální " +"číslo účtu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Pokud tomuto klientovi ještě nebylo přiděleno číslo účtu, ponechte pole " +"prázdné a nastavte jej později spuštěním „dpkg-reconfigure boxbackup-client“ " +"pod uživatelem root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Neplatné číslo účtu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Číslo účtu musí být hexadecimální číslo (např. 1F04 nebo 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Plně kvalifikované jméno zálohovacího serveru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Zadejte prosím plně kvalifikované doménové jméno BoxBackup serveru, který má " +"tento klient používat." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klient se připojí k serveru na TCP portu 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Seznam adresářů pro zálohování:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Zadejte prosím mezerami oddělený seznam adresářů, které se mají zálohovat na " +"vzdálený server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Tyto adresáře by v žádné úrovni podadresářů neměly obsahovat připojené " +"souborové systémy." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Neplatná cesta" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "Cesty k souborům musí být absolutní a oddělené mezerami." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Například: /home/mujucet /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (v sekundách) mezi prohledáváním adresářů:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup pravidelně prohledává vybrané adresáře a hledá změněné soubory." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Zadejte prosím interval mezi prohledáváními." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimální doba čekání (v sekundách) před nahráním souboru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Soubor se zazálohuje na server pouze po uplynutí určitého času od jeho " +"poslední změny." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Nízké hodnoty způsobí častější nahrávání na server, vytváření více revizí a " +"rychlejší rušení starých revizí." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximální doba čekání (v sekundách) před nahráním souboru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Často upravované soubory nejspíš nikdy nedosáhnou minimální hranice před " +"nahráním souboru a tedy nebudou zálohovány." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Zadejte prosím maximální čas, po kterém je nahrání změněného souboru na " +"server vynuceno." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Zadán neplatný čas" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Zadejte prosím neprázdnou celočíselnou hodnotu větší než nula." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Příjemce výstražných upozornění:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Jestliže se během zálohy vyskytne problém, klient BoxBackupu posílá " +"výstražné upozornění." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Zadejte prosím jméno lokálního uživatele (například „root“) nebo poštovní " +"adresu (například „spravce@priklad.cz“)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Vytvořit privátní klíč klienta a požadavek na certifikát X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Klient BoxBackupu vyžaduje privátní RSA klíč a odpovídající certifikát " +"X.509, aby se mohl autentizovat vůči serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Oba se mohou vytvořit automaticky. Požadavek na certifikát budete muset " +"poslat správci BoxBackup serveru, který požadavek podepíše a pošle zpět " +"současně s certifikátem certifikační autority serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Tyto soubory by měly být nakopírovány do konfiguračního adresáře BoxBackupu. " +"Očekávaná jména souborů jsou zadána v souboru /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Má se BoxBackup nastavit automaticky?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Konfigurační skripty balíku mohou vytvořit konfigurační soubory BoxBackup " +"serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Pokud zrovna nekamarádíte s konfiguračními volbami BoxBackupu, měli byste " +"tuto možnost povolit. Konfiguraci můžete provést i ručně pomocí skriptů " +"„raidfile-config“ a „bbstored-config“." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Dokud jej nenakonfigurujete, server se odmítne spustit. Každopádně je " +"doporučeno přečíst si /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Umístění RAID adresářů:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Vyberte prosím umístění tří adresářů se souborovými RAIDy." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Pro povolení RAIDu zadejte mezerami oddělený seznam tří oblastí, kde každá " +"by měla ležet na jiném fyzickém disku (například: „/raid/0.0 /raid/0.1 /" +"raid/0.2“)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Nechcete-li povolit RAID, zadejte cestu k jedinému adresáři, do kterého se " +"budou ukládat zálohy (například /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Pokud neexistují, budou tyto adresáře vytvořeny." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Neplatné cesty" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Například: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Velikost bloku RAIDu v uživatelském prostoru:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup využívá techniky RAIDu v uživatelském prostoru." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Zadejte prosím velikost bloku, která se má použít pro úložiště. Pro " +"maximální efektivitu byste měli zvolit stejnou velikost bloku, jakou má " +"souborový systém, na kterém je úložiště postaveno (pro souborové systémy " +"ext2 to zjistíte zjistíte například příkazem „tune2fs -l“)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Tuto hodnotu byste měli nastavit i v případě, že neplánujete použití RAIDu." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Vytvořit privátní klíč serveru a požadavek na certifikát X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup server vyžaduje privátní RSA klíč a odpovídající certifikát X.509, " +"aby mohl provádět autentizaci mezi klientem a serverem a aby mohl šifrovat " +"komunikaci." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Oba se mohou vytvořit automaticky. Certifikát budete muset podepsat svou " +"kořenovou certifikační autoritou (viz balík boxbackup-server) a podepsaný " +"jej umístit společně s kořenovým certifikátem certifikační autority do " +"složky s konfigurací." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Neplatná velikost bloku" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Velikost bloku musí být mocninou dvou (např. 1024 nebo 4096)." diff --git a/po/da.po b/po/da.po new file mode 100644 index 00000000..d9ca9924 --- /dev/null +++ b/po/da.po @@ -0,0 +1,515 @@ +# Danish translation BoxBackup. +# Copyright (C) 2010 BoxBackup & Joe Hansen. +# This file is distributed under the same license as the BoxBackup package. +# Joe Hansen , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: BoxBackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2010-05-18 17:30+01:00\n" +"Last-Translator: Joe Hansen \n" +"Language-Team: Danish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Skal klienten til BoxBackup konfigureres manuelt?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Pakkekonfigurationsskripterne kan oprette konfigurationsfiler for klienten " +"til BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Du skal vælge denne indstilling, hvis du ikke kender til BoxBackups " +"konfigurationsindstillinger." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Læs venligst /usr/share/doc/boxbackup-client/README.Debian for detaljer om " +"konfigurationen til BoxBackup-klienten." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Kørselstilstand for klienten til BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "" +"Klienten til BoxBackup understøtter to tilstande til sikkerhedskopiering:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"I tilstanden 'lazy' vil dæmonen til sikkerhedskopiering med passende " +"mellemrum skanne filsystemet på jagt efter ændrede filer. Den vil så " +"overføre filer ældre end en angivet alder til sikkerhedskopiserveren." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"I tilstanden 'snapshot' vil sikkerhedskopien blive kørt med faste " +"intervaller. Et cronjob (/etc/cron.d/boxbackup-client) tilbydes sammen med " +"pakken og skal tilpasses til dine behov." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontonummer for denne knude på sikkerhedskopiserveren:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Administratoren på BoxBackup-serveren skal have tildelt denne klient et " +"heksadecimalt kontonummer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Hvis intet kontonummer er blevet tildelt endnu, så efterlad dette felt tomt " +"og konfigurer det senere ved at køre 'dpkg-reconfigure boxbackup-client' som " +"administrator (root)." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ugyldigt kontonummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Kontonummeret skal være et heksadecimalt nummer (såsom 1F04 eller 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Fuldt kvalificeret domænenavn på sikkerhedskopiserveren:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Indtast venligt det fuldt kvalificeret domænenavn på den BoxBackup-server " +"som din klient vil bruge." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klienten vil forbinde til serveren på TCP port 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste af mapper der skal sikkerhedskopieres:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Angiv venligst en mellemrumsadskilt liste af mapper der skal " +"sikkerhedskopieres til den eksterne server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"De mapper må ikke indeholde monterede filsystemer på noget niveau i deres " +"undermapper." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ugyldigt stinavn" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Stinavnene til mapperne skal være absolutte stinavne, adskilt af mellemrum." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "For eksempel. /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (i sekunder) mellem mappeskanninger:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup skanner med faste intervaller de udvalgte mapper, på jagt efter " +"ændrede filer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Vælg venligst skanningsinterval i sekunder." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimum ventetid (i sekunder) før overførsel af en fil:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"En fil vil først blive overført til serveren efter et bestemt tidsinterval " +"efter sidste ændring." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lave intervalværdier vil udløse ofte overførsler til serveren og flere " +"revisioner oprettes, hvilket medfører at gamle revisioner vil blive fjernet " +"tidligere." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maksimal ventetid (i sekunder) før overførsel af en fil:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Ofte ændrede filer vil højst sandsynlig aldrig blive overført, hvis de " +"aldrig når den mindste ventetid." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Indtast venligst den maksimale tid før en ændret fil bliver overført til " +"serveren." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ugyldig tid indtastede" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Indtast venligst en heltalsværdi større end nul." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Modtager til alarmbeskeder:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Klienten til BoxBackup sender alarmbeskeder, når et problem opstår under " +"sikkerhedskopieringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Indtast venligst enten et lokalt brugernavn (for eksempel 'root') eller en e-" +"post-adresse (for eksempel 'admin@eksempl.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Opret klientens private nøgle og X-509-certifikatanmodning?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Klienten til BoxBackup kræver en RSA-privatnøgle og det tilsvarende X.509-" +"certificat for at kunne dokumentere sig selv i forhold til serveren." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Begge kan oprettes automatisk. Du vil skulle sende certifikatanmodningen til " +"BoxBackups serveradministrator som vil underskrive den og sende den tilbage " +"til dig sammen med serverens Certification Authority-certifikat." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Disse filer skal kopiers ind i BoxBackups konfigurationsmappe. Filnavnene " +"der skal bruges er oplyst i filen /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Skal BoxBackup automatisk konfigureres?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Skriptene for pakkekonfigurationen kan oprette konfigurationsfilerne for " +"BoxBackup-serveren." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Du skal vælge denne indstilling hvis du ikke kender til BoxBackups " +"konfigurationsindstillinger. Denne konfiguration kan foretages manuelt med " +"skripterne 'raidfile-config' og 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Serveren vil ikke starte hvis den ikke er konfigureret. Uanset hvad så " +"anbefales det at læse /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Placering af RAID-mapperne:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Vælg venligst placeringen af de tre RAID-filmapper." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"For at aktivere RAID, skal mappenavnene være en mellemrumsadskilt liste af " +"tre partitioner, hver på forskellige fysiske harddiske (for eksempel: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Hvis du ikke ønsker at aktivere RAID, så specificer bare stien til en mappe " +"hvor sikkerhedskopierne vil blive gemt (for eksempel, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Disse mapper vil blive oprettet, hvis de ikke findes." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ugyldige stinavne" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "For eksempel: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blokstørrelse for userland-RAID-systemet:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup bruger userland-RAID-teknikker." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Vælg venligst blokstørrelse til brug for lageret. For maksimal effektivitet, " +"skal du vælge blokstørrelsen på det underlæggende filsystem (som kan ses for " +"ext2-filsystemer med kommandoen 'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Denne værdi skal angives, selv om du ikke planlægger at bruge RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Opret en serverprivatnøgle og X.509-certifikatforespørgsel?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-serveren kræver en RSA-privatnøgle og den tilsvarende X.509-" +"certificat for at udføre klient-server bekræftelse og kryptering af " +"kommunikation." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Begge kan oprettes automatisk. Du vil skulle underskrive certifikatet med " +"din root CA (se pakken boxbackup-server) og placer dette underskrevne " +"certificat og root CA-certifikatet i konfigurationsmappen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ugyldig blokstørrelse" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Blokstørrelsen skal kunne opløses i 2 (for eksempel 1024 eller 4096)." diff --git a/po/de.po b/po/de.po new file mode 100644 index 00000000..20eca289 --- /dev/null +++ b/po/de.po @@ -0,0 +1,526 @@ +# Translation of the boxbackup debconf template. +# Copyright (C) 2008 Kai Wasserbäch +# This file is distributed under the same license as boxbackup package. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.10+really0.10-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-02-13 22:06+0100\n" +"Last-Translator: Kai Wasserbäch \n" +"Language-Team: German \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Soll der BoxBackup-Client automatisch konfiguriert werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Die Paket-Konfigurations-Skripte können die Konfigurationsdateien für den " +"BoxBackup-Client erstellen." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Sie sollten diese Option wählen, falls Sie nicht mit den " +"Konfigurationsoptionen von BoxBackup vertraut sind." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Bitte lesen Sie für detaillierte Informationen zur Konfiguration des " +"BoxBackup-Clients die Datei /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Betriebsmodus für den BoxBackup-Client:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Der BoxBackup-Client unterstützt zwei Backup-Modi:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Im »lazy«-Modus prüft der Sicherungsdienst das Dateisystem regelmäßig, um " +"veränderte Dateien zu finden. Dateien, die ein zuvor festgelegtes Alter " +"überschritten haben, werden auf den Sicherungsserver überspielt." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Im »snapshot«-Modus wird die Sicherung in zuvor festgelegten Abständen " +"durchgeführt. Eine Cron-Datei (/etc/cron.d/backup-client) wird mit diesem " +"Paket ausgeliefert und sollte an die eigenen Anforderungen angepasst werden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontennummer für diesen Knoten auf dem Sicherungsserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Der Administrator des BoxBackup-Servers sollte diesem Client eine " +"hexadezimale Kontennummer zugewiesen haben." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Falls bislang noch keine Kontennummer zugewiesen wurde, lassen Sie dieses " +"Feld frei und konfigurieren Sie die Option später durch Ausführen des " +"Befehls »dpkg-reconfigure boxbackup-client« als root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ungültige Kontennummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Die Kontennummer muss hexadezimal sein (wie zum Beispiel 1F04 oder 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Vollständiger Domainname des Sicherungsservers:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Bitte geben Sie den durch Ihren Client zu verwendenden vollständigen " +"Domainnamen des BoxBackup-Servers an." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Der Client wird sich mit dem Server auf dem TCP-Port 2201 verbinden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste der zu sichernden Verzeichnisse:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Bitte geben Sie die auf den entfernten Server zu sichernden Verzeichnisse " +"als durch Leerzeichen getrennte Liste ein." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Diese Verzeichnisse sollten in keinem Unterverzeichnis ein eingebundenes " +"Dateisystem enthalten." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ungültige Pfadangabe" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Die Pfade zu den Verzeichnissen müssen absolut sein und durch Leerzeichen " +"getrennt angegeben werden." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Zum Beispiel: /home/meinkonto /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervall (in Sekunden) zwischen Verzeichnisüberprüfungen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup überprüft die ausgewählten Verzeichnisse von Zeit zu Zeit auf " +"modifizierte Dateien." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Bitte wählen Sie das Überprüfungsintervall in Sekunden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Mindestwartezeit (in Sekunden) vor dem Hochladen einer Datei:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Eine Datei wird erst eine bestimmte Zeit nach der letzten Modifikation auf " +"den Server übertragen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Kleine Intervalle resultieren in häufigeren Uploads auf den Server. Dabei " +"werden mehr Revisionen angelegt und alte früher gelöscht." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximale Wartezeit (in Sekunden) bevor eine Datei hochgeladen wird:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Häufig modifizierte Dateien werden wahrscheinlich nie auf den Server " +"übertragen, falls sie die minimale Wartezeit nicht erreichen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Bitte geben sie die Zeit ein, die maximal erreicht werden darf, bevor das " +"Hochladen einer Datei erzwungen wird." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ungültige Zeit eingegeben" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Bitte geben Sie einen ganzzahligen Wert größer Null ein." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Empfänger für Alarmmeldungen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Der BoxBackup-Client versendet Alarmmeldungen, wenn während des Sicherns ein " +"Problem auftritt." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Bitte geben Sie entweder einen lokalen Benutzernamen (zum Beispiel »root«) " +"oder eine E-Mail-Adresse (zum Beispiel »admin@example.org«) ein." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Soll der private Schlüssel für den Client und der X.509-Zertifikat-Anfrage " +"erzeugt werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Der BoxBackup-Client benötigt einen privaten RSA-Schlüssel und das " +"zugehörige X.509-Zertifikat, um sich gegenüber dem Server authentifizieren " +"zu können." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Beides kann automatisch erzeugt werden. Sie müssen die Zertifikatanfrage an " +"den Administrator des BoxBackup-Servers senden, der dieses dann signieren " +"und Ihnen zusammen mit dem Certification-Authority-Zertifikat des Servers " +"zurücksenden wird." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Diese Dateien sollten in das Konfigurationsverzeichnis von BoxBackup kopiert " +"werden. Die zu verwendenden Dateinamen werden in /etc/boxbackup/bbacupd.conf " +"angegeben." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Soll BoxBackup automatisch konfiguriert werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Die im Paket enthaltenen Konfigurationskripte können die Konfiguration für " +"den BoxBackup-Server erzeugen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Sie sollten diese Option wählen, falls Sie nicht mit den " +"Konfigurationsoptionen von BoxBackup vertraut sind. Die Konfiguration kann " +"mit Hilfe der Skripte »raidfile-config« und »bbstored-config« manuell " +"vorgenommen werden." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Der Server wird nicht starten, falls er nicht konfiguriert wurde. In jedem " +"Fall wird empfohlen, /usr/share/doc/boxbackup-server/README.Debian zu lesen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ort der RAID-Verzeichnisse:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Bitte wählen Sie den Ort für die drei RAID-Datei-Verzeichnisse aus." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Um die RAID-Funktion zu aktivieren, sollte eine durch Leerzeichen getrennte " +"Liste dreier Partitionen eingegeben werden. Jede dieser Partitionen sollten " +"auf einem anderen physischen Laufwerk sein (zum Beispiel »/raid/0.0 /" +"raid/0.1 /raid/0.2«)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Falls Sie die RAID-Funktion nicht aktivieren wollen, geben Sie einfach einen " +"Pfad zu einem Verzeichnis an, in dem die Sicherungskopien gespeichert werden " +"sollen (zum Beispiel /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Diese Verzeichnisse werden angelegt, sollten sie nicht existieren." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ungültige Pfadnamen" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Zum Beispiel: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blockgröße für das im Benutzerbereich angesiedelte RAID-System:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup verwendet RAID-Techniken für den Benutzerbereich." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Bitte wählen Sie die zu verwendende Blockgröße zur Speicherung. Um maximale " +"Effizienz zu erreichen, sollten Sie die Blockgröße des darunterliegenden " +"Dateisystems wählen. Diese kann für Ext-Dateisysteme mit dem Befehl »tune2fs " +"-l« angezeigt werden." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Dieser Wert sollte auch dann angegeben werden, falls Sie kein RAID verwenden " +"wollen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Soll ein privater Server-Schlüssel und eine X.509-Zertifikatanfrage erzeugt " +"werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Der BoxBackup-Server benötigt einen privaten RSA-Schlüssel und ein " +"zugehöriges X.509-Zertifikat, um die Client-Server-Authentifizierung " +"durchführen zu können und die Kommunikation zu verschlüsseln." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Beides kann automatisch erzeugt werden. Sie müssen das Zertifikat mit Ihrem " +"Wurzel-CA-Zertifikat signieren (siehe das Paket »boxbackup-server«) und das " +"signierte Zertifikat sowie das Wurzel-CA-Zertifikat in Ihrem " +"Konfigurationsordner ablegen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ungültige Blockgröße." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Die Blockgröße muss eine Potenz von Zwei sein (z.B. 1024 oder 4096)." diff --git a/po/es.po b/po/es.po new file mode 100644 index 00000000..cf7bb72e --- /dev/null +++ b/po/es.po @@ -0,0 +1,549 @@ +# boxbackup po-debconf translation to Spanish +# Copyright (C) 2009 Software in the Public Interest +# This file is distributed under the same license as the boxbackup package. +# +# Changes: +# - Initial translation +# Omar Campagne , 2009 +# +# - Updates +# TRADUCTOR , AÑO +# +# Traductores, si no conocen el formato PO, merece la pena leer la +# de gettext, especialmente las secciones dedicadas a este +# formato, por ejemplo ejecutando: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Equipo de traducción al español, por favor, lean antes de traducir +# los siguientes documentos: +# +# - El proyecto de traducción de Debian al español +# http://www.debian.org/intl/spanish/ +# especialmente las notas de traducción en +# http://www.debian.org/intl/spanish/notas +# +# - La guía de traducción de po's de debconf: +# /usr/share/doc/po-debconf/README-trans +# o http://www.debian.org/intl/l10n/po-debconf/README-trans +# +msgid "" +msgstr "" +"Project-Id-Version: 0.11\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-07-16 18:15+0200\n" +"Last-Translator: Omar Campagne \n" +"Language-Team: Debian l10n Spanish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "¿Desea que el cliente de BoxBackup se configure automáticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"El paquete de scripts de configuración puede generar los archivos de " +"configuración para el cliente de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Debería escoger esta opción si no está familiarizado con las opciones de " +"configuración de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Lea el archivo «/usr/share/doc/boxbackup-client/README.Debian» para más " +"detalles acerca de la configuración del cliente de BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de ejecución para el cliente de BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "El cliente de BoxBackup ofrece dos modos de copia de seguridad:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"En modo «lazy» (vago), el demonio de copia de seguridad examinará con " +"regularidad el sistema de archivos en busca de archivos modificados. " +"Finalizado esto, subirá los archivos de una antigüedad específica al " +"servidor de copias de seguridad." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"En modo «snapshot» (captura de imagen), la copia de seguridad se generará a " +"intervalos regulares. Un archivo cron (/etc/cron.d/boxbackup-client) se " +"incluye en el paquete, el cual puede modificar para satisfacer sus " +"necesidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de cuenta de este nodo en el servidor de copia de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"El administrador del servidor de BoxBackup debería haber asignado a este " +"cliente un número de cuenta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Si no ha asignado aún ningún número de cuenta, deje este campo en blanco y " +"configúrelo más tarde ejecutando «dpkg-reconfigure boxbackup-client» como " +"administrador." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de cuenta no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"El número de cuenta debe ser un número hexadecimal (p. ej., «1F04» o «4500»)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nombre de dominio completo del servidor de copias de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Introduzca el nombre de dominio completo del servidor de BoxBackup que el " +"cliente usará." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "El cliente se conectará al servidor a través del puerto TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de directorios de los cuales crear una copia de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Introduzca una lista de directorios, separados por un espacio, de los cuales " +"crear una copia de seguridad en el servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esos directorios no pueden contener sistemas de archivos montados en ningún " +"nivel de sus subdirectorios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nombre de ruta no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Los nombres de ruta de los directorios deben ser nombres de ruta absolutos, " +"separados por espacios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por ejemplo: «/home/tucuenta /etc/»" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo de tiempo (en segundos) entre exploraciones de directorios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Boxbackup explora los directorios seleccionados con regularidad en busca de " +"archivos modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Seleccione el intervalo entre cada exploración en segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tiempo mínimo de espera (en segundos) antes de subir un archivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un archivo se sube al servidor sólo después de un tiempo especifico desde su " +"última modificación." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Un valor bajo de intervalo propicia subidas de archivos más regulares al " +"servidor, generando más revisiones y haciendo que las revisiones más " +"antiguas se eliminen antes." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tiempo máximo de espera (en segundos) antes de subir un archivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Puede que los archivos modificados con frecuencia no se suban si nunca " +"alcanzan el tiempo mínimo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Introduzca el tiempo máximo de espera antes de forzar la subida de un " +"archivo modificado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tiempo introducido no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Introduzca un valor entero mayor que 0." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario de las notificaciones de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"El cliente de BoxBackup envía notificaciones de alerta cuando ocurre un " +"problema durante la creación de la copia de seguridad." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Introduzca un nombre de usuario local (por ejemplo, «root») o una dirección " +"de correo electrónico (por ejemplo, «admin@example.org»)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"¿Desea generar la clave privada del cliente y la petición de certificado " +"X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"El cliente de BoxBackup necesita una clave privada RSA y su correspondiente " +"certificado X.509 para autenticarse con el servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos pueden ser generados automáticamente. Necesitará enviar la petición de " +"certificado al administrador del servidor de BoxBackup, el cual lo firmará y " +"reenviará a usted junto con el certificado de la «CA» (Autoridad de " +"Certificación) del servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Estos archivos se deberían copiar en el directorio de configuración de " +"BoxBackup. Los nombres de archivo empleados se encuentran en el archivo «/" +"etc/boxbackup/bbackupd.conf»." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "¿Desea que BoxBackup se configure automáticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Los scripts de configuración del paquete pueden generar los archivos de " +"configuración del servidor de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Debería escoger esta opción si no está familiarizado con las opciones de " +"configuración de BoxBackup. Puede configurarlo manualmente, o mediante los " +"scripts «raidfile-config» y «bbstored-config»." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"El servidor no se iniciará si no está configurado. En cualquier caso, se " +"recomienda leer «/usr/share/doc/boxbackup-server/README.Debian»." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ubicación de los directorios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Escoja la ubicación de los tres directorios RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para habilitar RAID, los nombres de directorio deberían conformar una lista " +"de tres particiones, separando cada elemento del mismo con un espacio, y " +"cada uno de ellos en diferentes discos duros físicos (por ejemplo: «/" +"raid/0.0 /raid/0.1 /raid/0.2»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Si no desea habilitar RAID, especifique sólo la ruta a un directorio donde " +"poder guardar las copias de seguridad (p. ej., «/usr/local/lib/boxbackup»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "En caso de no existir, se crearán estos directorios." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nombres de ruta no válidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por ejemplo: «/raid/0.0 /raid/0.1 /raid/0.2»" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamaño de bloque para el sistema «userland» de RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup utiliza técnicas «userland» de RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Escoja el tamaño de bloque empleado para el almacenamiento. Para obtener la " +"máxima eficiencia, debería escoger el tamaño de bloque del sistema de " +"archivos subyacente (el cual se muestra en los sistemas de archivos ext2 " +"ejecutando la orden «tune2fs -l»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Debe definir este valor aunque no tenga planeado utilizar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"¿Desea generar una clave privada de servidor y la petición de certificado " +"X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"El servidor «BoxBackup» requiere una clave privada RSA y el correspondiente " +"certificado X.509 para la autenticación cliente-servidor y el cifrado de " +"comunicaciones." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos pueden ser generados automáticamente. Necesitará firmar el certificado " +"con su «CA» raíz (vea el paquete «boxbackup-server») y poner este " +"certificado firmado y el certificado de la «CA» raíz en el directorio de " +"configuración." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamaño de bloque no válido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "El tamaño del bloque ha de ser potencia de dos (p. ej., 1024 o 4096)." diff --git a/po/eu.po b/po/eu.po new file mode 100644 index 00000000..ff3d71b8 --- /dev/null +++ b/po/eu.po @@ -0,0 +1,527 @@ +# translation of boxbackup debconf to Basque +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# xabier bilbao , 2008. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup-eu\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-03 02:21+0200\n" +"Last-Translator: xabier bilbao \n" +"Language-Team: basque \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "BoxBackup bezeroa automatikoki konfiguratu behar al da?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Paketearen konfigurazio script-ek BoxBackup bezeroarentzako konfigurazio-" +"fitxategiak sor ditzakete." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Aukera hau hautatu beharko zenuke ez baldin badituzu Boxbackup-en " +"konfigurazio-aukerak ondo ezagutzen." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"BoxBackup bezeroaren konfigurazioaz xehetasun gehiago jakiteko, irakur " +"ezazu /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup bezeroaren exekuzio-modua:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup bezeroak bi modutara egin dezake babeskopia:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"'Lazy' (sinplifikatu) moduan, babeskopia-deabruak fitxategi-sistema aldiro " +"arakatuko du aldatutako fitxategien bila. Data jakin bat baino zaharragoak " +"diren fitxategiak babeskopien zerbitzarira igoko ditu." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"'Snapshot' (argazki) moduan, babeskopia aldiro exekutatuko da. Cron " +"fitxategi bat (/etc/cron.d/boxbackup-client) dakar paketeak, norberaren " +"beharren arabera egokitu behar dena." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Nodo honentzako kontu-zenbakia babeskopien zerbitzarian:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Bezero honek kontu-zenbaki hamaseitar bat izan behar luke BoxBackup " +"zerbitzariaren administratzaileak ezarririk." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Oraindik ez bazaio kontu-zenbakirik ezarri, utzi bete gabe arlo hau eta " +"konfigura ezazu geroago, 'dpkg-reconfigure boxbackup-client' root gisa " +"exekutatuz." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Kontu-zenbaki baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Zenbaki hamaseitarra izan behar da kontu-zenbakia (1F04 edo 4500 erakoa)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Babeskopia-zerbitzariaren guztiz kualifikaturiko domeinu-izena (FQDN):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Sar ezazu hemen zure bezeroak erabiliko duen BoxBackup zerbitzariaren guztiz " +"kualifikaturiko domeinu-izena." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Bezeroa TCP 2201 atakan konektatuko da zerbitzarira." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Babeskopian gordetzeko direktorioen zerrenda:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Zerrenda itzazu, tarte soilez banatuta, urruneko zerbitzarian babeskopian " +"gordetzeko direktorioak." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Direktorio horiek ez lukete eduki behar muntatutako fitxategi-sistemarik " +"haien azpidirektorioetako edozein mailatan." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Bide-izen baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Direktorioetarako bide-izen absolutuak idatzi behar dira, tartez banaturik." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Adibidez: /home/nirekontua /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Denbora-tartea (segundotan) direktorioen arakatzeen artean:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup-ek aldiro arakatzen ditu aukeratu diren direktorioak, aldatutako " +"fitxategien bila." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Zehaztu ezazu arakatzea zenbatero egin behar den." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" +"Fitxategi bat igo aurretik itxaron beharreko gutxieneko denbora (segundotan):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Denbora jakin bat itxarongo du programak fitxategi bakoitza, aldaketak jaso " +"ondoren, zerbitzarira igo aurretik." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Arakatzeetarako denbora tarte txikiak ezarriz gero, sarri igoko dira " +"fitxategiak zerbitzarira, eta sarri berrituko dira babeskopiak, ondorioz " +"kopia zaharrak lehenago ezabatuz." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "" +"Fitxategi bakoitza igo aurretik itxaron beharreko gehienezko denbora " +"(segundotan):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Sarri aldatzen diren fitxategiak zerbitzarira inoiz ere igo gabe geldi " +"daitezke, ez badute gutxieneko denbora irauten aldatu gabe." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Sar ezazu itxaron beharreko gehienezko denbora aldatutako fitxategi bakoitza " +"zerbitzarira igo aurretik." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Denbora baliogabea sartu duzu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Sar ezazu balio oso bat, zero baino handiagoa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Alerta abisuen hartzailea:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup bezeroak alerta abisuak bidaltzen ditu babeskopia egitean arazoren " +"bat gertatzen denean." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Sar ezazu erabiltzaile-izen lokal bat (adibidez, 'root') edo eposta helbide " +"bat (adibidez, 'admin@adibidea.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Bezeroarentzako gako pribatua eta X.509 ziurtagiriaren eskaera sortzea nahi " +"duzu?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup bezeroak RSA gako pribatua eta hari dagokion X.509 ziurtagiria " +"behar ditu bere burua zerbitzarian egiaztatzeko." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Biak automatikoki sor daitezke. Ziurtagiri-eskaera BoxBackup zerbitzariko " +"administratzaileari bidali beharko diozu. Hark izenpetuko du eta atzera zuri " +"bidaliko dizu zerbitzariko Ziurtagiri Emailearen (CA) ziurtagiriarekin " +"batera." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Fitxategi horiek BoxBackup-en konfigurazio direktoriora kopiatu behar dira. " +"Fitxategiei eman beharreko izenak /etc/boxbackup/bbackupd.conf fixategian " +"aurkituko dituzu." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup automatikoki konfiguratu?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Paketearen konfigurazio script-ek BoxBackup zerbitzarirako konfigurazio-" +"fitxategiak sor ditzakete." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Hauta ezazu bide hau ez badituzu BoxBackup-en konfigurazio aukerak ondo " +"ezagutzen. Konfigurazioa eskuz egin daiteke 'raidfile-config' eta 'bbstored-" +"config' script-en bidez." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Zerbitzaria ez da abiatuko konfiguraturik ez badago. Zure kasua edozein dela " +"ere, gomendagarria da /usr/share/doc/boxbackup-server/README.Debian " +"irakurtzea." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID direktorioen kokalekua:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Aukera ezazu hiru RAID fitxategi-direktorioen kokalekua." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAID kudeaketa gaitzeko, hiru partizioren zerrenda gisa eman behar dira " +"direktorio-izenak, tarte hutsez banaturik, eta direktorio bakoitza disko " +"gogor fisiko batean kokaturik (adibidez: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"RAID kudeaketa gaitu nahi ez baduzu, nahikoa duzu direktorio baterako bidea " +"ematea, babeskopiak bertan gorde daitezen (adibidez, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Direktorio hauek sortu egingo dira aurretik ez badaude." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Bide-izen baliogabeak" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Adibidez: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Userland (erabiltzaile-eremuko) RAID sistemarako bloke-tamaina:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "Boxbackup-ek userland (erabiltzaile-eremuko) RAID sistema darabil." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Aukera ezazu biltegiratzerako erabiliko den bloke-tamaina. Eraginkortasun " +"handiena lortzeko, aukera ezazu azpiko fitxategi-sistemak duen bloke-tamaina " +"bera (hura zein den ikusteko 'tune2fs -l' komandoa erabil daiteke ext2 " +"fitxategi-sistemen kasuan)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Balio hau ezarri beharra dago, RAID erabiltzeko asmoa ez baduzu ere." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Zerbitzarirako gako pribatua eta X.509 ziurtagiriaren eskaera sortzea nahi " +"duzu?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup bezeroak RSA gako pribatua eta hari dagokion X.509 ziurtagiria " +"behar ditu bezero/zerbitzari egiaztapena eta komunikazio-enkriptazioa " +"gauzatzeko." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Automatikoki sor daitezke biak. Horretarako, ziurtagiria sinatu beharko dizu " +"zure Ziurtagiri Emaile (CA) nagusiak (ikus boxbackup-server paketea) eta " +"sinaturiko ziurtagiria, root (erro) CAren ziurtagiriarekin batera, " +"konfigurazio-karpetan kokatu beharko duzu." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Bloke-tamaina baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Bloke-tamainak biren berredura izan behar du (adb. 1024 edo 4096)." diff --git a/po/fi.po b/po/fi.po new file mode 100644 index 00000000..da150ff4 --- /dev/null +++ b/po/fi.po @@ -0,0 +1,504 @@ +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-03 23:20+0200\n" +"Last-Translator: Esko Arajärvi \n" +"Language-Team: Finnish \n" +"Language: fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Tulisiko BoxBackup-asiakkaan asetukset tehdä automaattisesti?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"BoxBackup-asiakkaan asetustiedostot voidaan luoda paketin " +"asetuskomentosarjoilla." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "Valitse tämä, jos BoxBackupin asetusvalitsimet eivät ole tuttuja." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Lisätietoja BoxBackup-asiakkaan asetuksista löytyy tiedostosta /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup-asiakkaan ajotapa:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup-asiakas tukee kahdenlaisia varmuuskopioita:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Laiska tapa (”lazy”) tarkoittaa, että taustaohjelma säännöllisesti etsii " +"tiedostojärjestelmästä muuttuneita tiedostoja. Tämän jälkeen tiettyä ikää " +"vanhemmat tiedostot kopioidaan varmuuskopiopalvelimelle." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Levykuvatapa (”snapshot”) tarkoittaa, että varmuuskopio otetaan säännöllisin " +"aikavälein. Paketissa on mukana cron-tiedosto (/etc/cron.d/boxbackup-" +"client), joka tulisi mukauttaa tarpeisiin." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Tämän solmun tilinumero varmuuskopiopalvelimella:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"BoxBackup-palvelimen ylläpitäjän olisi tullut antaa tälle asiakkaalle " +"heksadesimaalinen tilinumero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Jos tilinumeroa ei ole vielä annettu, jätä kenttä tyhjäksi ja aseta se " +"myöhemmin ajamalla pääkäyttäjänä komento ”dpkg-reconfigure boxbackup-client”." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Virheellinen tilinumero" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Tilinumeron tulee olla heksadesimaaliluku (kuten 1F04 tai 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Varmuuskopiopalvelimen täydellinen verkkonimi:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "Anna asiakkaan käyttämän BoxBackup-palvelimen täydellinen verkkonimi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Asiakas ottaa yhteyden palvelimen TCP-porttiin 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Hakemistot, joista otetaan varmuuskopiot:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Anna välilyönnein eroteltu lista hakemistoista, joista tehdään varmuuskopiot " +"etäpalvelimelle." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Näiden hakemistojen ei tulisi sisältää liitettyjä tiedostojärjestelmiä " +"millään alihakemistojen tasolla." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Virheellinen polku" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Hakemistojen polkujen tulee olla täydellisiä ja ne tulee erottaa toisistaan " +"välilyönnein." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Esimerkiksi: /home/omahakemisto /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Hakemistojen skannausten välinen aika (sekunteina):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup säännöllisesti skannaa valitut hakemistot etsien muutettuja " +"tiedostoja." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Anna skannausten aikaväli sekunteina." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Vähimmäisodotusaika (sekunteina) ennen tiedoston kopioimista:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Tiedosto kopioidaan palvelimelle vasta, kun sen viimeisimmästä muokkauksesta " +"on kulunut tietty aika." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lyhyt aika-arvo aiheuttaa tiheitä kopiointeja palvelimelle, jolloin luodaan " +"useampia versioita ja vanhemmat versiot poistetaan aiemmin." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Enimmäisodotusaika (sekunteina) ennen tiedoston kopioimista:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Usein muokattuja tiedostoja ei kopioida koskaan talteen, jos muokkausten " +"välissä ei ole vähimmäisodotusaikaa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Anna enimmäisaika, jonka jälkeen muokattu tiedosto kopioidaan pakolla " +"palvelimelle." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Virheellinen aika annettu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Anna nollaa suurempi kokonaisluku." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Varoitusviestien vastaanottaja:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup-asiakas lähettää varoitusviestejä, kun varmuuskopioiden otossa " +"ilmenee ongelmia." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Anna joko paikallinen käyttäjätunnus (esimerkiksi ”root”) tai " +"sähköpostiosoite (esimerkiksi ”admin@example.org”)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Luodaanko salattu asiakasavain ja X.509-varmennepyyntö?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup-asiakas tarvitsee salatun RSA-avaimen ja sitä vastaavan X.509-" +"varmenteen tunnistautuakseen palvelimelle." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Molemmat voidaan luoda automaattisesti. Varmennepyyntö tulee lähettää " +"BoxBackup-palvelimen ylläpitäjälle, joka allekirjoittaa sen ja lähettää sen " +"takaisin palvelimen varmentaja-varmenteen kanssa." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Nämä tiedostot tulisi kopioida BoxBackupin asetustiedostoon. Käytettävät " +"tiedostonimet on annettu tiedostossa /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Tulisiko BoxBackupin asetukset tehdä automaattisesti?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"BoxBackup-palvelimen asetustiedostot voidaan luoda paketin " +"asetuskomentosarjoilla." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Valitse tämä, jos BoxBackupin asetusvalitsimet eivät ole tuttuja. Asetukset " +"voidaan tehdä käsin komentosarjoilla ”raidfile-config” ja ”bbstored-config”." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Palvelin ei käynnisty ennen kuin sen asetukset on tehty. On joka tapauksessa " +"suositeltavaa lukea tiedosto /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID-hakemistojen sijainti:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Anna kolmen RAID-tiedostohakemiston sijainnit." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAIDin ottaminen käyttöön vaatii, että annetut hakemistot ovat kolmen, eri " +"fyysisillä kovalevyillä sijaitsevan osion välilyönnein eroteltu lista. " +"(esimerkiksi: ”/raid/0.0 /raid/0.1 /raid/0.2”)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Jos RAIDia ei haluta käyttää, anna vain yhden hakemiston polku (esimerkiksi: " +"”/usr/local/lib/boxbackup”). Varmuuskopiot tallennetaan tänne." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Nämä hakemistot luodaan, jos niitä ei vielä ole olemassa." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Virheellisiä polkuja" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Esimerkiksi: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Userland RAID -järjestelmien lohkokoko:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup käyttää userland RAID -tekniikoita." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Valitse varastossa käytettävä lohkokoko. Paras tehokkuus saadaan, jos " +"lohkokoko on sama kuin alla olevassa tiedostojärjestelmässä (joka saadaan " +"ext2-tiedostojärjestelmässä näkyviin komennolla ”tune2fs -l”)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Tämä arvo tulisi asettaa, vaikka RAIDia ei aiottaisi käyttää." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Luodaanko salainen palvelinavain ja X.509-varmennepyyntö?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-palvelin tarvitsee salaisen RSA-avaimen ja sitä vastaavan X.509-" +"varmenteen asiakkaiden ja palvelimen väliseen tunnistautumiseen ja " +"viestinnän salaamiseen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Molemmat voidaan luoda automaattisesti. Varmenne tulee allekirjoittaa " +"juurivarmenteella (”root CA”, katso pakettia boxbackup-server). " +"Allekirjoitettu varmenne ja juurivarmenne tulee laittaa ohjelman " +"asetustiedostoon." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Virheellinen lohkokoko" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Lohkokoon tulee olla kahden potenssi (esim. 1024 tai 4096)." diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 00000000..6294c5aa --- /dev/null +++ b/po/fr.po @@ -0,0 +1,526 @@ +# Copyright (C) 2007, Vincent Bernat +# This file is distributed under the same license as the boxbackup package. +# +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup_0.10-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2007-06-26 07:42+0200\n" +"Last-Translator: Vincent Bernat \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Faut-il automatiquement configurer le client BoxBackup ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Les scripts de configuration de ce paquet peuvent créer les fichiers de " +"configuration pour le client BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Vous devriez utiliser cette option si les options de configuration de " +"BoxBackup ne vous sont pas familières." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Veuillez consulter le fichier /usr/share/doc/boxbackup-client/README.Debian " +"pour obtenir des détails sur la configuration du client BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Mode d'exécution pour le client BoxBackup :" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Le client BoxBackup gère deux modes de sauvegarde :" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Avec le mode simplifié (« lazy »), le démon de sauvegarde recherchera " +"régulièrement les fichiers modifiés sur le système. Il enverra les fichiers " +"plus anciens qu'un âge donné au serveur de sauvegarde." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Avec le mode instantané (« snapshot »), la sauvegarde sera lancée " +"explicitement à intervalles réguliers. Le fichier /etc/cron.d/boxbackup-" +"client pour le démon cron est fourni avec le paquet et devra être adapté à " +"vos besoins." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Numéro de compte pour ce nœud sur le serveur de sauvegarde :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"L'administrateur de BoxBackup doit assigner à ce client un numéro de compte " +"en hexadécimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Si aucun numéro de compte n'est encore assigné, laissez ce champ vide et " +"remplissez le plus tard en lançant la commande « dpkg-reconfigure boxbackup-" +"client » en tant qu'utilisateur root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Numéro de compte invalide" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Le numéro de compte doit être un nombre en hexadécimal (comme 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nom d'hôte complet (FQDN) du serveur de sauvegarde :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Veuillez indiquer le nom d'hôte complet (FQDN) du serveur BoxBackup que " +"votre client doit utiliser." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Le client se connectera sur le port TCP 2201 du serveur." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste des répertoires à sauvegarder :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Veuillez indiquer une liste de répertoires, séparés par des espaces, à " +"sauvegarder sur le serveur distant." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Ces répertoires ne doivent pas contenir de systèmes de fichiers montés dans " +"l'un de leurs sous-répertoires, quelle que soit la profondeur." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Chemin invalide" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Ces répertoires doivent être indiqués sous forme de chemins absolus et " +"séparés par des espaces." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Exemple : /home/myaccount /etc/." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalle (en secondes) entre deux parcours du répertoire :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup parcourt régulièrement les répertoires sélectionnés à la recherche " +"de fichiers modifiés." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Veuillez choisir l'intervalle entre deux parcours en secondes." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Temps minimum à attendre (en secondes) avant d'envoyer un fichier :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un fichier est envoyé au serveur uniquement après un certain temps après sa " +"date de dernière modification." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Un intervalle faible provoquera des envois fréquents vers le serveur et des " +"versions successives en grand nombre. Les versions plus anciennes seront " +"également supprimées plus tôt." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Délai maximum (en secondes) avant d'envoyer un fichier :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Des fichiers fréquemment modifiés risquent de ne jamais être envoyés si le " +"temps minimal avant envoi n'est jamais atteint." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Veuillez indiquer le délai maximum à attendre avant de forcer l'envoi d'un " +"fichier vers le serveur." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Délai non valable" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Veuillez indiquer un entier strictement positif." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinataire des notifications d'alertes :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Le client BoxBackup envoie des notifications d'alertes quand un problème " +"survient lors d'une sauvegarde." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Vous pouvez soit indiquer un identifiant local (par exemple « root ») ou une " +"adresse de courrier électronique (par exemple « admin@example.org »)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Faut-il créer la clef privée et la demande de certificat X.509 du client ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Le client BoxBackup a besoin d'une clef privée RSA et d'un certificat X.509 " +"correspondant pour s'authentifier auprès du serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Tous deux peuvent être créés automatiquement. Vous devrez envoyer la demande " +"de certificat à l'administrateur du serveur BoxBackup qui la signera et vous " +"la renverra accompagnée du certificat de l'autorité de certification (CA) du " +"serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Ces fichiers doivent être copiés dans le répertoire de configuration de " +"BoxBackup. Les noms de fichier à utiliser sont indiqués dans le fichier /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Faut-il automatiquement configurer BoxBackup ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Les scripts de configuration de ce paquet peuvent créer les fichiers de " +"configuration pour le serveur BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Vous devriez utiliser cette option si les options de configuration de " +"BoxBackup ne vous sont pas familières. Vous pouvez aussi configurer le " +"serveur vous-même à l'aide des scripts « raidfile-config » et « bbstored-" +"config »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Le serveur ne démarrera pas s'il n'est pas configuré. Dans tous les cas, la " +"lecture de /usr/share/doc/boxbackup-server/README.Debian est recommandée." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Emplacement des répertoires RAID :" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Veuillez choisir l'emplacement des trois répertoires RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Pour activer la gestion RAID, veuillez indiquer, séparés par des espaces, " +"les noms de trois partitions situées sur des disques physiques différents " +"(par exemple : « /raid/0.0 /raid/0.1 /raid/0.2 »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Si vous ne voulez pas activer la gestion du RAID, indiquez seulement le " +"chemin d'un répertoire où les sauvegardes seront stockées (par exemple, /usr/" +"local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Ces répertoires seront créés s'ils n'existent pas." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Chemins non valables" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Par exemple : /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Taille des blocs pour le système RAID en espace utilisateur :" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup utilise un système de RAID en espace utilisateur." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Veuillez choisir une taille de blocs pour le stockage. Pour une efficacité " +"maximale, vous devriez choisir la même taille de blocs que le système de " +"fichiers sous-jacent (que vous pouvez obtenir, pour les systèmes de fichiers " +"ext2 et ext3, avec la commande « tune2fs -l »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Cette valeur est nécessaire même si vous ne comptez pas utiliser le RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Faut-il créer une clef privée et une demande de certificat X.509 pour le " +"serveur ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Le serveur BoxBackup a besoin d'une clef privée RSA et du certificat X.509 " +"correspondant pour l'authentification et le chiffrement entre le client et " +"le serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ces deux éléments peuvent être créés automatiquement. Vous aurez besoin de " +"faire signer le certificat par votre autorité de certification principale " +"(voir le paquet boxbackup-server) et de placer le certificat signé " +"accompagné du certificat du CA racine dans le répertoire de configuration." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Taille de blocs non valable" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"La taille d'un bloc doit être une puissance de deux (par exemple 1024 ou " +"4096)." diff --git a/po/gl.po b/po/gl.po new file mode 100644 index 00000000..d981e9a6 --- /dev/null +++ b/po/gl.po @@ -0,0 +1,516 @@ +# Galician translation of boxbackup's debconf templates +# This file is distributed under the same license as the boxbackup package. +# Jacobo Tarrio , 2008. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-05-31 18:06+0100\n" +"Last-Translator: Jacobo Tarrio \n" +"Language-Team: Galician \n" +"Language: gl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "¿Quere configurar o cliente BoxBackup de xeito automático?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuración do paquete poden crear os ficheiros de " +"configuración do cliente BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Debería escoller esta opción se non está afeito ás opcións de configuración " +"de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Consulte o ficheiro /usr/share/doc/boxbackup-client/README.Debian para máis " +"información sobre a configuración do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execución do cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup soporta dous modos de copia de seguridade:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo \"preguiceiro\" (\"lazy\"), o servizo de copias de seguridade ha " +"explorar periodicamente o sistema de ficheiros á busca de ficheiros " +"modificados. Despois ha copiar ao servidor os ficheiros máis vellos dunha " +"determinada idade." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo \"imaxe\" (\"snapshot\"), a copia de seguridade hase facer a " +"intervalos regulares. Fornécese un ficheiro cron (/etc/cron.d/boxbackup-" +"client) co paquete, que se debe adaptar ás súas necesidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de conta para este nodo no servidor de copias de seguridade:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup debeulle asignar a este cliente un " +"número de conta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se non se asignou aínda ningún número de conta, deixe este campo baleiro e " +"configúreo máis adiante executando \"dpkg-reconfigure boxbackup-client\" " +"coma administrador." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta non válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta debe ser un número hexadecimal (coma 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome de dominio completo do servidor de copias de seguridade:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Introduza o nome de dominio completo do servidor BoxBackup que ha empregar o " +"cliente." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente hase conectar ao servidor no porto TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de directorios a copiar:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Introduza unha lista de nomes de directorios a copiar no servidor remoto, " +"separados por espazos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Eses directorios non deberían conter sistemas de ficheiros montados en " +"ningún nivel dos seus subdirectorios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ruta non válida" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"As rutas dos directorios deben ser rutas absolutas, separadas por espazos." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/conta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (en segundos) entre as exploracións dos directorios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup explora periodicamente os directorios seleccionados, á busca de " +"ficheiros modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Escolla o intervalo de exploración en segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo a agardar (en segundos) antes de copiar un ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Só se ha copiar un ficheiro ao servidor despois de que pase un certo tempo " +"trala súa última modificación." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Unha periodicidade curta de máis ha causar actualizacións frecuentes no " +"servidor e a creación de máis revisións, o que ha facer que se eliminen " +"antes as revisións antigas." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo a agardar (en segundos) antes de copiar un ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"É posible que os ficheiros que se modifican con frecuencia non se copien " +"nunca se nunca chegan ao tempo de espera mínimo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Introduza o tempo máximo a agardar antes de forzar a copia dun ficheiro " +"modificado ao servidor." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Introduciuse un tempo non válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Introduza un número enteiro maior que cero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario para os avisos das alertas:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envía alertas cando hai un problema durante a copia de " +"seguridade." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Introduza un nome de usuario local (por exemplo, \"root\") ou un enderezo de " +"email (por exemplo, \"admin@exemplo.org\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "¿Xerar a clave privada e a solicitude de certificado X.509 do cliente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa dunha clave privada RSA e o certificado X.509 " +"correspondente para se autenticar co servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Pódense xerar os dous automaticamente. Ha ter que enviar a solicitude de " +"certificado ao administrador do servidor BoxBackup, que a ha asinar e lla ha " +"enviar de volta co certificado de Autoridade Certificadora do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Debe copiar eses ficheiros ao directorio de configuración de BoxBackup. Os " +"nomes de ficheiro a empregar figuran no ficheiro /etc/boxbackup/bbackupd." +"conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "¿Quere configurar BoxBackup de xeito automático?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuración do paquete poden crear os ficheiros de " +"configuración do servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Debería escoller esta opción se non está afeito ás opcións de configuración " +"de BoxBackup. Pódese facer a configuración cos scripts \"raidfile-config\" e " +"\"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Non se ha iniciar o servidor se non está configurado. En tódolos casos, " +"recoméndase ler o ficheiro /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ubicación dos directorios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Indique a ubicación dos tres directorios de ficheiros RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para activar o RAID, os nomes dos directorios deben ser unha lista con tres " +"particións separadas por espazos, cada unha delas nun disco duro físico " +"diferente (por exemplo, \"/raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se non quere activar o RAID, indique a ruta a un só directorio no que " +"armacenar as copias de seguridade (por exemplo, /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Hanse crear eses directorios se non existen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Rutas non válidas" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamaño de bloque para o sistema de RAID de nivel de usuario:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup emprega técnicas de RAID de nivel de usuario." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Escolla o tamaño de bloque a empregar para o armacenamento. Para alcanzar a " +"máxima eficiencia, debería escoller o tamaño de bloque do sistema de " +"ficheiros (que se pode ver, para os sistemas de ficheiros ext2, coa orde " +"\"tune2fs -l\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Debería configurar este valor incluso se non quere empregar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"¿Xerar a clave privada e a solicitude de certificado X.509 do servidor?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa dunha clave privada RSA e o certificado X.509 " +"correspondente para realizar a autenticación do cliente e o cifrado das " +"comunicacións." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Pódense xerar os dous automaticamente. Ha ter que asinar o certificado coa " +"CA raíz (vexa o paquete boxbackup-server) e poñer este certificado asinado e " +"o certificado da CA raíz no directorio de configuración." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamaño de bloque non válido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"O tamaño de bloque debe ser unha potencia de 2 (por exemplo, 1024 ou 4096)." diff --git a/po/it.po b/po/it.po new file mode 100644 index 00000000..564d3ccb --- /dev/null +++ b/po/it.po @@ -0,0 +1,525 @@ +# Italian translation of boxbackup debconf messages. +# Copyright (C) 2012 +# This file is distributed under the same license as the boxbackup package. +# Beatrice Torracca , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11.1~r2837-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2012-01-30 15:51+0100\n" +"Last-Translator: Beatrice Torracca \n" +"Language-Team: Italiano \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1)\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Configurare automaticamente il client BoxBackup?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Gli script di configurazione del pacchetto possono creare i file di " +"configurazione per il client BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Scegliere questa opzione se non si ha familiarità con le opzioni di " +"configurazione di BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Per dettagli sulla configurazione del client BoxBackup, leggere /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modalità di esecuzione del client BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Il client BoxBackup gestisce due modalità di backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Nella modalità \"pigra\" (\"lazy\"), il demone di backup scansiona a " +"intervalli regolari il file system alla ricerca di file modificati. Carica " +"quindi i file più vecchi di una data specificata sul server di backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Nella modalità \"istantanea\" (\"snapshot\") il backup viene eseguito " +"esplicitamente ad intervalli regolari. Con il pacchetto viene fornito un " +"file cron (/etc/cron.d/boxbackup-client) che deve essere adattato alle " +"proprie necessità." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Numero di account per questo nodo sul server di backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"L'amministratore del server BoxBackup dovrebbe aver assegnato un numero di " +"account esadecimale a questo client." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se non è ancora stato assegnato alcun numero di account, lasciare questo " +"campo in bianco e configurarlo in seguito eseguendo \"dpkg-reconfigure " +"boxbackup-client\" come utente root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Numero di account non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Il numero di account deve essere un numero esadecimale (come 1F04 o 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome di dominio pienamente qualificato del server di backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Inserire il nome di dominio pienamente qualificato del server BoxBackup che " +"verrà usato dal client." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Il client si connetterà al server sulla porta TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista di directory di cui fare il backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Inserire una lista, separata da spazi, di directory di cui fare il backup " +"sul server remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Queste directory non devono contenere file system montati in nessun livello " +"delle loro sottodirectory." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome di percorso non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"I nomi di percorso delle directory devono essere nomi di percorso assoluti, " +"separati da spazi." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Ad esempio: /home/mioaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervallo (in secondi) tra le scansioni delle directory:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup scansiona a intervalli regolari le directory selezionate alla " +"ricerca dei file modificati." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Scegliere l'intervallo di scansione in secondi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo minimo di attesa (in secondi) prima di caricare un file:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un file viene caricato sul server solo dopo che è trascorso un certo tempo " +"dalla sua ultima modifica." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Valori di intervallo bassi fanno scattare caricamenti frequenti sul server, " +"creano più revisioni e fanno sì che le revisioni più vecchie vengano rimosse " +"prima." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo massimo di attesa (in secondi) prima di caricare un file:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"È possibile che i file modificati di frequente non vengano mai caricati se " +"non raggiungono mai il tempo minimo di attesa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Inserire il tempo massimo da raggiungere prima di forzare il caricamento di " +"un file modificato sul server." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tempo inserito non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Inserire un valore intero maggiore di zero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario delle notifiche di avvertimento:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Il client BoxBackup invia notifiche di avvertimento quando si verifica un " +"problema durante il backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Inserire un nome di utente locale (ad esempio \"root\") oppure un indirizzo " +"di posta elettronica (ad esempio \"admin@example.com\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Generare la richiesta di chiave privata e di certificato X.509 del client?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Il client BoxBackup necessita di una chiave RSA privata e del certificato " +"X.509 corrispondente per autenticarsi sul server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Entrambi possono essere generati automaticamente. È necessario inviare la " +"richiesta di certificato all'amministratore del server BoxBackup che la " +"firmerà e invierà indietro insieme al certificato Certification Authority " +"del server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Questi file devono essere copiati nella directory di configurazione di " +"BoxBackup. I nomi di file da usare sono indicati nel file /etc/boxbackup/" +"bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Configurare BoxBackup automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Gli script di configurazione possono creare i file di configurazione per il " +"server BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Scegliere questa opzione se non si ha familiarità con le opzioni di " +"configurazione di BoxBackup. La configurazione può essere fatta in modo " +"manuale con gli script \"raidfile-config\" e \"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Il server non si avvia se non è configurato. In ogni caso è raccomandata la " +"lettura di /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Posizione delle directory RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Scegliere la posizione per le tre directory dei file RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Per abilitare RAID, i nomi di directory devono essere una lista separata da " +"spazi di tre partizioni, ciascuna su un'unità fisica differente (ad esempio: " +"\"/raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se non si desidera abilitare RAID, specificare semplicemente il percorso di " +"una directory in cui verranno memorizzati i backup (ad esempio, /usr/local/" +"lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Se non esistono, queste directory verranno create." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomi di percorso non validi" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Ad esempio: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Dimensione dei blocchi per il sistema RAID in spazio utente:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa tecniche RAID in spazio utente." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Scegliere la dimensione dei blocchi da usare per la memorizzazione. Per una " +"massima efficienza, scegliere la dimensione dei blocchi del file system " +"sottostante (che, per i file system ext2, può essere visualizzata con il " +"comando \"tune2fs -l\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Questo valore andrebbe impostato anche se non si ha intenzione di usare RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Generare una richiesta di chiave privata e certificato X.509 per il server?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Il server BoxBackup necessita di una chiave RSA privata e del certificato " +"X.509 corrispondente per effettuare l'autenticazione client-server e la " +"cifratura delle comunicazioni." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Entrambi possono essere generati automaticamente. È necessario firmare il " +"certificato con la CA di root (vedere il pacchetto boxbackup-server) e " +"mettere il certificato firmato e il certificato CA di root nella directory " +"di configurazione." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Dimensione dei blocchi non valida" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"La dimensione dei blocchi deve essere una potenza del due (es., 1024 o 4096)." diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 00000000..872b8d47 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,509 @@ +# Copyright (C) 2009 Reinhard Tartler +# This file is distributed under the same license as boxbackup package. +# Hideki Yamane (Debian-JP) , 2009. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc3~r2502-2\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-11-10 14:26+0900\n" +"Last-Translator: Hideki Yamane (Debian-JP) \n" +"Language-Team: Japanese \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "BoxBackup クライアントを自動的に設定しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"パッケージの設定スクリプトは BoxBackup クライアント用の設定ファイルを作成でき" +"ます。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"あなたが BoxBackup の設定オプションに詳しくない場合は、このオプションを選んで" +"ください。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"BoxBackup クライアントの設定の詳細については /usr/share/doc/boxbackup-client/" +"README.Debian を参照してください。" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup クライアントの動作モード:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup クライアントは 2 つのバックアップモードをサポートしています:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"'lazy' モードでは、バックアップのデーモンは変更されたファイルを探すため、定期" +"的にファイルシステムをスキャンします。そして、指定された期間よりも古いファイ" +"ルをバックアップサーバへアップロードします。" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"'snapshot' モードは、バックアップは明確に定期的な間隔で動作します。パッケージ" +"で cron ファイル (/etc/cron.d/boxbackup-client) が用意され、このファイルが用" +"途に合うはずです。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "バックアップサーバ上でのこのノードのアカウント番号:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"BoxBackup サーバの管理者は、このクライアントを 16 進数のアカウント番号で割り" +"当てる必要があります。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"アカウント番号がまだ割り当てられていない場合は、この欄を空白のままにして後ほ" +"ど root ユーザにて 'dpkg-reconfigure boxbackup-client' を実行して設定をしてく" +"ださい。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "アカウント番号が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "アカウント番号は (1F04 や 4500 などの) 16 進数である必要があります。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "バックアップサーバの完全修飾ドメイン名 (FQDN):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"クライアントが利用する BoxBackup サーバの完全修飾ドメイン名 (FQDN) を入力して" +"ください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "クライアントはサーバの TCP ポート 2201 に接続します。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "バックアップするディレクトリの一覧:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"リモートのサーバにバックアップされる、空白で区切ったディレクトリの一覧を指定" +"してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"このディレクトリには、サブディレクトリ以下にマウント済みのファイルシステムを" +"含んではいけません。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "パス名 (path) が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"ディレクトリへのパス名は絶対パスで、空白で区切られている必要があります。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "例: /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "ディレクトリをスキャンする間隔 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup は変更されたファイルを探すため、一定間隔で指定されたディレクトリを" +"スキャンします。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "スキャンの間隔を秒数で指定してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "ファイルをアップロードするまでの最小待ち時間 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"最後に変更されてから一定時間が経過しないとファイルはアップロードされません。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"短い間隔にすると、サーバへ頻繁にアップロードすることになり、作成されるリビ" +"ジョンが多くなって、古いリビジョンが早く削除されます。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "ファイルをアップロードするまでの最大待ち時間 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"頻繁に変更されるファイルが最小待ち時間にも達しない場合、このファイルは全く" +"アップロードされないでしょう。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"変更されたファイルがサーバへ強制的にアップロードされるまでの最大時間を入力し" +"てください。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "入力された時間が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "自然数を入力してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "警告通知の受信者:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup クライアントは、バックアップ中に問題が起きた際に警告の通知を送信し" +"ます。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"ローカルユーザの名前 (例えば 'root') かメールアドレス (例 'admin@example." +"org') を入力してください。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "クライアント秘密鍵と X.509 証明書のリクエストを生成しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup クライアントは、サーバに認証を受ける際に RSA 秘密鍵と対応する " +"X.509 証明書を必要とします。" + +# FIXME: もっとわかりやすい表現 +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"双方とも自動的に生成できます。証明書リクエストを BoxBackup サーバの管理者に送" +"信する必要があります。BoxBackup サーバの管理者は、サーバの認証局の認証に基づ" +"いて証明書リクエストに署名して返信します。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"このファイルは BoxBackup の設定ディレクトリにコピーされる必要があります。ファ" +"イル名は /etc/boxbackup/bbackupd.conf ファイルで指定します。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup を自動的に設定しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"パッケージの設定スクリプトで BoxBackup サーバの設定ファイルを生成できます。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"BoxBackup の設定オプションに詳しくない場合はこのオプションを選んでください。" +"設定は、手動の場合は 'raidfile-config' や'bbstored-config' スクリプトで設定で" +"きます。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"サーバは設定されていないと起動しません。どのような場合でも、/usr/share/doc/" +"boxbackup-server/README.Debian を一読いただくのをお勧めします。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID ディレクトリの位置:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "RAID ファイルが置かれる 3 つのディレクトリを指定してください。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAID を有効にするには、ディレクトリ名は 3 つのパーティションのリストを空白で" +"区切ったもので、それぞれが別の物理ドライブ上にある必要があります (例: '/" +"raid/0.0 /raid/0.1 /raid/0.2')。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"RAID を有効にしたくない場合は、バックアップが保存される単一ディレクトリへのパ" +"ス (path) を指定してください (例えば、/usr/local/lib/boxbackup)。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "このディレクトリが存在しない場合、作成されます。" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "パス名が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "例: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "ユーザランド RAID システムのブロックサイズ:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup はユーザランドの RAID 機能を使います。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"ストレージに使うブロックサイズを選んでください。効率を最大限にするには、ファ" +"イルシステムのブロックサイズに合わせて選ぶ必要があります (ext2 ファイルシステ" +"ムは 'tune2fs -l' コマンドで表示できます)。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "RAID を使う予定が無いとしても、この値は設定する必要があります。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "サーバ秘密鍵と X.509 証明書リクエストを生成しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup サーバは、クライアントとサーバ間の認証と通信暗号化を行うために RSA " +"秘密鍵と対応する X.509 証明書を必要とします。" + +# FIXME もっとわかりやすい表現 +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"双方とも自動的に生成ができます。ルート認証局 (root CA) での証明書への署名 " +"(boxbackup-server パッケージを参照) して、署名した証明書と CA 証明書を設定" +"フォルダに配置する必要があります。" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "ブロックサイズが正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "ブロックサイズは 2 の乗数である必要があります (例: 1024、4096)。" diff --git a/po/nl.po b/po/nl.po new file mode 100644 index 00000000..0ef91ab4 --- /dev/null +++ b/po/nl.po @@ -0,0 +1,528 @@ +# Dutch translation of boxbackup debconf templates. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Frans Spiesschaert , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2014-10-10 15:14+0200\n" +"Last-Translator: Frans Spiesschaert \n" +"Language-Team: Debian Dutch l10n Team \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Moet de BoxBackup-client automatisch ingesteld worden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"De configuratiescripts van het pakket kunnen de configuratiebestanden voor " +"de BoxBackup-client aanmaken." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"U zou deze optie moeten kiezen indien u niet vertrouwd bent met de " +"configuratieopties van BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Gelieve het bestand /usr/share/doc/boxbackup-client/README.Debian te " +"raadplegen voor details over het configureren van de BoxBackup-client." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modus voor het uitvoeren van de BoxBackup-client:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "De BoxBackup-client ondersteunt twee back-upmodussen:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"In de 'luie' modus zal de back-upachtergronddienst regelmatig het " +"bestandssysteem overlopen op zoek naar gewijzigde bestanden. Daarna zal het " +"bestanden ouder dan een ingestelde ouderdom naar de server uploaden." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"In de 'momentopname'-modus zal expliciet een back-up gemaakt worden met " +"vastgelegde intervallen. Met het pakket wordt een cron-bestand (/etc/cron.d/" +"boxbackup-client) meegeleverd dat volgens uw behoeften aangepast zou moeten " +"worden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Accountnummer voor deze machine op de back-upserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"De beheerder van de BoxBackup-server zou deze client een hexadecimaal " +"accountnummer moeten gegeven hebben." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Indien nog geen accountnummer toegekend werd, laat u dit veld leeg en " +"configureert u het later door als systeembeheerder het commando 'dpkg-" +"reconfigure boxbackup-client' uit te voeren." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ongeldig accountnummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Het accountnummer moet een hexadecimaal getal zijn (zoals 1F04 of 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "" +"Volledige domeinnaam (fully qualified domain name) van de back-upserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Voer de volledige domeinnaam in van de BoxBackup-server waarvan uw client " +"gebruik zal maken." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "De client zal met de server contact maken op de TCP-poort 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lijst van mappen waarvoor een back-up gemaakt moet worden:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Geef een met komma's gescheiden lijst op van mappen waarvoor een reservekopie " +"moet gemaakt worden op de externe server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Deze mappen zouden op geen enkel niveau in de onderliggende mappen " +"aangekoppelde bestandssystemen mogen bevatten." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ongeldig pad" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"De namen van de mappen moeten het absolute pad er naartoe bevatten en ze " +"moeten met spaties van elkaar gescheiden worden." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Bijvoorbeeld: /home/mijn_account /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (in seconden) tussen het doorlopen van de mappen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Met tussenpozen doorloopt BoxBackup de opgegeven mappen op zoek naar " +"gewijzigde bestanden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Gelieve het doorloopinterval te kiezen, uitgedrukt in seconden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimale wachttijd (in seconden) voor het uploaden van een bestand:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Slechts een bepaalde tijd na de laatste wijziging aan een bestand wordt het " +"naar de server geüpload." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lage inervalwaarden zullen frequente uploads naar de server en het aanmaken " +"van meer revisies tot gevolg hebben en meteen ook het vroeger wissen van " +"oudere revisies." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximale wachttijd (in seconden) vooraleer een bestand geüpload wordt:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"De kans bestaat dat bestanden die frequent bijgewerkt worden nooit geüpload " +"raken omdat ze nooit de minimale wachttijd halen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Gelieve aan te geven hoelang maximaal gewacht mag worden vooraleer het " +"uploaden van een gewijzigd bestand naar de server afgedwongen wordt." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "De opgegeven tijd is ongeldig" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Gelieve een geheel getal in te geven groter dan nul." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Ontvanger van waarschuwingen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"De BoxBackup-client stuurt een waarschuwing wanneer er zich een probleem " +"voordoet tijdens de back-up." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Geef ofwel de naam van een lokale gebruiker op (bijvoorbeeld 'root') of een " +"e-mailadres (bijvoorbeeld 'admin@voorbeeld.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Een geheime sleutel en een verzoek om een X.509-certificaat aanmaken?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"De BoxBackup-client heeft een geheime RSA-sleutel en een overeenkomstig " +"X.509-certificaat nodig om zich bij de server te authenticeren." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Beide kunnen automatisch aangemaakt worden. U zult het verzoek om een " +"certificaat moeten sturen naar de beheerder van de BoxBackup-server, die het " +"zal ondertekenen en naar u terugsturen samen met het certificaat van de " +"Certificatie-Autoriteit van de server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Deze bestanden moeten gekopieerd worden naar de map die de configuratie van " +"BoxBackup bevat. De te gebruiken bestandsnamen staan vermeld in het bestand /" +"etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Moet BoxBackup automatisch ingesteld worden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"De configuratiescripts van het pakket kunnen de configuratiebestanden voor " +"de BoxBackup-server aanmaken." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"U zou deze optie moeten kiezen indien u niet vertrouwd bent met de " +"configuratieopties voor BoxBackup. De configuratie kan manueel gebeuren met " +"behulp van de scripts 'raidfile-config' and 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"De server zal niet opstarten als hij niet geconfigureerd is. Hoe dan ook " +"wordt aanbevolen om /usr/share/doc/boxbackup-server/README.Debian te " +"raadplegen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Plaats van de RAID-mappen:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Gelieve de plaats op te geven van de drie RAID bestandsmappen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Om RAID te gebruiken moeten de namen van de mappen ingegeven worden als een " +"door komma's gescheiden lijst van drie partities, die zich elk op een andere " +"fysieke harde schijf bevinden (bijvoorbeeld: '/raid/0.0 /raid/0.1 /" +"raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Indien u RAID niet wilt gebruiken, geeft u enkel het pad op naar een map " +"waarin de reservekopieën bewaard zullen worden (bijvoorbeeld /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Deze mappen zullen aangemaakt worden als ze nog niet bestaan." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ongeldige padnamen" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Bijvoorbeeld: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blok-grootte voor het RAID-systeem in gebruikersmodus:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup maakt gebruik van RAID-technieken in gebruikersmodus." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Gelieve de blok-grootte voor de opslag te kiezen. Voor maximale efficiëntie, " +"zou u de blok-grootte van het onderliggende bestandssysteem moeten kiezen " +"(voor het ext2-bestandssysteem kunt u dat te zien krijgen met het commando " +"'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Deze waarde moet ook ingesteld worden als u niet zinnens bent RAID te " +"gebruiken." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Een geheime sleutel en een verzoek om een X.509-certificaat aanmaken voor de " +"server?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"De BoxBackup-server heeft een geheime RSA-sleutel en een overeenkomstig " +"X.509-certificaat nodig voor de client-server-authenticatie en de " +"versleuteling van de communicatie." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Beide kunnen automatisch aangemaakt worden. U zult het certificaat moeten " +"ondertekenen met uw systeembeheerders-CA (zie het boxbackup-serverpakket) en " +"dit ondertekend certificaat samen met het CA-certificaat van de " +"systeembeheerder in de configuratiemap plaatsen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ongeldige blok-grootte" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "De blok-grootte moet een macht van twee zijn (bijv. 1024 of 4096)." diff --git a/po/pt.po b/po/pt.po new file mode 100644 index 00000000..2bcb0a08 --- /dev/null +++ b/po/pt.po @@ -0,0 +1,519 @@ +# pt translation of boxbackup. +# Copyright (C) 2008 THE BOXBACKUP'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Bruno Queirós , 2008. +# +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-05-31 20:05+0100\n" +"Last-Translator: Bruno Queirós \n" +"Language-Team: Portuguese \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "O cliente BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuração do pacote podem criar os ficheiros de " +"configuração para o cliente do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Deve escolher esta opção se não está familiarizado com as opções de " +"configuração do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Por favor leia /usr/share/doc/boxbackup-client/README.Debian para detalhes " +"acerca da configuração do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execução para o cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup suporta dois modos de backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo 'lazy', o deamon de backup irá pesquisar regularmente o sistema de " +"ficheiros à procura de ficheiros modificados. De seguida envia os ficheiros " +"que sejam mais antigos que uma data especificada para o servidor de backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo 'snapshot' o backup será executado explicitamente em intervalos " +"regulares. Um ficheiro cron (/etc/cron.d/boxbackup-client) é fornecido " +"juntamente com o pacote e deve ser adaptado às suas necessidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de conta para este nó no servidor de backups:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup deve ter atribuído a este cliente um " +"número de conta em hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se ainda não foi atribuído nenhum número de conta, deixe este campo em " +"branco e configure-o mais tarde executando 'dpkg-reconfigure boxbackup-" +"client' como root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta deve ser um número hexadecimal (como 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome completo do domínio do servidor de backups:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Por favor introduza o nome completo do domínio do sevidor BoxBackup que o " +"seu cliente irá utilizar." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente irá ligar-se o servidor por TCP na porta 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista dos directórios para backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Por favor forneça uma lista de directórios separados por um espaço para " +"serem copiados para o servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esses directórios não devem conter sistemas de ficheiros montados em nenhum " +"nível dos seus subdirectórios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome de localização inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Os nomes das localizações têm que ser nomes de localização absolutos, " +"separados por espaços." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/minhaconta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (em segundos) entre pesquisas de directórios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"O BoxBackup pesquisa regularmente os directórios seleccionados, à procura de " +"ficheiros modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Por favor escolha o intervalo de pesquisa em segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo de espera (em segundos) antes de carregar um ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Um ficheiro será carregado para o servidor apenas após ter passado um certo " +"tempo depois da sua última modificação." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Baixos valores de intervalo irão activar carregamentos frequentes para o " +"servidor e serão criadas mais revisões e as revisões mais antigas são " +"removidas mais cedo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo de espera (em segundos) antes de carregar um ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Ficheiros que são frequentemente modificados raramente são carregados se " +"eles não atingirem o mínimo tempo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Por favor introduza o tempo máximo a atingir antes que o carregamento de um " +"ficheiro modificado para o servidor seja forçado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Introduzido tempo inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Por favor introduza um valor inteiro não nulo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatário para as notificações de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envia notificações de alerta quando ocorre um problema " +"durante o backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Por favor introduza ou um nome de utilizador local (por exemplo 'root') ou " +"um endereço de email (por exemplo 'admin@example.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do cliente e o pedido de certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa de uma chave privada RSA e o correspondente " +"certificado X.509 para se autenticar com o servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos podem ser gerados automaticamente. Você precisa de enviar o pedido de " +"certificado para o administrador do servidor BoxBackup que o irá assinar e " +"enviá-lo de volta para si juntamente com o certificado 'Certification " +"Authority' (CA) do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Estes ficheiros devem ser copiados para o directório de configuração do " +"BoxBackup. Os nomes de ficheiros a utilizar são dados no ficheiro /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "O BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuração do pacote podem criar os ficheiros de " +"configuração para o servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Deve escolher esta opção se não estiveir familiarizado com as opções de " +"configuração do BoxBackup. A configuração pode ser feita manualmente com os " +"scripts 'raidfile-config' e 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"O servidor não irá funcionar se não estiver configurado. Em todos os casos, " +"érecomendado ler o /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Localização dos directórios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" +"Por favor escolha a localização dos três directórios de ficheiros RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para activar o RAID, os nomes dos directórios devem ser uma lista separada " +"por um espaço de três particções, cada uma em discos rígidos diferentes (por " +"exemplo: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se não deseja activar o RAID, simplesmente especifique uma localização para " +"um directório onde os backups serão armazenados ( por exemplo, /usr/local/" +"lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Estes directórios devem ser criados caso não existam." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomes de localização inválidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamanho do bloco para a userland do sistema RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa técnicas userland RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Por favor escolha o tamanho do bloco para utilizar no armazenamento. Para " +"uma eficiência máxima, deverá escolher o tamanho do bloco do sistema de " +"ficheiros que utiliza (o qual pode ser visualizado com o comando 'tune2fs -" +"l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Este valor deve ser introduzido mesmo que não tencione usar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do servidor e o pedido de certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa de uma chave privada RSA e o correspondente " +"certificado X.509 para executar autenticações cliente-servidor e encriptação " +"de comunicação." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos podem ser gerados automaticamente. Precisará de assinar o certificado " +"com o seu root CA (veja o pacote boxbackup-server) e ponha este certificado " +"assinado e o certificado root CA na pasta de configuração." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamanho de bloco inválido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "O tamanho do bloco deve ser uma potência de 2 (e.g. 1024 ou 4096)." diff --git a/po/pt_BR.po b/po/pt_BR.po new file mode 100644 index 00000000..1a57bb70 --- /dev/null +++ b/po/pt_BR.po @@ -0,0 +1,522 @@ +# Debconf translations for boxbackup. +# Copyright (C) 2010 THE boxbackup'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Bruno Gurgel , 2010. +# Adriano Rafael Gomes , 2010, 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc8~r2714-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2008-04-21 12:53+0200\n" +"PO-Revision-Date: 2011-01-18 09:59-0200\n" +"Last-Translator: Adriano Rafael Gomes \n" +"Language-Team: Brazilian Portuguese \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"pt_BR utf-8\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "O cliente BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuração do pacote podem criar os arquivos de configuração " +"para o cliente BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Você deve escolher essa opção se você não estiver familiarizado com as " +"opções de configuração do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Por favor, leia /usr/share/doc/boxbackup-client/README.Debian para detalhes " +"sobre a configuração do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execução para o cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup suporta dois modos de backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo 'lazy', o daemon do backup verificará regularmente o sistema de " +"arquivos procurando por arquivos modificados. Ele então fará o carregamento " +"(\"upload\") dos arquivos mais velhos que uma idade especificada para o " +"servidor de backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo 'snapshot', o backup será explicitamente executado em intervalos " +"regulares. Um arquivo do cron (/etc/cron.d/boxbackup-client) é fornecido com " +"o pacote e deve ser adaptado para atender as suas necessidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número da conta para esse nó no servidor de backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup deve ter designado a esse cliente um " +"número de conta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se nenhum número de conta foi designado ainda, deixe esse campo em branco e " +"configure-o depois executando 'dpkg-reconfigure boxbackup-client' como root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta deve ser um número hexadecimal (como 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome de domínio totalmente qualificado do servidor de backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Por favor, informe o nome de domínio totalmente qualificado do servidor " +"BoxBackup que seu cliente usará." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente conectará no servidor na porta 2201 TCP." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de diretórios para fazer backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Por favor, informe uma lista separada por espaços de diretórios para fazer " +"backup no servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esses diretórios não devem conter sistemas de arquivos montados em nenhum " +"nível em seus subdiretórios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome de caminho inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Os nomes de caminho para os diretórios precisam ser nomes de caminho " +"absolutos, separados por espaços." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/minhaconta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (em segundos) entre as verificações dos diretórios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"O BoxBackup regularmente verifica os diretórios selecionados, procurando por " +"arquivos modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Por favor, escolha o intervalo de verificação em segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo de espera (em segundos) antes de carregar um arquivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Um arquivo será carregado para o servidor somente depois de um certo tempo " +"após sua última modificação." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Valores baixos de intervalo desencadearão carregamentos frequentes para o " +"servidor e mais revisões serão criadas, com as antigas revisões sendo " +"removidas mais cedo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo para esperar (em segundos) antes de carregar um arquivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Arquivos frequentemente modificados são suscetíveis a nunca serem carregados " +"caso nunca atinjam o tempo mínimo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Por favor, informe o tempo máximo para atingir antes que o carregamento para " +"o servidor de um arquivo modificado seja forçado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tempo inválido informado" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Por favor, informe um valor inteiro maior que zero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatário para as notificações de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envia notificações de alerta quando ocorre um problema " +"durante o backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Por favor, informe um nome de usuário local (por exemplo 'root') ou um " +"endereço de e-mail (por exemplo 'admin@exemple.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Gerar a chave privada do cliente e a requisição do certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa de uma chave privada RSA e o certificado X.509 " +"correspondente para se autenticar com o servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos podem ser gerados automaticamente. Você precisará enviar a requisição " +"do certificado para o administrador do servidor BoxBackup que irá assiná-lo " +"e enviá-lo de volta para você, juntamente com o certificado da Autoridade " +"Certificadora do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Esses arquivos devem ser copiados dentro do diretório de configuração do " +"BoxBackup. Os nomes de arquivo a serem usados são dados no arquivo /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "O BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuração do pacote podem criar os arquivos de configuração " +"para o servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Você deve escolher essa opção se você não estiver familiarizado com as " +"opções de configuração do BoxBackup. A configuração pode ser feita " +"manualmente com os scripts 'raidfile-config' e 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"O servidor não inicializará se não for configurado. Em todos os casos, ler /" +"usr/share/doc/boxbackup-server/README.Debian é recomendado." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Localização dos diretórios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" +"Por favor, escolha a localização para os três diretórios de arquivos RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para habilitar o RAID, os nomes dos diretórios devem ser uma lista separada " +"por espaços de três partições, cada uma em diferentes discos físicos (por " +"exemplo: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se você não deseja habilitar o RAID, apenas especifique o caminho para um " +"diretório onde os backups serão armazenados (por exemplo, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Esses diretórios serão criados se não existirem." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomes de caminho inválidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "" +"Tamanho do bloco para o sistema de RAID em espaço de usuário (\"userland\"):" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa técnicas de RAID em espaço de usuário." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Por favor, escolha o tamanho do bloco a ser usado para o armazenamento. Para " +"eficiência máxima, você deve escolher o tamanho de bloco do sistema de " +"arquivos subjacente (que pode ser mostrado para sistemas de arquivo ext2 com " +"o comando 'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Esse valor deve ser definido mesmo se você não planeja usar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do servidor e o pedido do certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa de uma chave privada RSA e do certificado X.509 " +"correspondente para realizar a autenticação cliente-servidor e a " +"criptografia da comunicação." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos podem ser gerados automaticamente. Você terá que assinar o certificado " +"com a sua CA raiz (veja o pacote boxbackup-server) e colocar este certificado " +"assinado e o certificado da CA raiz na pasta de configuração." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamanho de bloco inválido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "O tamanho do bloco deve ser uma potência de dois (ex. 1024 ou 4096)." diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 00000000..f0468989 --- /dev/null +++ b/po/ru.po @@ -0,0 +1,519 @@ +# translation of ru.po to Russian +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Yuri Kozlov , 2008. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup new\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-06 21:51+0400\n" +"Last-Translator: Yuri Kozlov \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Настроить клиента BoxBackup автоматически?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Сценарии настройки пакета могут создать файлы настройки клиента BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Вы должны ответить утвердительно, если не знакомы параметрами настройки " +"BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Подробное описание по настройке клиента BoxBackup дано в файле /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Режим запуска клиента BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Клиент BoxBackup поддерживает два режима резервного копирования:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"В режиме 'lazy' служба резервного копирования регулярно сканирует файловую " +"систему, ища изменившиеся файлы. Затем она закачивает файлы на сервер, если " +"они новее, чем на сервере (срок можно настроить)." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"В режиме 'snapshot' резервное копирование выполняется через одинаковые " +"интервалы. В пакете есть файл задания cron (/etc/cron.d/boxbackup-client), " +"который должен быть изменён под ваши нужды." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Учётный номер этого узла на сервере резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Администратор сервера BoxBackup должен назначить этому клиенту " +"шестнадцатеричный учётный номер." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Если учётный номер ещё не назначен, оставьте это поле пустым, и настройте " +"его позже, запустив команду 'dpkg-reconfigure boxbackup-client' с правами " +"суперпользователя." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Неверный учётный номер" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Учётный номер должен указываться в виде шестнадцатеричного числа (например, " +"1F04 или 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Полностью определённое доменное имя сервера резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Введите полностью определённое доменное имя сервера BoxBackup, который будет " +"использовать клиент." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Клиент будет подключаться к серверу по TCP-порту 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Список каталогов для резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Задайте через пробел список каталогов, для которых будет выполняться " +"резервное копирование на удалённый сервер." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Эти каталоги не должны содержать смонтированных файловых систем в своих " +"подкаталогах." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Неверный путь" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Пути к каталогам должны указываться в виде абсолютных путей и разделяться " +"пробелами." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Пример: /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Интервал (в секундах) между сканированиями:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup регулярно сканирует указанные каталоги, ища изменившиеся файлы." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Введите промежуток между сканированиями в секундах." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Минимальное время ожидания (в секундах) перед закачкой файла:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Файл будет закачан на сервер только по прошествии определённого времени с " +"момента его последнего изменения." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Короткий интервал приводит к частым закачкам на сервер и созданию большого " +"числа версий и скорому удалению предыдущих версий." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Максимальное время ожидания (в секундах) перед закачкой файла:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Часто изменяемые файлы, скорее всего, никогда не будут закачаны, так как для " +"них никогда не наступит конец минимального интервала ожидания." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Введите максимальный интервал, после которого изменённый файл должен быть " +"принудительно закачан на сервер." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Указано неправильное время" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Введите целое число больше нуля." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Получатель уведомлений о сбоях:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Клиент BoxBackup посылает уведомления о сбоях, если возникает проблема при " +"выполнении резервного копирования." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Введите имя локального пользователя (например, 'root') или адрес электронной " +"почты (например, 'admin@example.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Генерировать клиентский секретный ключ и запрос сертификата X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Для аутентификации клиента BoxBackup на сервере требуется секретный ключ RSA " +"и соответствующий сертификат X.509." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Они могут быть сгенерированы автоматически. Вам нужно отправить запрос " +"сертификата администратору сервера BoxBackup, который его подпишет и " +"отправит вам обратно вместе сертификатом сервера от удостоверяющего центра." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Эти файлы нужно скопировать в каталог настройки BoxBackup. Имена файлов " +"задаются в файле /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Настроить BoxBackup автоматически?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Сценарии настройки пакета могут создать файлы настройки сервера BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Вы должны ответить утвердительно, если не знакомы параметрами настройки " +"BoxBackup. Настройка может быть выполнена вручную с помощью сценариев " +"'raidfile-config' и 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Сервер не запустится без настройки. В любом случае, прочитайте файл /usr/" +"share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Расположение каталогов RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Введите расположение для трёх каталогов RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Чтобы включить RAID, нужно указать имена каталогов трёх разделов через " +"пробел, каждый на отдельном физическом жёстком диске (например: '/raid/0.0 /" +"raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Если вы не хотите использовать RAID, просто укажите путь к одному каталогу, " +"где будут сохраняться резервные копии (например, /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Если каталоги не существуют, то они будут созданы." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Неверные имена каталогов" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Например: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Размер блока пользовательской системы RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "" +"В BoxBackup используются технологии RAID, работающие в пользовательском " +"окружении." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Введите размер блока хранилища. Для максимальной эффективности, вам нужно " +"указать размер блока файловой системы, на которой расположено хранилище (его " +"можно узнать, например, для файловой системы ext2, с помощью команды " +"'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Это значение должно быть задано, даже если вы не планируете использовать " +"RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Генерировать секретный ключ сервера и запрос сертификата X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Для аутентификации клиентов и шифрования передачи серверу BoxBackup " +"требуется секретный ключ RSA и соответствующий сертификат X.509." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Они могут быть сгенерированы автоматически. Вам нужно подписать сертификат в " +"корневом CA (смотрите пакет boxbackup-server) и положить этот подписанный " +"сертификат и корневой сертификат CA в каталог с настройками." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Неверный размер блока" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"Размер блока должен быть кратен степени двойки (например, 1024 или 4096)." diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 00000000..3d797880 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,515 @@ +# translation of boxbackup.po to swedish +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# +# Martin Bagge , 2009. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-02-01 04:14+0100\n" +"Last-Translator: Martin Bagge \n" +"Language-Team: swedish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Ska inställningarna för BoxBackup-klienten skapas automatiskt?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Paketet kan automatiskt skapa inställningsfiler för Boxbackup-klienten." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Du bör välja detta alternativ om du inte är bekant med BoxBackups " +"inställningar sedan tidiare." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Läs även i /usr/share/doc/boxbackup-client/README.Debian för närmare " +"information om inställningsalternativen för BoxBackup-klienten." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Körläge för BoxBackup-klienten:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup-klienten har stöd för två olika lägen för säkerhetskopior:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"I \"lazy\"-mode söker tjänsten för säkerhetskopiering regelbundet av " +"dilsystemet för uppdaterade filer. Den kommer sedan att kopiera filer äldre " +"än ett visst tröskelvärde till servern med säkerhetskopia." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"I läget \"snapshot\" kommer säkerhetskopiorna att utföras enligt ett " +"rullande schema. Ett cronjobb kommer att installeras (/etc/cron.d/baxbackup-" +"client) med hjälp av paketet och kan ändras för att passa dina behov." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontonummer för den här noden på servern för säkerhetskopior:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Administratören för BoxBackup-servern ska ha givit denna klient ett " +"hexadecimalt-kontonummer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Om inget kontonummer har tilldelats ännu kan du lämna fältet blankt och " +"ställa in det senare genom att köra \"dpkg-reconfigure boxbackup-client\" " +"som root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ogiltigt kontonummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Kontonummret måste vara ett hexadecimaltnummer (ex. 1F04 eller 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Komplett domännamn för servern med säkerhetskopior:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Ange det kompletta domännamnet för BoxBackup-servern som din klient ska " +"använda." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klienten kommer att ansluta till servern på TCP-port 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista med kataloger som ska säkerhetskopieras:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Ange en lista separerad av mellanslag med kataloger som ska " +"säkerhetskopieras till fjärrservern." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Dessa kataloger får inte innehålla några monterade filsystem i någon nivå i " +"deras underkataloger." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ogiltig sökväg" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Sökvägarna till katalogerna måste vara absoluta separerade med mellanslag." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Exempelvis: /home/myaccount/etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (i sekunder) mellan katalogsökningar:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup söker regelbundet av valda kataloger och letar efter uppdaterade " +"filer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Ange avsökningsintervallet i sekunder." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" +"Minimal tid att vänta (i sekunder) innan en fil fkopieras till servern:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"En fil kommer att kopieras till servern så länge minst ett visst " +"tidsintervall har förflutit sedan förra förändringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Ett lågt intervall kommer att innebära frekventare kopieringar till servern " +"och fler revisioner kommer att skapas och därmed kommer äldre revisioner att " +"tas bort snabbare." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximal tid att vänta (i sekunder) innan en fil kopieras till servern:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Frekvent modifierade filer kan undgå att kopieras om de aldrig kommer upp i " +"den minsta väntetiden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Ange maximal tid som måste förflyta efter att en fil har modifierats innan " +"en fil kopieras till servern." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ogiltig tid angiven" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Ange ett heltal större än noll." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Mottagare som ska få varningsmeddelanden:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup-klienten skickar varningsmeddelanden när ett problem uppstår under " +"säkerhetskopieringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Ange antingen ett lokalt användarnamn (exempelvis \"root\") eller en e-post-" +"adress (exempelvis \"admin@example.org\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Skapa klientens privata nyckel och X.509-certifikatförfrågan?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup-klienten behöver en privat RSA-nyckel och dess motsvarande X.509-" +"certifikat för att kunna identifiera sig mot servern." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Båda kan skapas automatiskt. Du måste flytta certifikatförfrågningen till " +"administratören av BoxBackupservern som kommer att signera den och skicka " +"tillbaka den till dig tillsammans med servern certifikatutfärdar-certifikat." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Dessa filer ska kopieras till BoxBackups inställningskatalog. Filnamnen för " +"filerna anges i filen /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Ska inställningar för BoxBackup skapas automatiskt?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Paketets inställningsskript kan skapa inställningsfilerna för Boxbackup-" +"servern." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Du ska anta detta alternativ om du inte känner till BoxBackups " +"inställningsalternativ sedan tidigare. Inställningarna kan göras manuellt " +"med skripten \"raidfile-config\" och \"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Servern kommer inte att starta om inställningarna inte är kompletta. Du bör " +"läsa igenom /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Plats för RAID-kataloger:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Ange platsen för de tre RAID-katalogerna." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"För att aktivera RAID måste katalognamnen vara en mellanslagsseparerad lista " +"med tre partitioner, varje partition på en egen fysisk enhet (ex. \"/" +"raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Om du inte vill aktivera RAID anger du bara en sökväg till en ensam katalog " +"som säkerhetskopiorna ska lagras i (exempelvis /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Dessa kataloger kommer att skapas om de inte existerar." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ogiltigt namn på sökväg" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Exempelvis: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blockstorleken för userland-RAID-system:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup använder userland-RAID-teknologi." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Välj den blockstorlek som ska användas för lagringen. För maximal " +"effektivitet ska du välja samma blockstorlek som det underliggande " +"filsystemet använder (storleken kan visas med kommandot \"tune2fs -l\" för " +"ext2)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Detta värde ska anges även om du inte tänker använda RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Skapa en privat servernyckel och X.509 certifikatförfrågan?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-servern behöver en privat RSA-nyckel och ett tillhörande X.509-" +"certifikat för att kunna utföra klient-server-identifiering och " +"kommunikationskryptering." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Båda kan skapas automatiskt. Certifikatet måste signeras med ditt " +"huvudcertifikat från certifikatutfärdaren (se även paketet boxbackup-server) " +"sedan placeras både det signerade certifikatet och huvudcertifikatet från " +"certifikatutfärdaren i inställningskatalogen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Felaktig storlek på block" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Blockstorleken måste vara binärkompatibel (ex. 1024 eller 4096)." diff --git a/po/templates.pot b/po/templates.pot new file mode 100644 index 00000000..b897dfbf --- /dev/null +++ b/po/templates.pot @@ -0,0 +1,446 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" diff --git a/po/vi.po b/po/vi.po new file mode 100644 index 00000000..d1412967 --- /dev/null +++ b/po/vi.po @@ -0,0 +1,514 @@ +# Vietnamese translation for BoxBackup. +# Copyright © 2010 Free Software Foundation, Inc. +# Clytie Siddall , 2007-2010. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc3~r2502-2.1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2010-09-30 18:14+0930\n" +"Last-Translator: Clytie Siddall \n" +"Language-Team: Vietnamese \n" +"Language: vi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: LocFactoryEditor 1.8\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Ứng dụng khách BoxBackup có nên được tự động cấu hình không?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Các văn lệnh cấu hình gói có khả năng tạo những tập tin cấu hình cho ứng " +"dụng khách BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Có nên bật tùy chọn này nếu bạn chưa quen với các tùy chọn cấu hình của " +"BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Xem tài liệu Đọc Đi « /usr/share/doc/boxbackup-client/README.Debian » để tìm " +"chi tiết về cấu hình của ứng dụng khách BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Chế độ chạy ứng dụng khách BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Ứng dụng khách BoxBackup hỗ trợ hai chế độ sao lưu :" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Trong chế độ « làm biếng », trình nền sao lưu sẽ quét đều đặn hệ thống tập " +"tin tìm tập tin bị sửa đổi. Tìm được thì cũng tải lên máy phục vụ sao lưu " +"tập tin nào « cũ » hơn một thời gian nào đó." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Trong chế độ « chụp hiện trạng », việc sao lưu sẽ được chạy dứt khoát theo " +"mỗi khoảng đều đặn. Một tập tin định kỳ cron (/etc/cron.d/boxbackup-client) " +"có sẵn với gói này và nên được sửa đổi để thích hợp với yêu cầu của bạn." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Số thứ tự tài khoản cho nút này trên máy phục vụ sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Quản trị của máy phục vụ BoxBackup nên đã gán cho ứng dụng khách này một số " +"thứ tự tài khoản kiểu thập lục." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Chưa gán số thứ tự tài khoản thì bạn bỏ trống trường này và cấu hình về sau " +"bằng cách chạy câu lệnh « dpkg-reconfigure boxbackup-client » với quyền " +"người chủ." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Số thứ tự tài khoản sai" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Số thứ tự tài khoản phải là một con số thập lục (v.d. 1F04 hay 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Tên miền có khả năng đầy đủ của máy phục vụ sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Hãy gõ tên miền có khả năng đầy đủ (FQDN) của máy phục vụ Boxbackup cho ứng " +"dụng khách sử dụng." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Ứng dụng khách sẽ kết nối tới máy phục vụ trên cổng TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Danh sách các thư mục cần sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Hãy đưa ra một danh sách định giới bằng dấu cách chứa những thư mục cần sao " +"lưu vào máy phục vụ từ xa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Những thư mục này không nên chứa hệ thống tập tin đã lắp ở cấp nào trong các " +"thư mục phụ." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Tên đường dẫn sai" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Mỗi tên đường dẫn tới thư mục phải là một tên đường dẫn tuyệt đối, định giới " +"bằng dấu cách." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Thí dụ : /home/tài_khoản_mình /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Khoảng (theo giây) giữa hai lần quét thư mục:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Trình BoxBackup quét đều đặn các thư mục đã chọn, tìm tập tin bị sửa đổi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Hãy chọn khoảng quét theo giây." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Thời gian tối thiểu (theo giây) cần đợi trước khi tải lên tập tin:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Mỗi tập tin sẽ được tải lên máy phục vụ chỉ sau khi đợi một khoảng thời gian " +"nào đó sau lần cuối cùng bị sửa đổi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Giá trị khoảng thấp sẽ gây ra nhiều việc tải lên máy phục vụ hơn thì nhiều " +"bản sửa đổi được tạo hơn, và các bản sửa đổi cũ bị gỡ bỏ trước." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Thời gian tối đa (theo giây) cần đợi trước khi tải lên tập tin:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Tập tin thường bị sửa đổi thì sẽ không được tải lên nếu chưa tới thời gian " +"đợi tối thiểu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Hãy gõ khoảng thời gian tối đa cần tới trước khi ép buộc việc tải lên máy " +"phục vụ tập tin bị sửa đổi." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Sai nhập thời gian" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Hãy gõ một giá trị số nguyên dương (>0)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Người nhận thông báo cảnh giác:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Ứng dụng khách BoxBackup gửi thông báo cảnh giác khi gặp vấn đề trong khi " +"sao lưu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Hãy gõ hoặc một tên người dùng cục bộ (v.d. « root »), hoặc một địa chỉ thư " +"điện tử (v.d. « admin@thí_dụ.org »)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Tạo ra khoá riêng ứng dụng khách và yêu cầu chứng nhận X.509 ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Ứng dụng khách BoxBackup yêu cầu một khoá riêng kiểu RSA, và chứng nhận " +"X.509 tương ứng, để tự xác thực với máy phục vụ." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Cả hai có thể được tự động tạo ra. Bạn cần phải gửi yêu cầu chứng nhận cho " +"quản trị máy phục vụ BoxBackup ký và trả lại cùng với chứng nhận CA (nhà cầm " +"quyền cấp chứng nhận) của máy phục vụ." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Những tập tin này nên được sao chép vào thư mục cấu hình của BoxBackup. Các " +"tên tập tin cần dùng được đưa ra trong tập tin cấu hình « /etc/boxbackup/" +"bbackupd.conf file »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup có nên được tự động cấu hình không?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Các văn lệnh cấu hình gói có khả năng tạo những tập tin cấu hình cho trình " +"phục vụ BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Có nên bật tùy chọn này nếu bạn chưa quen với các tùy chọn cấu hình của " +"BoxBackup. Bạn cũng có thể tự cấu hình dùng văn lệnh « raidfile-config » va " +"« bbstored-config »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Chưa cấu hình thì trình phục vụ không khởi chạy. Trong mọi trường hợp, " +"khuyên bạn đọc tài liệu Đọc Đi « /usr/share/doc/boxbackup-server/README." +"Debian »." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Vị trí của thư mục RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Hãy chọn vị trí cho ba thư mục tập tin RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Để hiệu lực chức năng RAID, những tên thư mục nên làm một danh sách định " +"giới bằng dấu cách chứa ba phân vùng, mỗi điều trên một ổ đĩa cứng vật lý " +"riêng (v.d. « /raid/0.0 /raid/0.1 /raid/0.2 »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Không muốn hiệu lực RAID thì chỉ cần xác định đường dẫn tới một thư mục " +"trong đó có thể cất giữ các bản sao lưu (v.d. « /usr/local/lib/boxbackup »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Chưa tồn tại thì tự động tạo những thư mục này." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Tên đường dẫn sai" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Thí dụ : /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Kích cỡ khối cho hệ thống RAID vùng người dùng:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup sử dụng kỹ thuật RAID kiểu vùng người dùng." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Hãy chọn kích cỡ khối cần dùng cho sức chứa. Để hữu hiệu nhất, bạn nên chọn " +"kích cỡ khối của hệ thống tập tin bên dưới (trên hệ thống tập tin ext2 có " +"thể hiển thị nó dùng câu lệnh « tune2fs -l »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Giá trị này nên được lập thậm chí nếu bạn không định sử dụng RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Tạo ra một khoá riêng máy phục vụ và yêu cầu chứng nhận X.509 ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Trình phục vụ BoxBackup yêu cầu một khoá riêng kiểu RSA, và chứng nhận X.509 " +"tương ứng, để thực hiện tiến trình xác thực giữa ứng dụng khách và trình " +"phục vụ, và để mật mã hoá giao thông." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Cả hai có thể được tự động tạo ra. Bạn cần phải ký chứng nhận dùng CA gốc " +"(xem gói tiện ích « boxbackup-server »), và để vào thư mục cấu hình chứng " +"nhận đã ký và chứng nhận CA gốc." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Sai lập kích cỡ khối" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Kích cỡ khối phải là hai lũy thừa (v.d. 1024 hay 4096)." diff --git a/rules b/rules new file mode 100755 index 00000000..666ba0ea --- /dev/null +++ b/rules @@ -0,0 +1,102 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + +TMP:=$(CURDIR)/debian/tmp + +ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) + CFLAGS += -g +endif +ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) + INSTALL_PROGRAM += -s +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + dh_autotools-dev_updateconfig + echo "0.11rc8+`cat .svnrevision`" > VERSION.txt + echo "boxbackup" >> VERSION.txt + sh -x ./bootstrap + ./configure $(DEB_EXTRA_CONFIG_FLAGS) LDFLAGS="-Wl,--as-needed" + touch configure-stamp + +build-stamp: configure-stamp + dh_testdir + $(MAKE) V=1 +ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) + -./runtest.pl ALL +endif + touch build-stamp + +docs/docbook/instguide.pdf: + $(MAKE) -C docs instguide + cd docs/docbook && docbook2pdf instguide.xml + +docs/docbook/adminguide.pdf: configure-stamp + $(MAKE) -C docs adminguide + cd docs/docbook && docbook2pdf adminguide.xml + +docs: docs/docbook/instguide.pdf docs/docbook/adminguide.pdf + $(MAKE) -C docs manpages + +build-arch: build-stamp +build-indep: docs + +build: build-arch build-indep + +clean: + dh_testdir + dh_testroot + dh_clean build-stamp configure-stamp + echo "USE_SVN_VERSION" > VERSION.txt + echo "boxbackup" >> VERSION.txt + [ ! -f Makefile ] || make clean + sh debian/clean.sh + dh_autotools-dev_restoreconfig + dh_clean config.log config.status + +install: DH_OPTIONS= +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + + mkdir -p $(TMP)/etc/logcheck/ignore.d.workstation + mkdir -p $(TMP)/etc/logcheck/ignore.d.server + install -m 644 debian/boxbackup-server.logcheck.ignore $(TMP)/etc/logcheck/ignore.d.workstation/boxbackup-server + install -m 644 debian/boxbackup-server.logcheck.ignore $(TMP)/etc/logcheck/ignore.d.server/boxbackup-server + + dh_install + +binary-indep: +# no architecture independant packages are being built + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir -a + dh_testroot -a + dh_installdebconf -a + dh_installdocs -a -A ExceptionCodes.txt docs/docbook/instguide.pdf docs/docbook/adminguide.pdf + dh_installinit -a + dh_installcron -a + dh_installman + dh_installchangelogs -a + dh_strip -a + dh_compress -a + dh_fixperms -a + dh_installdeb -a + dh_shlibdeps -a + dh_gencontrol -a + dh_md5sums -a + dh_builddeb -a + +binary: binary-arch +.PHONY: build build-arch build-indep clean binary-indep binary-arch binary install docs diff --git a/source/format b/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/watch b/watch new file mode 100644 index 00000000..5ec27736 --- /dev/null +++ b/watch @@ -0,0 +1,4 @@ +version=3 +# release candidates are not on sf +# http://sf.net/boxbackup/ boxbackup-(.*)\.tgz +http://boxbackup.org/svn/box/packages/ boxbackup-(.*)\.tgz -- cgit v1.2.3 From b19da4f742f1814bd289ae1516a898d3bc5e4187 Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Sat, 21 Jan 2017 21:29:52 -0500 Subject: adjust-syslog-facility change default syslog facility from LOG_LOCAL6 to LOG_DAEMON Gbp-Pq: Name 03-adjust-syslog-facility.diff --- bin/bbstored/BackupStoreDaemon.cpp | 2 +- docs/docbook/adminguide.xml | 6 ++++++ lib/common/Logging.cpp | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bin/bbstored/BackupStoreDaemon.cpp b/bin/bbstored/BackupStoreDaemon.cpp index 4de0a078..6e4fa79d 100644 --- a/bin/bbstored/BackupStoreDaemon.cpp +++ b/bin/bbstored/BackupStoreDaemon.cpp @@ -203,7 +203,7 @@ void BackupStoreDaemon::Run() SetProcessTitle("housekeeping, idle"); whichSocket = 1; // Change the log name - ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); + ::openlog("bbstored/hk", LOG_PID, LOG_DAEMON); // Log that housekeeping started BOX_INFO("Housekeeping process started"); // Ignore term and hup diff --git a/docs/docbook/adminguide.xml b/docs/docbook/adminguide.xml index edb0a58c..440c7d4f 100644 --- a/docs/docbook/adminguide.xml +++ b/docs/docbook/adminguide.xml @@ -286,6 +286,12 @@ local5.info /var/log/raidfile Note: Separators must be tabs, otherwise these entries will be ignored. + Note2: The packaged + debian and ubuntu versions of boxbackup do not log to local6, + but to the more standard 'daemon' facility. This means you + should not have anything to do to your syslog configuration, + since it is configured to be logged by default. + touch /var/log/box touch /var/log/raidfile diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp index 296443ea..a54ef244 100644 --- a/lib/common/Logging.cpp +++ b/lib/common/Logging.cpp @@ -401,7 +401,7 @@ bool Syslog::Log(Log::Level level, const std::string& rFile, return true; } -Syslog::Syslog() : mFacility(LOG_LOCAL6) +Syslog::Syslog() : mFacility(LOG_DAEMON) { ::openlog("Box Backup", LOG_PID, mFacility); } @@ -439,8 +439,8 @@ int Syslog::GetNamedFacility(const std::string& rFacility) #undef CASE_RETURN BOX_ERROR("Unknown log facility '" << rFacility << "', " - "using default LOCAL6"); - return LOG_LOCAL6; + "using default DAEMON"); + return LOG_DAEMON; } bool FileLogger::Log(Log::Level Level, const std::string& rFile, -- cgit v1.2.3 From 1a29e357291ce7e781078b66fafcf1b8dfeb70ae Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Sat, 21 Jan 2017 21:29:52 -0500 Subject: dont_use_net_for_docs === modified file 'docs/Makefile' Gbp-Pq: Name 05-dont_use_net_for_docs.diff --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index c4a63671..2fcca31e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,7 +10,7 @@ all: docs -DBPROC_COMMAND = xsltproc +DBPROC_COMMAND = xsltproc --nonet MKDIR_COMMAND = mkdir CP_COMMAND = cp PERL_COMMAND = perl -- cgit v1.2.3 From 5b54d2ddc90fc4ac39f1b67d7e065377925e8353 Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Sat, 21 Jan 2017 21:29:52 -0500 Subject: gcc_4.4_fixes http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=526152 https://bugs.launchpad.net/bugs/371809 Impact: FTBFS with gcc 4.4 === modified file 'lib/server/ServerControl.cpp' Gbp-Pq: Name 06-gcc_4.4_fixes.diff --- lib/server/ServerControl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/server/ServerControl.cpp b/lib/server/ServerControl.cpp index b9650cee..ca076ee7 100644 --- a/lib/server/ServerControl.cpp +++ b/lib/server/ServerControl.cpp @@ -1,5 +1,6 @@ #include "Box.h" +#include #include #include -- cgit v1.2.3 From 4ef577f45aab85ea5591d2957d280d6533e66124 Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Sat, 17 Jun 2017 17:54:09 -0400 Subject: Import boxbackup_0.12~gitcf52058f.orig.tar.gz [dgit import orig boxbackup_0.12~gitcf52058f.orig.tar.gz] --- .gitignore | 83 + .hgignore | 37 + .travis.yml | 36 + BUGS.txt | 16 + COPYING.txt | 491 ++ LICENSE-DUAL.txt | 59 + LICENSE-GPL.txt | 41 + LICENSE.txt | 79 + README.md | 22 + VERSION.txt | 2 + appveyor.yml | 119 + bin/bbackupctl/bbackupctl.cpp | 394 ++ bin/bbackupd/bbackupd-config.in | 601 ++ bin/bbackupd/bbackupd.cpp | 59 + bin/bbackupd/win32/NotifySysAdmin.vbs | 113 + bin/bbackupd/win32/bbackupd.conf | 238 + bin/bbackupd/win32/installer.iss | 0 bin/bbackupobjdump/bbackupobjdump.cpp | 83 + bin/bbackupquery/bbackupquery.cpp | 543 ++ bin/bbstoreaccounts/bbstoreaccounts.cpp | 290 + bin/bbstored/bbstored-certs.in | 319 ++ bin/bbstored/bbstored-config.in | 245 + bin/bbstored/bbstored.cpp | 31 + bin/s3simulator/s3simulator.cpp | 32 + bootstrap | 6 + cleanupforcvs.pl | 196 + configure.ac | 173 + contrib/bbadmin/accounts.cgi | 580 ++ contrib/bbadmin/apache.conf | 14 + contrib/bbadmin/bb.css | 70 + contrib/bbreporter/LICENSE | 674 +++ contrib/bbreporter/bbreporter.py | 538 ++ contrib/debian/README.txt | 9 + contrib/debian/bbackupd.in | 59 + contrib/debian/bbstored.in | 59 + contrib/mac_osx/org.boxbackup.bbackupd.plist.in | 22 + contrib/mac_osx/org.boxbackup.bbstored.plist.in | 22 + contrib/redhat/README.txt | 7 + contrib/redhat/bbackupd.in | 83 + contrib/redhat/bbstored.in | 83 + contrib/rpm/README.txt | 16 + contrib/rpm/boxbackup.spec | 244 + contrib/solaris/bbackupd-manifest.xml.in | 59 + contrib/solaris/bbackupd-smf-method.in | 24 + contrib/solaris/bbstored-manifest.xml.in | 60 + contrib/solaris/bbstored-smf-method.in | 23 + contrib/suse/README.txt | 5 + contrib/suse/bbackupd.in | 103 + contrib/suse/bbstored.in | 104 + contrib/suse/bbstored.service | 26 + contrib/windows/installer/bbackupd.conf.template | 176 + contrib/windows/installer/boxbackup.mpi.in | 3693 ++++++++++++ contrib/windows/installer/tools/InstallService.bat | 3 + .../windows/installer/tools/KillBackupProcess.bat | 3 + contrib/windows/installer/tools/QueryOutputAll.bat | 5 + .../windows/installer/tools/QueryOutputCurrent.bat | 5 + contrib/windows/installer/tools/ReloadConfig.bat | 3 + contrib/windows/installer/tools/RemoveService.bat | 3 + contrib/windows/installer/tools/RestartService.bat | 5 + contrib/windows/installer/tools/ShowUsage.bat | 3 + contrib/windows/installer/tools/StartService.bat | 3 + contrib/windows/installer/tools/StopService.bat | 3 + contrib/windows/installer/tools/Sync.bat | 3 + distribution/COMMON-MANIFEST.txt | 47 + distribution/boxbackup/CONTACT.txt | 6 + distribution/boxbackup/DISTRIBUTION-MANIFEST.txt | 92 + distribution/boxbackup/DOCUMENTATION.txt | 6 + distribution/boxbackup/LINUX.txt | 29 + distribution/boxbackup/NETBSD.txt | 8 + distribution/boxbackup/THANKS.txt | 69 + distribution/boxbackup/VERSION.txt | 2 + docs/Makefile | 183 + docs/api-notes/INDEX.txt | 61 + docs/api-notes/Win32_Clients.txt | 13 + docs/api-notes/backup_encryption.txt | 109 + docs/api-notes/bin_bbackupd.txt | 88 + docs/api-notes/bin_bbstored.txt | 54 + docs/api-notes/common/lib_common.txt | 52 + docs/api-notes/common/lib_common/BoxTime.txt | 7 + .../common/lib_common/CollectInBufferStream.txt | 26 + docs/api-notes/common/lib_common/Configuration.txt | 102 + docs/api-notes/common/lib_common/Conversion.txt | 14 + docs/api-notes/common/lib_common/ExcludeList.txt | 21 + docs/api-notes/common/lib_common/FdGetLine.txt | 11 + docs/api-notes/common/lib_common/Guards.txt | 5 + docs/api-notes/common/lib_common/IOStream.txt | 89 + .../common/lib_common/IOStreamGetLine.txt | 29 + docs/api-notes/common/lib_common/MainHelper.txt | 4 + docs/api-notes/common/lib_common/WaitForEvent.txt | 16 + docs/api-notes/common/lib_common/xStream.txt | 40 + docs/api-notes/common/lib_compress.txt | 8 + .../common/lib_compress/CompressStream.txt | 27 + docs/api-notes/common/lib_crypto.txt | 28 + docs/api-notes/common/lib_crypto/CipherContext.txt | 28 + .../common/lib_crypto/RollingChecksum.txt | 36 + docs/api-notes/common/lib_server.txt | 9 + docs/api-notes/common/lib_server/Daemon.txt | 96 + docs/api-notes/common/lib_server/Protocol.txt | 120 + docs/api-notes/common/lib_server/ServerStream.txt | 29 + docs/api-notes/common/lib_server/ServerTLS.txt | 6 + docs/api-notes/common/lib_server/SocketStream.txt | 8 + .../common/lib_server/SocketStreamTLS.txt | 11 + docs/api-notes/common/lib_server/TLSContext.txt | 16 + docs/api-notes/common/memory_leaks.txt | 44 + docs/api-notes/encrypt_rsync.txt | 66 + docs/api-notes/lib_backupclient.txt | 46 + docs/api-notes/lib_backupstore.txt | 30 + docs/api-notes/raidfile/RaidFileRead.txt | 14 + docs/api-notes/raidfile/RaidFileWrite.txt | 36 + docs/api-notes/raidfile/lib_raidfile.txt | 73 + .../win32_build_on_cygwin_using_mingw.txt | 157 + .../api-notes/win32_build_on_linux_using_mingw.txt | 108 + docs/api-notes/windows_porting.txt | 100 + docs/docbook/adminguide.xml | 1981 +++++++ docs/docbook/bb-book.xsl | 17 + docs/docbook/bb-man.xsl | 9 + docs/docbook/bb-nochunk-book.xsl | 17 + docs/docbook/bbackupctl.xml | 205 + docs/docbook/bbackupd-config.xml | 153 + docs/docbook/bbackupd.conf.xml | 479 ++ docs/docbook/bbackupd.xml | 209 + docs/docbook/bbackupquery.xml | 506 ++ docs/docbook/bbstoreaccounts.xml | 386 ++ docs/docbook/bbstored-certs.xml | 180 + docs/docbook/bbstored-config.xml | 148 + docs/docbook/bbstored.conf.xml | 211 + docs/docbook/bbstored.xml | 90 + docs/docbook/html/bbdoc-man.css | 104 + docs/docbook/html/bbdoc.css | 112 + docs/docbook/html/favicon.ico | Bin 0 -> 67646 bytes docs/docbook/html/images/arrow.png | Bin 0 -> 197 bytes docs/docbook/html/images/bblogo.png | Bin 0 -> 5882 bytes docs/docbook/html/images/stepahead.png | Bin 0 -> 298 bytes docs/docbook/instguide.xml | 766 +++ docs/docbook/raidfile-config.xml | 198 + docs/docbook/raidfile.conf.xml | 143 + docs/images/bblogo-alpha.xcf | Bin 0 -> 93980 bytes docs/images/box-alpha.png | Bin 0 -> 5728 bytes docs/images/box-alpha.xcf | Bin 0 -> 33665 bytes docs/tools/generate_except_xml.pl | 74 + docs/xsl-generic/VERSION | 113 + docs/xsl-generic/common/af.xml | 1223 ++++ docs/xsl-generic/common/am.xml | 1223 ++++ docs/xsl-generic/common/ar.xml | 1223 ++++ docs/xsl-generic/common/autoidx-kimber.xsl | 43 + docs/xsl-generic/common/autoidx-kosek.xsl | 150 + docs/xsl-generic/common/az.xml | 666 +++ docs/xsl-generic/common/bg.xml | 718 +++ docs/xsl-generic/common/bn.xml | 1223 ++++ docs/xsl-generic/common/bs.xml | 656 +++ docs/xsl-generic/common/ca.xml | 1223 ++++ docs/xsl-generic/common/charmap.xml | 185 + docs/xsl-generic/common/charmap.xsl | 221 + docs/xsl-generic/common/common.xml | 622 ++ docs/xsl-generic/common/common.xsl | 1981 +++++++ docs/xsl-generic/common/cs.xml | 694 +++ docs/xsl-generic/common/cy.xml | 1239 ++++ docs/xsl-generic/common/da.xml | 658 +++ docs/xsl-generic/common/de.xml | 660 +++ docs/xsl-generic/common/el.xml | 1223 ++++ docs/xsl-generic/common/en.xml | 1223 ++++ docs/xsl-generic/common/entities.ent | 47 + docs/xsl-generic/common/eo.xml | 1223 ++++ docs/xsl-generic/common/es.xml | 670 +++ docs/xsl-generic/common/et.xml | 1223 ++++ docs/xsl-generic/common/eu.xml | 1223 ++++ docs/xsl-generic/common/fa.xml | 1223 ++++ docs/xsl-generic/common/fi.xml | 664 +++ docs/xsl-generic/common/fr.xml | 684 +++ docs/xsl-generic/common/ga.xml | 1223 ++++ docs/xsl-generic/common/gentext.xsl | 831 +++ docs/xsl-generic/common/gu.xml | 1223 ++++ docs/xsl-generic/common/he.xml | 1223 ++++ docs/xsl-generic/common/hi.xml | 1223 ++++ docs/xsl-generic/common/hr.xml | 1223 ++++ docs/xsl-generic/common/hu.xml | 1223 ++++ docs/xsl-generic/common/id.xml | 1223 ++++ docs/xsl-generic/common/insertfile.xsl | 111 + docs/xsl-generic/common/it.xml | 1223 ++++ docs/xsl-generic/common/ja.xml | 1223 ++++ docs/xsl-generic/common/kn.xml | 1223 ++++ docs/xsl-generic/common/ko.xml | 1223 ++++ docs/xsl-generic/common/l10n.dtd | 63 + docs/xsl-generic/common/l10n.xml | 127 + docs/xsl-generic/common/l10n.xsl | 441 ++ docs/xsl-generic/common/la.xml | 1223 ++++ docs/xsl-generic/common/labels.xsl | 869 +++ docs/xsl-generic/common/lt.xml | 672 +++ docs/xsl-generic/common/lv.xml | 1223 ++++ docs/xsl-generic/common/mn.xml | 724 +++ docs/xsl-generic/common/nl.xml | 1223 ++++ docs/xsl-generic/common/nn.xml | 1223 ++++ docs/xsl-generic/common/no.xml | 1223 ++++ docs/xsl-generic/common/olink.xsl | 1149 ++++ docs/xsl-generic/common/or.xml | 1223 ++++ docs/xsl-generic/common/pa.xml | 1223 ++++ docs/xsl-generic/common/pi.xsl | 346 ++ docs/xsl-generic/common/pl.xml | 1223 ++++ docs/xsl-generic/common/pt.xml | 1223 ++++ docs/xsl-generic/common/pt_br.xml | 1223 ++++ docs/xsl-generic/common/refentry.xml | 781 +++ docs/xsl-generic/common/refentry.xsl | 1277 +++++ docs/xsl-generic/common/ro.xml | 1223 ++++ docs/xsl-generic/common/ru.xml | 720 +++ docs/xsl-generic/common/sk.xml | 1223 ++++ docs/xsl-generic/common/sl.xml | 1223 ++++ docs/xsl-generic/common/sq.xml | 1223 ++++ docs/xsl-generic/common/sr.xml | 714 +++ docs/xsl-generic/common/sr_Latn.xml | 673 +++ docs/xsl-generic/common/stripns.xsl | 342 ++ docs/xsl-generic/common/subtitles.xsl | 155 + docs/xsl-generic/common/sv.xml | 658 +++ docs/xsl-generic/common/ta.xml | 1223 ++++ docs/xsl-generic/common/table.xsl | 514 ++ docs/xsl-generic/common/targetdatabase.dtd | 49 + docs/xsl-generic/common/targets.xsl | 272 + docs/xsl-generic/common/th.xml | 1223 ++++ docs/xsl-generic/common/titles.xsl | 740 +++ docs/xsl-generic/common/tl.xml | 1223 ++++ docs/xsl-generic/common/tr.xml | 660 +++ docs/xsl-generic/common/uk.xml | 1223 ++++ docs/xsl-generic/common/utility.xml | 259 + docs/xsl-generic/common/utility.xsl | 290 + docs/xsl-generic/common/vi.xml | 1223 ++++ docs/xsl-generic/common/xh.xml | 1223 ++++ docs/xsl-generic/common/zh_cn.xml | 654 +++ docs/xsl-generic/common/zh_tw.xml | 1223 ++++ docs/xsl-generic/highlighting/c-hl.xml | 105 + docs/xsl-generic/highlighting/common.xsl | 62 + docs/xsl-generic/highlighting/delphi-hl.xml | 174 + docs/xsl-generic/highlighting/ini-hl.xml | 43 + docs/xsl-generic/highlighting/java-hl.xml | 98 + docs/xsl-generic/highlighting/m2-hl.xml | 86 + docs/xsl-generic/highlighting/myxml-hl.xml | 131 + docs/xsl-generic/highlighting/php-hl.xml | 127 + docs/xsl-generic/highlighting/xslthl-config.xml | 11 + docs/xsl-generic/html/admon.xsl | 132 + docs/xsl-generic/html/annotations.xsl | 169 + docs/xsl-generic/html/autoidx-kimber.xsl | 168 + docs/xsl-generic/html/autoidx-kosek.xsl | 125 + docs/xsl-generic/html/autoidx-ng.xsl | 20 + docs/xsl-generic/html/autoidx.xsl | 645 +++ docs/xsl-generic/html/autotoc.xsl | 675 +++ docs/xsl-generic/html/biblio-iso690.xsl | 1300 +++++ docs/xsl-generic/html/biblio.xsl | 1228 ++++ docs/xsl-generic/html/block.xsl | 434 ++ docs/xsl-generic/html/callout.xsl | 201 + docs/xsl-generic/html/changebars.xsl | 100 + docs/xsl-generic/html/chunk-code.xsl | 670 +++ docs/xsl-generic/html/chunk-common.xsl | 1886 ++++++ docs/xsl-generic/html/chunk.xsl | 52 + docs/xsl-generic/html/chunker.xsl | 439 ++ docs/xsl-generic/html/chunkfast.xsl | 72 + docs/xsl-generic/html/chunktoc.xsl | 468 ++ docs/xsl-generic/html/component.xsl | 401 ++ docs/xsl-generic/html/division.xsl | 228 + docs/xsl-generic/html/docbook.xsl | 479 ++ docs/xsl-generic/html/ebnf.xsl | 329 ++ docs/xsl-generic/html/footnote.xsl | 299 + docs/xsl-generic/html/formal.xsl | 400 ++ docs/xsl-generic/html/glossary.xsl | 482 ++ docs/xsl-generic/html/graphics.xsl | 1489 +++++ docs/xsl-generic/html/highlight.xsl | 54 + docs/xsl-generic/html/html-rtf.xsl | 336 ++ docs/xsl-generic/html/html.xsl | 241 + docs/xsl-generic/html/htmltbl.xsl | 55 + docs/xsl-generic/html/index.xsl | 229 + docs/xsl-generic/html/info.xsl | 43 + docs/xsl-generic/html/inline.xsl | 1439 +++++ docs/xsl-generic/html/keywords.xsl | 35 + docs/xsl-generic/html/lists.xsl | 1103 ++++ docs/xsl-generic/html/maketoc.xsl | 86 + docs/xsl-generic/html/manifest.xsl | 22 + docs/xsl-generic/html/math.xsl | 270 + docs/xsl-generic/html/oldchunker.xsl | 202 + docs/xsl-generic/html/onechunk.xsl | 37 + docs/xsl-generic/html/param.xsl | 412 ++ docs/xsl-generic/html/pi.xsl | 1240 ++++ docs/xsl-generic/html/profile-chunk-code.xsl | 609 ++ docs/xsl-generic/html/profile-chunk.xsl | 52 + docs/xsl-generic/html/profile-docbook.xsl | 411 ++ docs/xsl-generic/html/profile-onechunk.xsl | 37 + docs/xsl-generic/html/qandaset.xsl | 389 ++ docs/xsl-generic/html/refentry.xsl | 309 + docs/xsl-generic/html/sections.xsl | 622 ++ docs/xsl-generic/html/synop.xsl | 1596 ++++++ docs/xsl-generic/html/table.xsl | 1120 ++++ docs/xsl-generic/html/task.xsl | 76 + docs/xsl-generic/html/titlepage.templates.xml | 662 +++ docs/xsl-generic/html/titlepage.templates.xsl | 3622 ++++++++++++ docs/xsl-generic/html/titlepage.xsl | 1031 ++++ docs/xsl-generic/html/toc.xsl | 173 + docs/xsl-generic/html/verbatim.xsl | 376 ++ docs/xsl-generic/html/xref.xsl | 1348 +++++ docs/xsl-generic/lib/lib.xsl | 480 ++ docs/xsl-generic/manpages/block.xsl | 296 + docs/xsl-generic/manpages/charmap.groff.xsl | 5985 ++++++++++++++++++++ docs/xsl-generic/manpages/docbook.xsl | 293 + docs/xsl-generic/manpages/endnotes.xsl | 535 ++ docs/xsl-generic/manpages/html-synop.xsl | 1605 ++++++ docs/xsl-generic/manpages/info.xsl | 630 +++ docs/xsl-generic/manpages/inline.xsl | 190 + docs/xsl-generic/manpages/lists.xsl | 368 ++ docs/xsl-generic/manpages/other.xsl | 674 +++ docs/xsl-generic/manpages/param.xsl | 167 + docs/xsl-generic/manpages/profile-docbook.xsl | 259 + docs/xsl-generic/manpages/refentry.xsl | 256 + docs/xsl-generic/manpages/synop.xsl | 305 + docs/xsl-generic/manpages/table.xsl | 633 +++ docs/xsl-generic/manpages/utility.xsl | 452 ++ infrastructure/BoxPlatform.pm.in | 158 + infrastructure/buildenv-testmain-template.cpp | 438 ++ infrastructure/cmake/.gitignore | 6 + infrastructure/cmake/CMakeLists.txt | 300 + .../cmake/build/bin_bbackupd.vcxproj.user | 8 + .../cmake/build/bin_bbstored.vcxproj.user | 8 + .../cmake/build/test_backupstore.vcxproj.user | 9 + .../cmake/build/test_backupstorefix.vcxproj.user | 7 + .../cmake/build/test_bbackupd.vcxproj.user | 8 + .../cmake/build/test_common.vcxproj.user | 7 + .../cmake/build/test_httpserver.vcxproj.user | 7 + .../cmake/build/test_raidfile.vcxproj.user | 7 + infrastructure/config.guess | 1558 +++++ infrastructure/config.sub | 1788 ++++++ infrastructure/install-sh | 527 ++ infrastructure/m4/ac_cxx_exceptions.m4 | 24 + infrastructure/m4/ac_cxx_namespaces.m4 | 25 + infrastructure/m4/ax_bswap64.m4 | 52 + infrastructure/m4/ax_check_bdb_v1.m4 | 43 + infrastructure/m4/ax_check_define_pragma.m4 | 25 + infrastructure/m4/ax_check_dirent_d_type.m4 | 45 + infrastructure/m4/ax_check_llong_minmax.m4 | 76 + infrastructure/m4/ax_check_malloc_workaround.m4 | 38 + infrastructure/m4/ax_check_mount_point.m4 | 60 + infrastructure/m4/ax_check_nonaligned_access.m4 | 57 + infrastructure/m4/ax_check_ssl.m4 | 38 + infrastructure/m4/ax_check_syscall_lseek.m4 | 69 + infrastructure/m4/ax_compare_version.m4 | 162 + infrastructure/m4/ax_config_scripts.m4 | 16 + infrastructure/m4/ax_func_syscall.m4 | 50 + infrastructure/m4/ax_path_bdb.m4 | 615 ++ infrastructure/m4/ax_random_device.m4 | 31 + infrastructure/m4/ax_split_version.m4 | 19 + infrastructure/m4/boxbackup_tests.m4 | 348 ++ infrastructure/m4/vl_lib_readline.m4 | 168 + infrastructure/makebuildenv.pl.in | 1031 ++++ infrastructure/makedistribution.pl.in | 363 ++ infrastructure/makeparcels.pl.in | 417 ++ infrastructure/mingw/compile-boxbackup-cygwin.sh | 57 + infrastructure/mingw/configure.sh | 45 + infrastructure/mingw/environment.sh | 11 + infrastructure/mingw/runtest.sh | 7 + infrastructure/msvc/2003/bbackupctl.vcproj | 159 + infrastructure/msvc/2003/bbackupd.vcproj | 219 + infrastructure/msvc/2003/boxbackup.sln | 57 + infrastructure/msvc/2003/boxquery.vcproj | 174 + infrastructure/msvc/2003/common.vcproj | 672 +++ infrastructure/msvc/2003/win32test.vcproj | 148 + infrastructure/msvc/2005/bbackupctl.vcproj | 222 + infrastructure/msvc/2005/bbackupd.vcproj | 299 + infrastructure/msvc/2005/boxbackup.sln | 55 + infrastructure/msvc/2005/boxbackup.suo | Bin 0 -> 58880 bytes infrastructure/msvc/2005/boxquery.vcproj | 246 + infrastructure/msvc/2005/common.vcproj | 896 +++ infrastructure/msvc/2005/win32test.vcproj | 218 + infrastructure/msvc/2010/bbackupctl.vcxproj | 109 + infrastructure/msvc/2010/bbackupd.vcxproj | 139 + infrastructure/msvc/2010/bbstoreaccounts.vcxproj | 84 + infrastructure/msvc/2010/bbstored.vcxproj | 93 + infrastructure/msvc/2010/boxbackup.sln | 89 + infrastructure/msvc/2010/boxquery.vcxproj | 123 + infrastructure/msvc/2010/common.vcxproj | 249 + infrastructure/msvc/2010/libbackupclient.vcxproj | 94 + infrastructure/msvc/2010/libbackupstore.vcxproj | 151 + infrastructure/msvc/2010/qdbm.vcxproj | 81 + infrastructure/msvc/2010/win32test.vcxproj | 108 + infrastructure/msvc/2013/bbackupctl.vcxproj | 117 + infrastructure/msvc/2013/bbackupd.vcxproj | 147 + infrastructure/msvc/2013/bbstoreaccounts.vcxproj | 98 + infrastructure/msvc/2013/bbstored.vcxproj | 109 + infrastructure/msvc/2013/boxbackup.sln | 89 + infrastructure/msvc/2013/boxquery.vcxproj | 130 + infrastructure/msvc/2013/common.vcxproj | 258 + infrastructure/msvc/2013/libbackupclient.vcxproj | 108 + infrastructure/msvc/2013/libbackupstore.vcxproj | 184 + infrastructure/msvc/2013/qdbm.vcxproj | 90 + infrastructure/msvc/2013/win32test.vcxproj | 115 + infrastructure/msvc/fake-config.sub.pl | 18 + infrastructure/msvc/getversion.pl | 33 + infrastructure/msvc/win32.bat | 44 + infrastructure/parcelpath.pl | 17 + infrastructure/printversion.pl | 12 + infrastructure/setupexternal.pl | 55 + lib/backupclient/BackupClientCryptoKeys.cpp | 85 + lib/backupclient/BackupClientCryptoKeys.h | 55 + lib/backupclient/BackupClientMakeExcludeList.cpp | 75 + lib/backupclient/BackupClientMakeExcludeList.h | 48 + lib/backupclient/BackupClientRestore.cpp | 920 +++ lib/backupclient/BackupClientRestore.h | 36 + lib/backupclient/BackupDaemonConfigVerify.cpp | 161 + lib/backupclient/BackupDaemonConfigVerify.h | 18 + lib/backupclient/BackupStoreObjectDump.cpp | 227 + lib/backupclient/ClientException.txt | 11 + lib/backupclient/Makefile.extra | 7 + lib/backupstore/BackgroundTask.h | 39 + lib/backupstore/BackupAccountControl.cpp | 267 + lib/backupstore/BackupAccountControl.h | 94 + lib/backupstore/BackupClientFileAttributes.cpp | 1237 ++++ lib/backupstore/BackupClientFileAttributes.h | 82 + lib/backupstore/BackupCommands.cpp | 1037 ++++ lib/backupstore/BackupConstants.h | 24 + lib/backupstore/BackupProtocol.h | 71 + lib/backupstore/BackupStoreAccountDatabase.cpp | 374 ++ lib/backupstore/BackupStoreAccountDatabase.h | 75 + lib/backupstore/BackupStoreAccounts.cpp | 595 ++ lib/backupstore/BackupStoreAccounts.h | 85 + lib/backupstore/BackupStoreCheck.cpp | 1021 ++++ lib/backupstore/BackupStoreCheck.h | 221 + lib/backupstore/BackupStoreCheck2.cpp | 970 ++++ lib/backupstore/BackupStoreCheckData.cpp | 203 + lib/backupstore/BackupStoreConfigVerify.cpp | 51 + lib/backupstore/BackupStoreConfigVerify.h | 18 + lib/backupstore/BackupStoreConstants.h | 44 + lib/backupstore/BackupStoreContext.cpp | 1945 +++++++ lib/backupstore/BackupStoreContext.h | 235 + lib/backupstore/BackupStoreDirectory.cpp | 611 ++ lib/backupstore/BackupStoreDirectory.h | 491 ++ lib/backupstore/BackupStoreException.h | 17 + lib/backupstore/BackupStoreException.txt | 76 + lib/backupstore/BackupStoreFile.cpp | 1950 +++++++ lib/backupstore/BackupStoreFile.h | 309 + lib/backupstore/BackupStoreFileCmbDiff.cpp | 326 ++ lib/backupstore/BackupStoreFileCmbIdx.cpp | 326 ++ lib/backupstore/BackupStoreFileCombine.cpp | 410 ++ lib/backupstore/BackupStoreFileCryptVar.cpp | 31 + lib/backupstore/BackupStoreFileCryptVar.h | 39 + lib/backupstore/BackupStoreFileDiff.cpp | 1062 ++++ lib/backupstore/BackupStoreFileEncodeStream.cpp | 741 +++ lib/backupstore/BackupStoreFileEncodeStream.h | 144 + lib/backupstore/BackupStoreFileRevDiff.cpp | 258 + lib/backupstore/BackupStoreFileWire.h | 74 + lib/backupstore/BackupStoreFilename.cpp | 281 + lib/backupstore/BackupStoreFilename.h | 107 + lib/backupstore/BackupStoreFilenameClear.cpp | 347 ++ lib/backupstore/BackupStoreFilenameClear.h | 61 + lib/backupstore/BackupStoreInfo.cpp | 753 +++ lib/backupstore/BackupStoreInfo.h | 214 + lib/backupstore/BackupStoreObjectMagic.h | 31 + lib/backupstore/BackupStoreRefCountDatabase.cpp | 377 ++ lib/backupstore/BackupStoreRefCountDatabase.h | 126 + lib/backupstore/HousekeepStoreAccount.cpp | 1152 ++++ lib/backupstore/HousekeepStoreAccount.h | 121 + lib/backupstore/Makefile.extra | 15 + lib/backupstore/RunStatusProvider.h | 29 + lib/backupstore/StoreStructure.cpp | 95 + lib/backupstore/StoreStructure.h | 32 + lib/backupstore/StoreTestUtils.cpp | 300 + lib/backupstore/StoreTestUtils.h | 118 + lib/backupstore/backupprotocol.txt | 266 + lib/bbackupd/BackupClientContext.cpp | 578 ++ lib/bbackupd/BackupClientContext.h | 252 + lib/bbackupd/BackupClientDeleteList.cpp | 229 + lib/bbackupd/BackupClientDeleteList.h | 75 + lib/bbackupd/BackupClientDirectoryRecord.cpp | 2302 ++++++++ lib/bbackupd/BackupClientDirectoryRecord.h | 244 + lib/bbackupd/BackupClientInodeToIDMap.cpp | 320 ++ lib/bbackupd/BackupClientInodeToIDMap.h | 59 + lib/bbackupd/BackupDaemon.cpp | 3648 ++++++++++++ lib/bbackupd/BackupDaemon.h | 539 ++ lib/bbackupd/BackupDaemonInterface.h | 166 + lib/bbackupd/Win32BackupService.cpp | 48 + lib/bbackupd/Win32BackupService.h | 21 + lib/bbackupd/Win32ServiceFunctions.cpp | 384 ++ lib/bbackupd/Win32ServiceFunctions.h | 19 + lib/bbackupquery/BackupQueries.cpp | 2410 ++++++++ lib/bbackupquery/BackupQueries.h | 440 ++ lib/bbackupquery/BoxBackupCompareParams.h | 112 + lib/bbackupquery/CommandCompletion.cpp | 604 ++ lib/bbackupquery/Makefile.extra | 6 + lib/bbackupquery/documentation.txt | 194 + lib/bbackupquery/makedocumentation.pl.in | 75 + lib/bbstored/BBStoreDHousekeeping.cpp | 261 + lib/bbstored/BackupStoreDaemon.cpp | 377 ++ lib/bbstored/BackupStoreDaemon.h | 101 + lib/common/Archive.h | 230 + lib/common/BannerText.h | 22 + lib/common/BeginStructPackForWire.h | 23 + lib/common/Box.h | 201 + lib/common/BoxConfig-MSVC.h | 408 ++ lib/common/BoxException.cpp | 21 + lib/common/BoxException.h | 39 + lib/common/BoxPlatform.h | 160 + lib/common/BoxPortsAndFiles.h.in | 47 + lib/common/BoxTime.cpp | 150 + lib/common/BoxTime.h | 53 + lib/common/BoxTimeToText.cpp | 76 + lib/common/BoxTimeToText.h | 19 + lib/common/BoxTimeToUnix.h | 34 + lib/common/BufferedStream.cpp | 209 + lib/common/BufferedStream.h | 48 + lib/common/BufferedWriteStream.cpp | 181 + lib/common/BufferedWriteStream.h | 45 + lib/common/CollectInBufferStream.cpp | 274 + lib/common/CollectInBufferStream.h | 67 + lib/common/CommonException.h | 17 + lib/common/CommonException.txt | 59 + lib/common/Configuration.cpp | 926 +++ lib/common/Configuration.h | 156 + lib/common/Conversion.h | 98 + lib/common/ConversionException.txt | 8 + lib/common/ConversionString.cpp | 129 + lib/common/Database.h | 31 + lib/common/DebugAssertFailed.cpp | 37 + lib/common/DebugMemLeakFinder.cpp | 726 +++ lib/common/DebugPrintf.cpp | 83 + lib/common/EndStructPackForWire.h | 23 + lib/common/ExcludeList.cpp | 482 ++ lib/common/ExcludeList.h | 76 + lib/common/FdGetLine.cpp | 142 + lib/common/FdGetLine.h | 50 + lib/common/FileModificationTime.cpp | 70 + lib/common/FileModificationTime.h | 22 + lib/common/FileStream.cpp | 465 ++ lib/common/FileStream.h | 72 + lib/common/GetLine.cpp | 176 + lib/common/GetLine.h | 67 + lib/common/Guards.h | 142 + lib/common/IOStream.cpp | 274 + lib/common/IOStream.h | 73 + lib/common/IOStreamGetLine.cpp | 127 + lib/common/IOStreamGetLine.h | 67 + lib/common/InvisibleTempFileStream.cpp | 40 + lib/common/InvisibleTempFileStream.h | 35 + lib/common/Logging.cpp | 766 +++ lib/common/Logging.h | 672 +++ lib/common/MainHelper.h | 50 + lib/common/Makefile.extra | 11 + lib/common/MemBlockStream.cpp | 268 + lib/common/MemBlockStream.h | 60 + lib/common/MemLeakFindOff.h | 27 + lib/common/MemLeakFindOn.h | 26 + lib/common/MemLeakFinder.h | 68 + lib/common/NamedLock.cpp | 299 + lib/common/NamedLock.h | 50 + lib/common/PartialReadStream.cpp | 138 + lib/common/PartialReadStream.h | 47 + lib/common/PathUtils.cpp | 34 + lib/common/PathUtils.h | 26 + lib/common/RateLimitingStream.cpp | 95 + lib/common/RateLimitingStream.h | 72 + lib/common/ReadGatherStream.cpp | 263 + lib/common/ReadGatherStream.h | 68 + lib/common/ReadLoggingStream.cpp | 203 + lib/common/ReadLoggingStream.h | 59 + lib/common/SelfFlushingStream.h | 78 + lib/common/StreamableMemBlock.cpp | 371 ++ lib/common/StreamableMemBlock.h | 72 + lib/common/Test.cpp | 640 +++ lib/common/Test.h | 254 + lib/common/Timer.cpp | 672 +++ lib/common/Timer.h | 93 + lib/common/UnixUser.cpp | 123 + lib/common/UnixUser.h | 37 + lib/common/Utils.cpp | 410 ++ lib/common/Utils.h | 48 + lib/common/WaitForEvent.cpp | 197 + lib/common/WaitForEvent.h | 152 + lib/common/ZeroStream.cpp | 170 + lib/common/ZeroStream.h | 40 + lib/common/makeexception.pl.in | 250 + lib/compress/Compress.h | 197 + lib/compress/CompressException.h | 17 + lib/compress/CompressException.txt | 12 + lib/compress/CompressStream.cpp | 425 ++ lib/compress/CompressStream.h | 64 + lib/compress/Makefile.extra | 7 + lib/crypto/CipherAES.cpp | 163 + lib/crypto/CipherAES.h | 59 + lib/crypto/CipherBlowfish.cpp | 220 + lib/crypto/CipherBlowfish.h | 69 + lib/crypto/CipherContext.cpp | 640 +++ lib/crypto/CipherContext.h | 91 + lib/crypto/CipherDescription.cpp | 73 + lib/crypto/CipherDescription.h | 76 + lib/crypto/CipherException.h | 17 + lib/crypto/CipherException.txt | 18 + lib/crypto/CryptoUtils.cpp | 46 + lib/crypto/CryptoUtils.h | 27 + lib/crypto/MD5Digest.cpp | 82 + lib/crypto/MD5Digest.h | 57 + lib/crypto/Makefile.extra | 7 + lib/crypto/Random.cpp | 128 + lib/crypto/Random.h | 25 + lib/crypto/RollingChecksum.cpp | 62 + lib/crypto/RollingChecksum.h | 107 + lib/httpserver/HTTPException.txt | 17 + lib/httpserver/HTTPQueryDecoder.cpp | 159 + lib/httpserver/HTTPQueryDecoder.h | 47 + lib/httpserver/HTTPRequest.cpp | 813 +++ lib/httpserver/HTTPRequest.h | 194 + lib/httpserver/HTTPResponse.cpp | 649 +++ lib/httpserver/HTTPResponse.h | 177 + lib/httpserver/HTTPServer.cpp | 256 + lib/httpserver/HTTPServer.h | 82 + lib/httpserver/Makefile.extra | 7 + lib/httpserver/S3Client.cpp | 292 + lib/httpserver/S3Client.h | 78 + lib/httpserver/S3Simulator.cpp | 312 + lib/httpserver/S3Simulator.h | 47 + lib/httpserver/cdecode.cpp | 92 + lib/httpserver/cdecode.h | 28 + lib/httpserver/cencode.cpp | 113 + lib/httpserver/cencode.h | 32 + lib/httpserver/decode.h | 77 + lib/httpserver/encode.h | 87 + lib/intercept/intercept.cpp | 672 +++ lib/intercept/intercept.h | 66 + lib/raidfile/Makefile.extra | 7 + lib/raidfile/RaidFileController.cpp | 232 + lib/raidfile/RaidFileController.h | 108 + lib/raidfile/RaidFileException.h | 17 + lib/raidfile/RaidFileException.txt | 28 + lib/raidfile/RaidFileRead.cpp | 1789 ++++++ lib/raidfile/RaidFileRead.h | 87 + lib/raidfile/RaidFileUtil.cpp | 202 + lib/raidfile/RaidFileUtil.h | 97 + lib/raidfile/RaidFileWrite.cpp | 992 ++++ lib/raidfile/RaidFileWrite.h | 79 + lib/raidfile/raidfile-config.in | 100 + lib/server/ConnectionException.txt | 28 + lib/server/Daemon.cpp | 989 ++++ lib/server/Daemon.h | 125 + lib/server/LocalProcessStream.cpp | 180 + lib/server/LocalProcessStream.h | 20 + lib/server/Makefile.extra | 11 + lib/server/Message.cpp | 125 + lib/server/Message.h | 70 + lib/server/OverlappedIO.h | 42 + lib/server/Protocol.cpp | 1193 ++++ lib/server/Protocol.h | 213 + lib/server/ProtocolUncertainStream.cpp | 207 + lib/server/ProtocolUncertainStream.h | 48 + lib/server/ProtocolWire.h | 43 + lib/server/SSLLib.cpp | 94 + lib/server/SSLLib.h | 35 + lib/server/ServerControl.cpp | 290 + lib/server/ServerControl.h | 22 + lib/server/ServerException.txt | 39 + lib/server/ServerStream.h | 446 ++ lib/server/ServerTLS.h | 81 + lib/server/Socket.cpp | 171 + lib/server/Socket.h | 56 + lib/server/SocketListen.h | 332 ++ lib/server/SocketStream.cpp | 571 ++ lib/server/SocketStream.h | 131 + lib/server/SocketStreamTLS.cpp | 460 ++ lib/server/SocketStreamTLS.h | 62 + lib/server/TLSContext.cpp | 132 + lib/server/TLSContext.h | 41 + lib/server/TcpNice.cpp | 235 + lib/server/TcpNice.h | 178 + lib/server/WinNamedPipeListener.h | 232 + lib/server/WinNamedPipeStream.cpp | 599 ++ lib/server/WinNamedPipeStream.h | 112 + lib/server/makeprotocol.pl.in | 1352 +++++ lib/win32/MSG00001.bin | Bin 0 -> 32 bytes lib/win32/box_getopt.h | 14 + lib/win32/bsd_getopt.h | 105 + lib/win32/emu.cpp | 2033 +++++++ lib/win32/emu.h | 433 ++ lib/win32/emu_winver.h | 37 + lib/win32/getopt_long.cpp | 546 ++ lib/win32/messages.h | 57 + lib/win32/messages.mc | 22 + lib/win32/messages.rc | 2 + modules.txt | 54 + parcels.txt | 90 + qdbm/COPYING | 504 ++ qdbm/ChangeLog | 990 ++++ qdbm/LTmakefile.in | 318 ++ qdbm/Makefile.in | 646 +++ qdbm/NEWS | 43 + qdbm/NO-AUTO-GEN | 0 qdbm/README | 50 + qdbm/RISCmakefile | 140 + qdbm/THANKS | 45 + qdbm/VCmakefile | 248 + qdbm/cabin.c | 3529 ++++++++++++ qdbm/cabin.h | 1544 +++++ qdbm/cbcodec.c | 1079 ++++ qdbm/cbtest.c | 924 +++ qdbm/configure.in | 313 + qdbm/crmgr.c | 956 ++++ qdbm/crtest.c | 873 +++ qdbm/crtsv.c | 266 + qdbm/curia.c | 1192 ++++ qdbm/curia.h | 474 ++ qdbm/depot.c | 2219 ++++++++ qdbm/depot.h | 492 ++ qdbm/dpmgr.c | 916 +++ qdbm/dptest.c | 836 +++ qdbm/dptsv.c | 261 + qdbm/hovel.c | 568 ++ qdbm/hovel.h | 278 + qdbm/hvmgr.c | 582 ++ qdbm/hvtest.c | 272 + qdbm/misc/COPYING.txt | 504 ++ qdbm/misc/README-win32.txt | 101 + qdbm/misc/VCmakefile-old | 169 + qdbm/misc/benchmark.pdf | Bin 0 -> 52196 bytes qdbm/misc/icon16.png | Bin 0 -> 339 bytes qdbm/misc/icon20.png | Bin 0 -> 275 bytes qdbm/misc/index.html | 202 + qdbm/misc/index.ja.html | 209 + qdbm/misc/logo.png | Bin 0 -> 11430 bytes qdbm/misc/makevcdef | 48 + qdbm/misc/mymemo-ja.html | 34 + qdbm/misc/tutorial-ja.html | 622 ++ qdbm/misc/win32check.bat | 111 + qdbm/myconf.c | 1113 ++++ qdbm/myconf.h | 593 ++ qdbm/odeum.c | 2090 +++++++ qdbm/odeum.h | 590 ++ qdbm/odidx.c | 890 +++ qdbm/odmgr.c | 1085 ++++ qdbm/odtest.c | 694 +++ qdbm/qdbm.def | 424 ++ qdbm/qdbm.pc.in | 14 + qdbm/qdbm.spec.in | 218 + qdbm/qmttest.c | 300 + qdbm/relic.c | 266 + qdbm/relic.h | 170 + qdbm/rlmgr.c | 465 ++ qdbm/rltest.c | 241 + qdbm/spex-ja.html | 4348 ++++++++++++++ qdbm/spex.html | 4343 ++++++++++++++ qdbm/villa.c | 2666 +++++++++ qdbm/villa.h | 758 +++ qdbm/vista.c | 171 + qdbm/vista.h | 138 + qdbm/vlmgr.c | 968 ++++ qdbm/vltest.c | 1507 +++++ qdbm/vltsv.c | 261 + runtest.pl.in | 263 + test/backupdiff/difftestfiles.cpp | 295 + test/backupdiff/testbackupdiff.cpp | 606 ++ test/backupdiff/testextra | 2 + test/backupstore/testbackupstore.cpp | 3305 +++++++++++ test/backupstore/testextra | 4 + test/backupstore/testfiles/accounts.txt | 0 test/backupstore/testfiles/bbackupd.keys | Bin 0 -> 1024 bytes test/backupstore/testfiles/bbstored.conf | 17 + test/backupstore/testfiles/bbstored_multi.conf | 16 + test/backupstore/testfiles/clientCerts.pem | 11 + test/backupstore/testfiles/clientPrivKey.pem | 15 + test/backupstore/testfiles/clientReq.pem | 10 + test/backupstore/testfiles/clientTrustedCAs.pem | 11 + test/backupstore/testfiles/query.conf | 35 + test/backupstore/testfiles/raidfile.conf | 10 + test/backupstore/testfiles/root.pem | 26 + test/backupstore/testfiles/root.srl | 1 + test/backupstore/testfiles/rootcert.pem | 11 + test/backupstore/testfiles/rootkey.pem | 15 + test/backupstore/testfiles/rootreq.pem | 10 + test/backupstore/testfiles/serverCerts.pem | 11 + test/backupstore/testfiles/serverPrivKey.pem | 15 + test/backupstore/testfiles/serverReq.pem | 10 + test/backupstore/testfiles/serverTrustedCAs.pem | 11 + test/backupstorefix/testbackupstorefix.cpp | 1061 ++++ test/backupstorefix/testextra | 5 + .../testfiles/testbackupstorefix.pl.in | 237 + test/backupstorepatch/testbackupstorepatch.cpp | 677 +++ test/backupstorepatch/testextra | 6 + test/basicserver/Makefile.extra | 12 + test/basicserver/TestCommands.cpp | 108 + test/basicserver/TestContext.cpp | 16 + test/basicserver/TestContext.h | 7 + test/basicserver/testbasicserver.cpp | 777 +++ test/basicserver/testfiles/clientCerts.pem | 14 + test/basicserver/testfiles/clientPrivKey.pem | 15 + test/basicserver/testfiles/clientReq.pem | 11 + test/basicserver/testfiles/clientTrustedCAs.pem | 14 + test/basicserver/testfiles/key-creation.txt | 83 + test/basicserver/testfiles/root.pem | 29 + test/basicserver/testfiles/root.srl | 1 + test/basicserver/testfiles/rootcert.pem | 14 + test/basicserver/testfiles/rootkey.pem | 15 + test/basicserver/testfiles/rootreq.pem | 11 + test/basicserver/testfiles/serverCerts.pem | 14 + test/basicserver/testfiles/serverPrivKey.pem | 15 + test/basicserver/testfiles/serverReq.pem | 11 + test/basicserver/testfiles/serverTrustedCAs.pem | 14 + test/basicserver/testfiles/srv1.conf | 6 + test/basicserver/testfiles/srv1b.conf | 6 + test/basicserver/testfiles/srv2.conf | 6 + test/basicserver/testfiles/srv3.conf | 9 + test/basicserver/testfiles/srv4.conf | 6 + test/basicserver/testprotocol.txt | 42 + test/bbackupd/testbbackupd.cpp | 4115 ++++++++++++++ test/bbackupd/testextra | 4 + test/bbackupd/testfiles/accounts.txt | 0 test/bbackupd/testfiles/bbackupd-exclude.conf.in | 47 + test/bbackupd/testfiles/bbackupd-snapshot.conf.in | 57 + test/bbackupd/testfiles/bbackupd-symlink.conf.in | 55 + test/bbackupd/testfiles/bbackupd-temploc.conf.in | 58 + test/bbackupd/testfiles/bbackupd.conf.in | 56 + test/bbackupd/testfiles/bbackupd.keys | Bin 0 -> 1024 bytes test/bbackupd/testfiles/bbstored.conf | 17 + test/bbackupd/testfiles/clientCerts.pem | 11 + test/bbackupd/testfiles/clientPrivKey.pem | 15 + test/bbackupd/testfiles/clientTrustedCAs.pem | 11 + test/bbackupd/testfiles/extcheck1.pl.in | 61 + test/bbackupd/testfiles/extcheck2.pl.in | 53 + test/bbackupd/testfiles/notifyscript.pl.in | 24 + test/bbackupd/testfiles/raidfile.conf | 10 + test/bbackupd/testfiles/serverCerts.pem | 11 + test/bbackupd/testfiles/serverPrivKey.pem | 15 + test/bbackupd/testfiles/serverTrustedCAs.pem | 11 + test/bbackupd/testfiles/spacetest1.tgz | Bin 0 -> 288 bytes test/bbackupd/testfiles/spacetest2.tgz | Bin 0 -> 203 bytes test/bbackupd/testfiles/syncallowscript.pl.in | 33 + test/bbackupd/testfiles/test2.tgz | Bin 0 -> 25190 bytes test/bbackupd/testfiles/test3.tgz | Bin 0 -> 44957 bytes test/bbackupd/testfiles/test_base.tgz | Bin 0 -> 14950 bytes test/bbackupd/testfiles/testexclude.tgz | Bin 0 -> 377 bytes test/common/testcommon.cpp | 924 +++ test/common/testfiles/config1.txt | 40 + test/common/testfiles/config10.txt | 37 + test/common/testfiles/config11.txt | 39 + test/common/testfiles/config12.txt | 33 + test/common/testfiles/config13.txt | 15 + test/common/testfiles/config14.txt | 41 + test/common/testfiles/config15.txt | 45 + test/common/testfiles/config16.txt | 42 + test/common/testfiles/config2.txt | 39 + test/common/testfiles/config3.txt | 39 + test/common/testfiles/config4.txt | 40 + test/common/testfiles/config5.txt | 37 + test/common/testfiles/config6.txt | 39 + test/common/testfiles/config7.txt | 39 + test/common/testfiles/config8.txt | 37 + test/common/testfiles/config9.txt | 38 + test/common/testfiles/config9b.txt | 38 + test/common/testfiles/config9c.txt | 38 + test/common/testfiles/config9d.txt | 38 + test/common/testfiles/fdgetlinetest.txt | 20 + test/compress/testcompress.cpp | 262 + test/crypto/testcrypto.cpp | 313 + test/httpserver/testfiles/httpserver.conf | 8 + test/httpserver/testfiles/photos/puppy.jpg | 1 + test/httpserver/testfiles/s3simulator.conf | 10 + test/httpserver/testfiles/testrequests.pl | 145 + test/httpserver/testhttpserver.cpp | 493 ++ test/raidfile/testextra | 7 + test/raidfile/testfiles/raidfile.conf | 30 + test/raidfile/testraidfile.cpp | 985 ++++ test/s3store/testextra | 4 + test/s3store/testfiles/bbackupd.conf | 61 + test/s3store/testfiles/bbackupd.keys | Bin 0 -> 1024 bytes test/s3store/testfiles/clientTrustedCAs.pem | 11 + test/s3store/testfiles/s3simulator.conf | 10 + test/s3store/testfiles/serverCerts.pem | 11 + test/s3store/testfiles/serverPrivKey.pem | 15 + test/s3store/testfiles/serverReq.pem | 10 + .../testfiles/store/subdir/dirs/create-me.txt | 0 test/s3store/tests3store.cpp | 128 + test/win32/Makefile | 5 + test/win32/testlibwin32.cpp | 345 ++ test/win32/timezone.cpp | 87 + 869 files changed, 292990 insertions(+) create mode 100644 .gitignore create mode 100644 .hgignore create mode 100644 .travis.yml create mode 100644 BUGS.txt create mode 100644 COPYING.txt create mode 100644 LICENSE-DUAL.txt create mode 100644 LICENSE-GPL.txt create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 VERSION.txt create mode 100644 appveyor.yml create mode 100644 bin/bbackupctl/bbackupctl.cpp create mode 100755 bin/bbackupd/bbackupd-config.in create mode 100644 bin/bbackupd/bbackupd.cpp create mode 100644 bin/bbackupd/win32/NotifySysAdmin.vbs create mode 100644 bin/bbackupd/win32/bbackupd.conf create mode 100644 bin/bbackupd/win32/installer.iss create mode 100644 bin/bbackupobjdump/bbackupobjdump.cpp create mode 100644 bin/bbackupquery/bbackupquery.cpp create mode 100644 bin/bbstoreaccounts/bbstoreaccounts.cpp create mode 100755 bin/bbstored/bbstored-certs.in create mode 100755 bin/bbstored/bbstored-config.in create mode 100644 bin/bbstored/bbstored.cpp create mode 100644 bin/s3simulator/s3simulator.cpp create mode 100755 bootstrap create mode 100755 cleanupforcvs.pl create mode 100644 configure.ac create mode 100755 contrib/bbadmin/accounts.cgi create mode 100644 contrib/bbadmin/apache.conf create mode 100644 contrib/bbadmin/bb.css create mode 100644 contrib/bbreporter/LICENSE create mode 100755 contrib/bbreporter/bbreporter.py create mode 100644 contrib/debian/README.txt create mode 100644 contrib/debian/bbackupd.in create mode 100644 contrib/debian/bbstored.in create mode 100644 contrib/mac_osx/org.boxbackup.bbackupd.plist.in create mode 100644 contrib/mac_osx/org.boxbackup.bbstored.plist.in create mode 100644 contrib/redhat/README.txt create mode 100644 contrib/redhat/bbackupd.in create mode 100644 contrib/redhat/bbstored.in create mode 100644 contrib/rpm/README.txt create mode 100644 contrib/rpm/boxbackup.spec create mode 100644 contrib/solaris/bbackupd-manifest.xml.in create mode 100755 contrib/solaris/bbackupd-smf-method.in create mode 100644 contrib/solaris/bbstored-manifest.xml.in create mode 100755 contrib/solaris/bbstored-smf-method.in create mode 100644 contrib/suse/README.txt create mode 100644 contrib/suse/bbackupd.in create mode 100644 contrib/suse/bbstored.in create mode 100644 contrib/suse/bbstored.service create mode 100644 contrib/windows/installer/bbackupd.conf.template create mode 100755 contrib/windows/installer/boxbackup.mpi.in create mode 100755 contrib/windows/installer/tools/InstallService.bat create mode 100755 contrib/windows/installer/tools/KillBackupProcess.bat create mode 100755 contrib/windows/installer/tools/QueryOutputAll.bat create mode 100755 contrib/windows/installer/tools/QueryOutputCurrent.bat create mode 100755 contrib/windows/installer/tools/ReloadConfig.bat create mode 100755 contrib/windows/installer/tools/RemoveService.bat create mode 100755 contrib/windows/installer/tools/RestartService.bat create mode 100755 contrib/windows/installer/tools/ShowUsage.bat create mode 100755 contrib/windows/installer/tools/StartService.bat create mode 100755 contrib/windows/installer/tools/StopService.bat create mode 100755 contrib/windows/installer/tools/Sync.bat create mode 100644 distribution/COMMON-MANIFEST.txt create mode 100644 distribution/boxbackup/CONTACT.txt create mode 100644 distribution/boxbackup/DISTRIBUTION-MANIFEST.txt create mode 100644 distribution/boxbackup/DOCUMENTATION.txt create mode 100644 distribution/boxbackup/LINUX.txt create mode 100644 distribution/boxbackup/NETBSD.txt create mode 100644 distribution/boxbackup/THANKS.txt create mode 100644 distribution/boxbackup/VERSION.txt create mode 100644 docs/Makefile create mode 100644 docs/api-notes/INDEX.txt create mode 100644 docs/api-notes/Win32_Clients.txt create mode 100644 docs/api-notes/backup_encryption.txt create mode 100644 docs/api-notes/bin_bbackupd.txt create mode 100644 docs/api-notes/bin_bbstored.txt create mode 100644 docs/api-notes/common/lib_common.txt create mode 100644 docs/api-notes/common/lib_common/BoxTime.txt create mode 100644 docs/api-notes/common/lib_common/CollectInBufferStream.txt create mode 100644 docs/api-notes/common/lib_common/Configuration.txt create mode 100644 docs/api-notes/common/lib_common/Conversion.txt create mode 100644 docs/api-notes/common/lib_common/ExcludeList.txt create mode 100644 docs/api-notes/common/lib_common/FdGetLine.txt create mode 100644 docs/api-notes/common/lib_common/Guards.txt create mode 100644 docs/api-notes/common/lib_common/IOStream.txt create mode 100644 docs/api-notes/common/lib_common/IOStreamGetLine.txt create mode 100644 docs/api-notes/common/lib_common/MainHelper.txt create mode 100644 docs/api-notes/common/lib_common/WaitForEvent.txt create mode 100644 docs/api-notes/common/lib_common/xStream.txt create mode 100644 docs/api-notes/common/lib_compress.txt create mode 100644 docs/api-notes/common/lib_compress/CompressStream.txt create mode 100644 docs/api-notes/common/lib_crypto.txt create mode 100644 docs/api-notes/common/lib_crypto/CipherContext.txt create mode 100644 docs/api-notes/common/lib_crypto/RollingChecksum.txt create mode 100644 docs/api-notes/common/lib_server.txt create mode 100644 docs/api-notes/common/lib_server/Daemon.txt create mode 100644 docs/api-notes/common/lib_server/Protocol.txt create mode 100644 docs/api-notes/common/lib_server/ServerStream.txt create mode 100644 docs/api-notes/common/lib_server/ServerTLS.txt create mode 100644 docs/api-notes/common/lib_server/SocketStream.txt create mode 100644 docs/api-notes/common/lib_server/SocketStreamTLS.txt create mode 100644 docs/api-notes/common/lib_server/TLSContext.txt create mode 100644 docs/api-notes/common/memory_leaks.txt create mode 100644 docs/api-notes/encrypt_rsync.txt create mode 100644 docs/api-notes/lib_backupclient.txt create mode 100644 docs/api-notes/lib_backupstore.txt create mode 100644 docs/api-notes/raidfile/RaidFileRead.txt create mode 100644 docs/api-notes/raidfile/RaidFileWrite.txt create mode 100644 docs/api-notes/raidfile/lib_raidfile.txt create mode 100644 docs/api-notes/win32_build_on_cygwin_using_mingw.txt create mode 100644 docs/api-notes/win32_build_on_linux_using_mingw.txt create mode 100644 docs/api-notes/windows_porting.txt create mode 100644 docs/docbook/adminguide.xml create mode 100644 docs/docbook/bb-book.xsl create mode 100644 docs/docbook/bb-man.xsl create mode 100644 docs/docbook/bb-nochunk-book.xsl create mode 100644 docs/docbook/bbackupctl.xml create mode 100644 docs/docbook/bbackupd-config.xml create mode 100644 docs/docbook/bbackupd.conf.xml create mode 100644 docs/docbook/bbackupd.xml create mode 100644 docs/docbook/bbackupquery.xml create mode 100644 docs/docbook/bbstoreaccounts.xml create mode 100644 docs/docbook/bbstored-certs.xml create mode 100644 docs/docbook/bbstored-config.xml create mode 100644 docs/docbook/bbstored.conf.xml create mode 100644 docs/docbook/bbstored.xml create mode 100644 docs/docbook/html/bbdoc-man.css create mode 100644 docs/docbook/html/bbdoc.css create mode 100644 docs/docbook/html/favicon.ico create mode 100644 docs/docbook/html/images/arrow.png create mode 100644 docs/docbook/html/images/bblogo.png create mode 100644 docs/docbook/html/images/stepahead.png create mode 100644 docs/docbook/instguide.xml create mode 100644 docs/docbook/raidfile-config.xml create mode 100644 docs/docbook/raidfile.conf.xml create mode 100644 docs/images/bblogo-alpha.xcf create mode 100644 docs/images/box-alpha.png create mode 100644 docs/images/box-alpha.xcf create mode 100644 docs/tools/generate_except_xml.pl create mode 100644 docs/xsl-generic/VERSION create mode 100644 docs/xsl-generic/common/af.xml create mode 100644 docs/xsl-generic/common/am.xml create mode 100644 docs/xsl-generic/common/ar.xml create mode 100644 docs/xsl-generic/common/autoidx-kimber.xsl create mode 100644 docs/xsl-generic/common/autoidx-kosek.xsl create mode 100644 docs/xsl-generic/common/az.xml create mode 100644 docs/xsl-generic/common/bg.xml create mode 100644 docs/xsl-generic/common/bn.xml create mode 100644 docs/xsl-generic/common/bs.xml create mode 100644 docs/xsl-generic/common/ca.xml create mode 100644 docs/xsl-generic/common/charmap.xml create mode 100644 docs/xsl-generic/common/charmap.xsl create mode 100644 docs/xsl-generic/common/common.xml create mode 100644 docs/xsl-generic/common/common.xsl create mode 100644 docs/xsl-generic/common/cs.xml create mode 100644 docs/xsl-generic/common/cy.xml create mode 100644 docs/xsl-generic/common/da.xml create mode 100644 docs/xsl-generic/common/de.xml create mode 100644 docs/xsl-generic/common/el.xml create mode 100644 docs/xsl-generic/common/en.xml create mode 100644 docs/xsl-generic/common/entities.ent create mode 100644 docs/xsl-generic/common/eo.xml create mode 100644 docs/xsl-generic/common/es.xml create mode 100644 docs/xsl-generic/common/et.xml create mode 100644 docs/xsl-generic/common/eu.xml create mode 100644 docs/xsl-generic/common/fa.xml create mode 100644 docs/xsl-generic/common/fi.xml create mode 100644 docs/xsl-generic/common/fr.xml create mode 100644 docs/xsl-generic/common/ga.xml create mode 100644 docs/xsl-generic/common/gentext.xsl create mode 100644 docs/xsl-generic/common/gu.xml create mode 100644 docs/xsl-generic/common/he.xml create mode 100644 docs/xsl-generic/common/hi.xml create mode 100644 docs/xsl-generic/common/hr.xml create mode 100644 docs/xsl-generic/common/hu.xml create mode 100644 docs/xsl-generic/common/id.xml create mode 100644 docs/xsl-generic/common/insertfile.xsl create mode 100644 docs/xsl-generic/common/it.xml create mode 100644 docs/xsl-generic/common/ja.xml create mode 100644 docs/xsl-generic/common/kn.xml create mode 100644 docs/xsl-generic/common/ko.xml create mode 100644 docs/xsl-generic/common/l10n.dtd create mode 100644 docs/xsl-generic/common/l10n.xml create mode 100644 docs/xsl-generic/common/l10n.xsl create mode 100644 docs/xsl-generic/common/la.xml create mode 100644 docs/xsl-generic/common/labels.xsl create mode 100644 docs/xsl-generic/common/lt.xml create mode 100644 docs/xsl-generic/common/lv.xml create mode 100644 docs/xsl-generic/common/mn.xml create mode 100644 docs/xsl-generic/common/nl.xml create mode 100644 docs/xsl-generic/common/nn.xml create mode 100644 docs/xsl-generic/common/no.xml create mode 100644 docs/xsl-generic/common/olink.xsl create mode 100644 docs/xsl-generic/common/or.xml create mode 100644 docs/xsl-generic/common/pa.xml create mode 100644 docs/xsl-generic/common/pi.xsl create mode 100644 docs/xsl-generic/common/pl.xml create mode 100644 docs/xsl-generic/common/pt.xml create mode 100644 docs/xsl-generic/common/pt_br.xml create mode 100644 docs/xsl-generic/common/refentry.xml create mode 100644 docs/xsl-generic/common/refentry.xsl create mode 100644 docs/xsl-generic/common/ro.xml create mode 100644 docs/xsl-generic/common/ru.xml create mode 100644 docs/xsl-generic/common/sk.xml create mode 100644 docs/xsl-generic/common/sl.xml create mode 100644 docs/xsl-generic/common/sq.xml create mode 100644 docs/xsl-generic/common/sr.xml create mode 100644 docs/xsl-generic/common/sr_Latn.xml create mode 100644 docs/xsl-generic/common/stripns.xsl create mode 100644 docs/xsl-generic/common/subtitles.xsl create mode 100644 docs/xsl-generic/common/sv.xml create mode 100644 docs/xsl-generic/common/ta.xml create mode 100644 docs/xsl-generic/common/table.xsl create mode 100644 docs/xsl-generic/common/targetdatabase.dtd create mode 100644 docs/xsl-generic/common/targets.xsl create mode 100644 docs/xsl-generic/common/th.xml create mode 100644 docs/xsl-generic/common/titles.xsl create mode 100644 docs/xsl-generic/common/tl.xml create mode 100644 docs/xsl-generic/common/tr.xml create mode 100644 docs/xsl-generic/common/uk.xml create mode 100644 docs/xsl-generic/common/utility.xml create mode 100644 docs/xsl-generic/common/utility.xsl create mode 100644 docs/xsl-generic/common/vi.xml create mode 100644 docs/xsl-generic/common/xh.xml create mode 100644 docs/xsl-generic/common/zh_cn.xml create mode 100644 docs/xsl-generic/common/zh_tw.xml create mode 100644 docs/xsl-generic/highlighting/c-hl.xml create mode 100644 docs/xsl-generic/highlighting/common.xsl create mode 100644 docs/xsl-generic/highlighting/delphi-hl.xml create mode 100644 docs/xsl-generic/highlighting/ini-hl.xml create mode 100644 docs/xsl-generic/highlighting/java-hl.xml create mode 100644 docs/xsl-generic/highlighting/m2-hl.xml create mode 100644 docs/xsl-generic/highlighting/myxml-hl.xml create mode 100644 docs/xsl-generic/highlighting/php-hl.xml create mode 100644 docs/xsl-generic/highlighting/xslthl-config.xml create mode 100644 docs/xsl-generic/html/admon.xsl create mode 100644 docs/xsl-generic/html/annotations.xsl create mode 100644 docs/xsl-generic/html/autoidx-kimber.xsl create mode 100644 docs/xsl-generic/html/autoidx-kosek.xsl create mode 100644 docs/xsl-generic/html/autoidx-ng.xsl create mode 100644 docs/xsl-generic/html/autoidx.xsl create mode 100644 docs/xsl-generic/html/autotoc.xsl create mode 100644 docs/xsl-generic/html/biblio-iso690.xsl create mode 100644 docs/xsl-generic/html/biblio.xsl create mode 100644 docs/xsl-generic/html/block.xsl create mode 100644 docs/xsl-generic/html/callout.xsl create mode 100644 docs/xsl-generic/html/changebars.xsl create mode 100644 docs/xsl-generic/html/chunk-code.xsl create mode 100644 docs/xsl-generic/html/chunk-common.xsl create mode 100644 docs/xsl-generic/html/chunk.xsl create mode 100644 docs/xsl-generic/html/chunker.xsl create mode 100644 docs/xsl-generic/html/chunkfast.xsl create mode 100644 docs/xsl-generic/html/chunktoc.xsl create mode 100644 docs/xsl-generic/html/component.xsl create mode 100644 docs/xsl-generic/html/division.xsl create mode 100644 docs/xsl-generic/html/docbook.xsl create mode 100644 docs/xsl-generic/html/ebnf.xsl create mode 100644 docs/xsl-generic/html/footnote.xsl create mode 100644 docs/xsl-generic/html/formal.xsl create mode 100644 docs/xsl-generic/html/glossary.xsl create mode 100644 docs/xsl-generic/html/graphics.xsl create mode 100644 docs/xsl-generic/html/highlight.xsl create mode 100644 docs/xsl-generic/html/html-rtf.xsl create mode 100644 docs/xsl-generic/html/html.xsl create mode 100644 docs/xsl-generic/html/htmltbl.xsl create mode 100644 docs/xsl-generic/html/index.xsl create mode 100644 docs/xsl-generic/html/info.xsl create mode 100644 docs/xsl-generic/html/inline.xsl create mode 100644 docs/xsl-generic/html/keywords.xsl create mode 100644 docs/xsl-generic/html/lists.xsl create mode 100644 docs/xsl-generic/html/maketoc.xsl create mode 100644 docs/xsl-generic/html/manifest.xsl create mode 100644 docs/xsl-generic/html/math.xsl create mode 100644 docs/xsl-generic/html/oldchunker.xsl create mode 100644 docs/xsl-generic/html/onechunk.xsl create mode 100644 docs/xsl-generic/html/param.xsl create mode 100644 docs/xsl-generic/html/pi.xsl create mode 100644 docs/xsl-generic/html/profile-chunk-code.xsl create mode 100644 docs/xsl-generic/html/profile-chunk.xsl create mode 100644 docs/xsl-generic/html/profile-docbook.xsl create mode 100644 docs/xsl-generic/html/profile-onechunk.xsl create mode 100644 docs/xsl-generic/html/qandaset.xsl create mode 100644 docs/xsl-generic/html/refentry.xsl create mode 100644 docs/xsl-generic/html/sections.xsl create mode 100644 docs/xsl-generic/html/synop.xsl create mode 100644 docs/xsl-generic/html/table.xsl create mode 100644 docs/xsl-generic/html/task.xsl create mode 100644 docs/xsl-generic/html/titlepage.templates.xml create mode 100644 docs/xsl-generic/html/titlepage.templates.xsl create mode 100644 docs/xsl-generic/html/titlepage.xsl create mode 100644 docs/xsl-generic/html/toc.xsl create mode 100644 docs/xsl-generic/html/verbatim.xsl create mode 100644 docs/xsl-generic/html/xref.xsl create mode 100644 docs/xsl-generic/lib/lib.xsl create mode 100644 docs/xsl-generic/manpages/block.xsl create mode 100644 docs/xsl-generic/manpages/charmap.groff.xsl create mode 100644 docs/xsl-generic/manpages/docbook.xsl create mode 100644 docs/xsl-generic/manpages/endnotes.xsl create mode 100644 docs/xsl-generic/manpages/html-synop.xsl create mode 100644 docs/xsl-generic/manpages/info.xsl create mode 100644 docs/xsl-generic/manpages/inline.xsl create mode 100644 docs/xsl-generic/manpages/lists.xsl create mode 100644 docs/xsl-generic/manpages/other.xsl create mode 100644 docs/xsl-generic/manpages/param.xsl create mode 100644 docs/xsl-generic/manpages/profile-docbook.xsl create mode 100644 docs/xsl-generic/manpages/refentry.xsl create mode 100644 docs/xsl-generic/manpages/synop.xsl create mode 100644 docs/xsl-generic/manpages/table.xsl create mode 100644 docs/xsl-generic/manpages/utility.xsl create mode 100644 infrastructure/BoxPlatform.pm.in create mode 100644 infrastructure/buildenv-testmain-template.cpp create mode 100644 infrastructure/cmake/.gitignore create mode 100644 infrastructure/cmake/CMakeLists.txt create mode 100755 infrastructure/cmake/build/bin_bbackupd.vcxproj.user create mode 100755 infrastructure/cmake/build/bin_bbstored.vcxproj.user create mode 100755 infrastructure/cmake/build/test_backupstore.vcxproj.user create mode 100755 infrastructure/cmake/build/test_backupstorefix.vcxproj.user create mode 100755 infrastructure/cmake/build/test_bbackupd.vcxproj.user create mode 100755 infrastructure/cmake/build/test_common.vcxproj.user create mode 100755 infrastructure/cmake/build/test_httpserver.vcxproj.user create mode 100755 infrastructure/cmake/build/test_raidfile.vcxproj.user create mode 100755 infrastructure/config.guess create mode 100755 infrastructure/config.sub create mode 100755 infrastructure/install-sh create mode 100644 infrastructure/m4/ac_cxx_exceptions.m4 create mode 100644 infrastructure/m4/ac_cxx_namespaces.m4 create mode 100644 infrastructure/m4/ax_bswap64.m4 create mode 100644 infrastructure/m4/ax_check_bdb_v1.m4 create mode 100644 infrastructure/m4/ax_check_define_pragma.m4 create mode 100644 infrastructure/m4/ax_check_dirent_d_type.m4 create mode 100644 infrastructure/m4/ax_check_llong_minmax.m4 create mode 100644 infrastructure/m4/ax_check_malloc_workaround.m4 create mode 100644 infrastructure/m4/ax_check_mount_point.m4 create mode 100644 infrastructure/m4/ax_check_nonaligned_access.m4 create mode 100644 infrastructure/m4/ax_check_ssl.m4 create mode 100644 infrastructure/m4/ax_check_syscall_lseek.m4 create mode 100644 infrastructure/m4/ax_compare_version.m4 create mode 100644 infrastructure/m4/ax_config_scripts.m4 create mode 100644 infrastructure/m4/ax_func_syscall.m4 create mode 100644 infrastructure/m4/ax_path_bdb.m4 create mode 100644 infrastructure/m4/ax_random_device.m4 create mode 100644 infrastructure/m4/ax_split_version.m4 create mode 100644 infrastructure/m4/boxbackup_tests.m4 create mode 100644 infrastructure/m4/vl_lib_readline.m4 create mode 100755 infrastructure/makebuildenv.pl.in create mode 100755 infrastructure/makedistribution.pl.in create mode 100755 infrastructure/makeparcels.pl.in create mode 100755 infrastructure/mingw/compile-boxbackup-cygwin.sh create mode 100755 infrastructure/mingw/configure.sh create mode 100644 infrastructure/mingw/environment.sh create mode 100755 infrastructure/mingw/runtest.sh create mode 100644 infrastructure/msvc/2003/bbackupctl.vcproj create mode 100644 infrastructure/msvc/2003/bbackupd.vcproj create mode 100644 infrastructure/msvc/2003/boxbackup.sln create mode 100644 infrastructure/msvc/2003/boxquery.vcproj create mode 100644 infrastructure/msvc/2003/common.vcproj create mode 100644 infrastructure/msvc/2003/win32test.vcproj create mode 100644 infrastructure/msvc/2005/bbackupctl.vcproj create mode 100644 infrastructure/msvc/2005/bbackupd.vcproj create mode 100644 infrastructure/msvc/2005/boxbackup.sln create mode 100644 infrastructure/msvc/2005/boxbackup.suo create mode 100644 infrastructure/msvc/2005/boxquery.vcproj create mode 100644 infrastructure/msvc/2005/common.vcproj create mode 100644 infrastructure/msvc/2005/win32test.vcproj create mode 100644 infrastructure/msvc/2010/bbackupctl.vcxproj create mode 100644 infrastructure/msvc/2010/bbackupd.vcxproj create mode 100644 infrastructure/msvc/2010/bbstoreaccounts.vcxproj create mode 100644 infrastructure/msvc/2010/bbstored.vcxproj create mode 100644 infrastructure/msvc/2010/boxbackup.sln create mode 100644 infrastructure/msvc/2010/boxquery.vcxproj create mode 100644 infrastructure/msvc/2010/common.vcxproj create mode 100644 infrastructure/msvc/2010/libbackupclient.vcxproj create mode 100644 infrastructure/msvc/2010/libbackupstore.vcxproj create mode 100644 infrastructure/msvc/2010/qdbm.vcxproj create mode 100644 infrastructure/msvc/2010/win32test.vcxproj create mode 100644 infrastructure/msvc/2013/bbackupctl.vcxproj create mode 100644 infrastructure/msvc/2013/bbackupd.vcxproj create mode 100644 infrastructure/msvc/2013/bbstoreaccounts.vcxproj create mode 100644 infrastructure/msvc/2013/bbstored.vcxproj create mode 100644 infrastructure/msvc/2013/boxbackup.sln create mode 100644 infrastructure/msvc/2013/boxquery.vcxproj create mode 100644 infrastructure/msvc/2013/common.vcxproj create mode 100644 infrastructure/msvc/2013/libbackupclient.vcxproj create mode 100644 infrastructure/msvc/2013/libbackupstore.vcxproj create mode 100644 infrastructure/msvc/2013/qdbm.vcxproj create mode 100644 infrastructure/msvc/2013/win32test.vcxproj create mode 100644 infrastructure/msvc/fake-config.sub.pl create mode 100755 infrastructure/msvc/getversion.pl create mode 100644 infrastructure/msvc/win32.bat create mode 100644 infrastructure/parcelpath.pl create mode 100644 infrastructure/printversion.pl create mode 100755 infrastructure/setupexternal.pl create mode 100644 lib/backupclient/BackupClientCryptoKeys.cpp create mode 100644 lib/backupclient/BackupClientCryptoKeys.h create mode 100644 lib/backupclient/BackupClientMakeExcludeList.cpp create mode 100644 lib/backupclient/BackupClientMakeExcludeList.h create mode 100644 lib/backupclient/BackupClientRestore.cpp create mode 100644 lib/backupclient/BackupClientRestore.h create mode 100644 lib/backupclient/BackupDaemonConfigVerify.cpp create mode 100644 lib/backupclient/BackupDaemonConfigVerify.h create mode 100644 lib/backupclient/BackupStoreObjectDump.cpp create mode 100644 lib/backupclient/ClientException.txt create mode 100644 lib/backupclient/Makefile.extra create mode 100644 lib/backupstore/BackgroundTask.h create mode 100644 lib/backupstore/BackupAccountControl.cpp create mode 100644 lib/backupstore/BackupAccountControl.h create mode 100644 lib/backupstore/BackupClientFileAttributes.cpp create mode 100644 lib/backupstore/BackupClientFileAttributes.h create mode 100644 lib/backupstore/BackupCommands.cpp create mode 100644 lib/backupstore/BackupConstants.h create mode 100644 lib/backupstore/BackupProtocol.h create mode 100644 lib/backupstore/BackupStoreAccountDatabase.cpp create mode 100644 lib/backupstore/BackupStoreAccountDatabase.h create mode 100644 lib/backupstore/BackupStoreAccounts.cpp create mode 100644 lib/backupstore/BackupStoreAccounts.h create mode 100644 lib/backupstore/BackupStoreCheck.cpp create mode 100644 lib/backupstore/BackupStoreCheck.h create mode 100644 lib/backupstore/BackupStoreCheck2.cpp create mode 100644 lib/backupstore/BackupStoreCheckData.cpp create mode 100644 lib/backupstore/BackupStoreConfigVerify.cpp create mode 100644 lib/backupstore/BackupStoreConfigVerify.h create mode 100644 lib/backupstore/BackupStoreConstants.h create mode 100644 lib/backupstore/BackupStoreContext.cpp create mode 100644 lib/backupstore/BackupStoreContext.h create mode 100644 lib/backupstore/BackupStoreDirectory.cpp create mode 100644 lib/backupstore/BackupStoreDirectory.h create mode 100644 lib/backupstore/BackupStoreException.h create mode 100644 lib/backupstore/BackupStoreException.txt create mode 100644 lib/backupstore/BackupStoreFile.cpp create mode 100644 lib/backupstore/BackupStoreFile.h create mode 100644 lib/backupstore/BackupStoreFileCmbDiff.cpp create mode 100644 lib/backupstore/BackupStoreFileCmbIdx.cpp create mode 100644 lib/backupstore/BackupStoreFileCombine.cpp create mode 100644 lib/backupstore/BackupStoreFileCryptVar.cpp create mode 100644 lib/backupstore/BackupStoreFileCryptVar.h create mode 100644 lib/backupstore/BackupStoreFileDiff.cpp create mode 100644 lib/backupstore/BackupStoreFileEncodeStream.cpp create mode 100644 lib/backupstore/BackupStoreFileEncodeStream.h create mode 100644 lib/backupstore/BackupStoreFileRevDiff.cpp create mode 100644 lib/backupstore/BackupStoreFileWire.h create mode 100644 lib/backupstore/BackupStoreFilename.cpp create mode 100644 lib/backupstore/BackupStoreFilename.h create mode 100644 lib/backupstore/BackupStoreFilenameClear.cpp create mode 100644 lib/backupstore/BackupStoreFilenameClear.h create mode 100644 lib/backupstore/BackupStoreInfo.cpp create mode 100644 lib/backupstore/BackupStoreInfo.h create mode 100644 lib/backupstore/BackupStoreObjectMagic.h create mode 100644 lib/backupstore/BackupStoreRefCountDatabase.cpp create mode 100644 lib/backupstore/BackupStoreRefCountDatabase.h create mode 100644 lib/backupstore/HousekeepStoreAccount.cpp create mode 100644 lib/backupstore/HousekeepStoreAccount.h create mode 100644 lib/backupstore/Makefile.extra create mode 100644 lib/backupstore/RunStatusProvider.h create mode 100644 lib/backupstore/StoreStructure.cpp create mode 100644 lib/backupstore/StoreStructure.h create mode 100644 lib/backupstore/StoreTestUtils.cpp create mode 100644 lib/backupstore/StoreTestUtils.h create mode 100644 lib/backupstore/backupprotocol.txt create mode 100644 lib/bbackupd/BackupClientContext.cpp create mode 100644 lib/bbackupd/BackupClientContext.h create mode 100644 lib/bbackupd/BackupClientDeleteList.cpp create mode 100644 lib/bbackupd/BackupClientDeleteList.h create mode 100644 lib/bbackupd/BackupClientDirectoryRecord.cpp create mode 100644 lib/bbackupd/BackupClientDirectoryRecord.h create mode 100644 lib/bbackupd/BackupClientInodeToIDMap.cpp create mode 100644 lib/bbackupd/BackupClientInodeToIDMap.h create mode 100644 lib/bbackupd/BackupDaemon.cpp create mode 100644 lib/bbackupd/BackupDaemon.h create mode 100644 lib/bbackupd/BackupDaemonInterface.h create mode 100644 lib/bbackupd/Win32BackupService.cpp create mode 100644 lib/bbackupd/Win32BackupService.h create mode 100644 lib/bbackupd/Win32ServiceFunctions.cpp create mode 100644 lib/bbackupd/Win32ServiceFunctions.h create mode 100644 lib/bbackupquery/BackupQueries.cpp create mode 100644 lib/bbackupquery/BackupQueries.h create mode 100644 lib/bbackupquery/BoxBackupCompareParams.h create mode 100644 lib/bbackupquery/CommandCompletion.cpp create mode 100644 lib/bbackupquery/Makefile.extra create mode 100644 lib/bbackupquery/documentation.txt create mode 100755 lib/bbackupquery/makedocumentation.pl.in create mode 100644 lib/bbstored/BBStoreDHousekeeping.cpp create mode 100644 lib/bbstored/BackupStoreDaemon.cpp create mode 100644 lib/bbstored/BackupStoreDaemon.h create mode 100644 lib/common/Archive.h create mode 100644 lib/common/BannerText.h create mode 100644 lib/common/BeginStructPackForWire.h create mode 100644 lib/common/Box.h create mode 100644 lib/common/BoxConfig-MSVC.h create mode 100644 lib/common/BoxException.cpp create mode 100644 lib/common/BoxException.h create mode 100644 lib/common/BoxPlatform.h create mode 100644 lib/common/BoxPortsAndFiles.h.in create mode 100644 lib/common/BoxTime.cpp create mode 100644 lib/common/BoxTime.h create mode 100644 lib/common/BoxTimeToText.cpp create mode 100644 lib/common/BoxTimeToText.h create mode 100644 lib/common/BoxTimeToUnix.h create mode 100644 lib/common/BufferedStream.cpp create mode 100644 lib/common/BufferedStream.h create mode 100644 lib/common/BufferedWriteStream.cpp create mode 100644 lib/common/BufferedWriteStream.h create mode 100644 lib/common/CollectInBufferStream.cpp create mode 100644 lib/common/CollectInBufferStream.h create mode 100644 lib/common/CommonException.h create mode 100644 lib/common/CommonException.txt create mode 100644 lib/common/Configuration.cpp create mode 100644 lib/common/Configuration.h create mode 100644 lib/common/Conversion.h create mode 100644 lib/common/ConversionException.txt create mode 100644 lib/common/ConversionString.cpp create mode 100644 lib/common/Database.h create mode 100644 lib/common/DebugAssertFailed.cpp create mode 100644 lib/common/DebugMemLeakFinder.cpp create mode 100644 lib/common/DebugPrintf.cpp create mode 100644 lib/common/EndStructPackForWire.h create mode 100644 lib/common/ExcludeList.cpp create mode 100644 lib/common/ExcludeList.h create mode 100644 lib/common/FdGetLine.cpp create mode 100644 lib/common/FdGetLine.h create mode 100644 lib/common/FileModificationTime.cpp create mode 100644 lib/common/FileModificationTime.h create mode 100644 lib/common/FileStream.cpp create mode 100644 lib/common/FileStream.h create mode 100644 lib/common/GetLine.cpp create mode 100644 lib/common/GetLine.h create mode 100644 lib/common/Guards.h create mode 100644 lib/common/IOStream.cpp create mode 100644 lib/common/IOStream.h create mode 100644 lib/common/IOStreamGetLine.cpp create mode 100644 lib/common/IOStreamGetLine.h create mode 100644 lib/common/InvisibleTempFileStream.cpp create mode 100644 lib/common/InvisibleTempFileStream.h create mode 100644 lib/common/Logging.cpp create mode 100644 lib/common/Logging.h create mode 100644 lib/common/MainHelper.h create mode 100644 lib/common/Makefile.extra create mode 100644 lib/common/MemBlockStream.cpp create mode 100644 lib/common/MemBlockStream.h create mode 100644 lib/common/MemLeakFindOff.h create mode 100644 lib/common/MemLeakFindOn.h create mode 100644 lib/common/MemLeakFinder.h create mode 100644 lib/common/NamedLock.cpp create mode 100644 lib/common/NamedLock.h create mode 100644 lib/common/PartialReadStream.cpp create mode 100644 lib/common/PartialReadStream.h create mode 100644 lib/common/PathUtils.cpp create mode 100644 lib/common/PathUtils.h create mode 100644 lib/common/RateLimitingStream.cpp create mode 100644 lib/common/RateLimitingStream.h create mode 100644 lib/common/ReadGatherStream.cpp create mode 100644 lib/common/ReadGatherStream.h create mode 100644 lib/common/ReadLoggingStream.cpp create mode 100644 lib/common/ReadLoggingStream.h create mode 100644 lib/common/SelfFlushingStream.h create mode 100644 lib/common/StreamableMemBlock.cpp create mode 100644 lib/common/StreamableMemBlock.h create mode 100644 lib/common/Test.cpp create mode 100644 lib/common/Test.h create mode 100644 lib/common/Timer.cpp create mode 100644 lib/common/Timer.h create mode 100644 lib/common/UnixUser.cpp create mode 100644 lib/common/UnixUser.h create mode 100644 lib/common/Utils.cpp create mode 100644 lib/common/Utils.h create mode 100644 lib/common/WaitForEvent.cpp create mode 100644 lib/common/WaitForEvent.h create mode 100644 lib/common/ZeroStream.cpp create mode 100644 lib/common/ZeroStream.h create mode 100755 lib/common/makeexception.pl.in create mode 100644 lib/compress/Compress.h create mode 100644 lib/compress/CompressException.h create mode 100644 lib/compress/CompressException.txt create mode 100644 lib/compress/CompressStream.cpp create mode 100644 lib/compress/CompressStream.h create mode 100644 lib/compress/Makefile.extra create mode 100644 lib/crypto/CipherAES.cpp create mode 100644 lib/crypto/CipherAES.h create mode 100644 lib/crypto/CipherBlowfish.cpp create mode 100644 lib/crypto/CipherBlowfish.h create mode 100644 lib/crypto/CipherContext.cpp create mode 100644 lib/crypto/CipherContext.h create mode 100644 lib/crypto/CipherDescription.cpp create mode 100644 lib/crypto/CipherDescription.h create mode 100644 lib/crypto/CipherException.h create mode 100644 lib/crypto/CipherException.txt create mode 100644 lib/crypto/CryptoUtils.cpp create mode 100644 lib/crypto/CryptoUtils.h create mode 100644 lib/crypto/MD5Digest.cpp create mode 100644 lib/crypto/MD5Digest.h create mode 100644 lib/crypto/Makefile.extra create mode 100644 lib/crypto/Random.cpp create mode 100644 lib/crypto/Random.h create mode 100644 lib/crypto/RollingChecksum.cpp create mode 100644 lib/crypto/RollingChecksum.h create mode 100644 lib/httpserver/HTTPException.txt create mode 100644 lib/httpserver/HTTPQueryDecoder.cpp create mode 100644 lib/httpserver/HTTPQueryDecoder.h create mode 100644 lib/httpserver/HTTPRequest.cpp create mode 100644 lib/httpserver/HTTPRequest.h create mode 100644 lib/httpserver/HTTPResponse.cpp create mode 100644 lib/httpserver/HTTPResponse.h create mode 100644 lib/httpserver/HTTPServer.cpp create mode 100644 lib/httpserver/HTTPServer.h create mode 100644 lib/httpserver/Makefile.extra create mode 100644 lib/httpserver/S3Client.cpp create mode 100644 lib/httpserver/S3Client.h create mode 100644 lib/httpserver/S3Simulator.cpp create mode 100644 lib/httpserver/S3Simulator.h create mode 100644 lib/httpserver/cdecode.cpp create mode 100644 lib/httpserver/cdecode.h create mode 100644 lib/httpserver/cencode.cpp create mode 100644 lib/httpserver/cencode.h create mode 100644 lib/httpserver/decode.h create mode 100644 lib/httpserver/encode.h create mode 100644 lib/intercept/intercept.cpp create mode 100644 lib/intercept/intercept.h create mode 100644 lib/raidfile/Makefile.extra create mode 100644 lib/raidfile/RaidFileController.cpp create mode 100644 lib/raidfile/RaidFileController.h create mode 100644 lib/raidfile/RaidFileException.h create mode 100644 lib/raidfile/RaidFileException.txt create mode 100644 lib/raidfile/RaidFileRead.cpp create mode 100644 lib/raidfile/RaidFileRead.h create mode 100644 lib/raidfile/RaidFileUtil.cpp create mode 100644 lib/raidfile/RaidFileUtil.h create mode 100644 lib/raidfile/RaidFileWrite.cpp create mode 100644 lib/raidfile/RaidFileWrite.h create mode 100755 lib/raidfile/raidfile-config.in create mode 100644 lib/server/ConnectionException.txt create mode 100644 lib/server/Daemon.cpp create mode 100644 lib/server/Daemon.h create mode 100644 lib/server/LocalProcessStream.cpp create mode 100644 lib/server/LocalProcessStream.h create mode 100644 lib/server/Makefile.extra create mode 100644 lib/server/Message.cpp create mode 100644 lib/server/Message.h create mode 100644 lib/server/OverlappedIO.h create mode 100644 lib/server/Protocol.cpp create mode 100644 lib/server/Protocol.h create mode 100644 lib/server/ProtocolUncertainStream.cpp create mode 100644 lib/server/ProtocolUncertainStream.h create mode 100644 lib/server/ProtocolWire.h create mode 100644 lib/server/SSLLib.cpp create mode 100644 lib/server/SSLLib.h create mode 100644 lib/server/ServerControl.cpp create mode 100644 lib/server/ServerControl.h create mode 100644 lib/server/ServerException.txt create mode 100644 lib/server/ServerStream.h create mode 100644 lib/server/ServerTLS.h create mode 100644 lib/server/Socket.cpp create mode 100644 lib/server/Socket.h create mode 100644 lib/server/SocketListen.h create mode 100644 lib/server/SocketStream.cpp create mode 100644 lib/server/SocketStream.h create mode 100644 lib/server/SocketStreamTLS.cpp create mode 100644 lib/server/SocketStreamTLS.h create mode 100644 lib/server/TLSContext.cpp create mode 100644 lib/server/TLSContext.h create mode 100644 lib/server/TcpNice.cpp create mode 100644 lib/server/TcpNice.h create mode 100644 lib/server/WinNamedPipeListener.h create mode 100644 lib/server/WinNamedPipeStream.cpp create mode 100644 lib/server/WinNamedPipeStream.h create mode 100755 lib/server/makeprotocol.pl.in create mode 100755 lib/win32/MSG00001.bin create mode 100644 lib/win32/box_getopt.h create mode 100755 lib/win32/bsd_getopt.h create mode 100644 lib/win32/emu.cpp create mode 100644 lib/win32/emu.h create mode 100644 lib/win32/emu_winver.h create mode 100755 lib/win32/getopt_long.cpp create mode 100755 lib/win32/messages.h create mode 100644 lib/win32/messages.mc create mode 100755 lib/win32/messages.rc create mode 100644 modules.txt create mode 100644 parcels.txt create mode 100644 qdbm/COPYING create mode 100644 qdbm/ChangeLog create mode 100644 qdbm/LTmakefile.in create mode 100644 qdbm/Makefile.in create mode 100644 qdbm/NEWS create mode 100644 qdbm/NO-AUTO-GEN create mode 100644 qdbm/README create mode 100644 qdbm/RISCmakefile create mode 100644 qdbm/THANKS create mode 100644 qdbm/VCmakefile create mode 100644 qdbm/cabin.c create mode 100644 qdbm/cabin.h create mode 100644 qdbm/cbcodec.c create mode 100644 qdbm/cbtest.c create mode 100644 qdbm/configure.in create mode 100644 qdbm/crmgr.c create mode 100644 qdbm/crtest.c create mode 100644 qdbm/crtsv.c create mode 100644 qdbm/curia.c create mode 100644 qdbm/curia.h create mode 100644 qdbm/depot.c create mode 100644 qdbm/depot.h create mode 100644 qdbm/dpmgr.c create mode 100644 qdbm/dptest.c create mode 100644 qdbm/dptsv.c create mode 100644 qdbm/hovel.c create mode 100644 qdbm/hovel.h create mode 100644 qdbm/hvmgr.c create mode 100644 qdbm/hvtest.c create mode 100644 qdbm/misc/COPYING.txt create mode 100644 qdbm/misc/README-win32.txt create mode 100644 qdbm/misc/VCmakefile-old create mode 100644 qdbm/misc/benchmark.pdf create mode 100644 qdbm/misc/icon16.png create mode 100644 qdbm/misc/icon20.png create mode 100644 qdbm/misc/index.html create mode 100644 qdbm/misc/index.ja.html create mode 100644 qdbm/misc/logo.png create mode 100755 qdbm/misc/makevcdef create mode 100644 qdbm/misc/mymemo-ja.html create mode 100644 qdbm/misc/tutorial-ja.html create mode 100644 qdbm/misc/win32check.bat create mode 100644 qdbm/myconf.c create mode 100644 qdbm/myconf.h create mode 100644 qdbm/odeum.c create mode 100644 qdbm/odeum.h create mode 100644 qdbm/odidx.c create mode 100644 qdbm/odmgr.c create mode 100644 qdbm/odtest.c create mode 100644 qdbm/qdbm.def create mode 100644 qdbm/qdbm.pc.in create mode 100644 qdbm/qdbm.spec.in create mode 100644 qdbm/qmttest.c create mode 100644 qdbm/relic.c create mode 100644 qdbm/relic.h create mode 100644 qdbm/rlmgr.c create mode 100644 qdbm/rltest.c create mode 100644 qdbm/spex-ja.html create mode 100644 qdbm/spex.html create mode 100644 qdbm/villa.c create mode 100644 qdbm/villa.h create mode 100644 qdbm/vista.c create mode 100644 qdbm/vista.h create mode 100644 qdbm/vlmgr.c create mode 100644 qdbm/vltest.c create mode 100644 qdbm/vltsv.c create mode 100755 runtest.pl.in create mode 100644 test/backupdiff/difftestfiles.cpp create mode 100644 test/backupdiff/testbackupdiff.cpp create mode 100644 test/backupdiff/testextra create mode 100644 test/backupstore/testbackupstore.cpp create mode 100644 test/backupstore/testextra create mode 100644 test/backupstore/testfiles/accounts.txt create mode 100644 test/backupstore/testfiles/bbackupd.keys create mode 100644 test/backupstore/testfiles/bbstored.conf create mode 100644 test/backupstore/testfiles/bbstored_multi.conf create mode 100644 test/backupstore/testfiles/clientCerts.pem create mode 100644 test/backupstore/testfiles/clientPrivKey.pem create mode 100644 test/backupstore/testfiles/clientReq.pem create mode 100644 test/backupstore/testfiles/clientTrustedCAs.pem create mode 100644 test/backupstore/testfiles/query.conf create mode 100644 test/backupstore/testfiles/raidfile.conf create mode 100644 test/backupstore/testfiles/root.pem create mode 100644 test/backupstore/testfiles/root.srl create mode 100644 test/backupstore/testfiles/rootcert.pem create mode 100644 test/backupstore/testfiles/rootkey.pem create mode 100644 test/backupstore/testfiles/rootreq.pem create mode 100644 test/backupstore/testfiles/serverCerts.pem create mode 100644 test/backupstore/testfiles/serverPrivKey.pem create mode 100644 test/backupstore/testfiles/serverReq.pem create mode 100644 test/backupstore/testfiles/serverTrustedCAs.pem create mode 100644 test/backupstorefix/testbackupstorefix.cpp create mode 100644 test/backupstorefix/testextra create mode 100755 test/backupstorefix/testfiles/testbackupstorefix.pl.in create mode 100644 test/backupstorepatch/testbackupstorepatch.cpp create mode 100644 test/backupstorepatch/testextra create mode 100644 test/basicserver/Makefile.extra create mode 100644 test/basicserver/TestCommands.cpp create mode 100644 test/basicserver/TestContext.cpp create mode 100644 test/basicserver/TestContext.h create mode 100644 test/basicserver/testbasicserver.cpp create mode 100644 test/basicserver/testfiles/clientCerts.pem create mode 100644 test/basicserver/testfiles/clientPrivKey.pem create mode 100644 test/basicserver/testfiles/clientReq.pem create mode 100644 test/basicserver/testfiles/clientTrustedCAs.pem create mode 100644 test/basicserver/testfiles/key-creation.txt create mode 100644 test/basicserver/testfiles/root.pem create mode 100644 test/basicserver/testfiles/root.srl create mode 100644 test/basicserver/testfiles/rootcert.pem create mode 100644 test/basicserver/testfiles/rootkey.pem create mode 100644 test/basicserver/testfiles/rootreq.pem create mode 100644 test/basicserver/testfiles/serverCerts.pem create mode 100644 test/basicserver/testfiles/serverPrivKey.pem create mode 100644 test/basicserver/testfiles/serverReq.pem create mode 100644 test/basicserver/testfiles/serverTrustedCAs.pem create mode 100644 test/basicserver/testfiles/srv1.conf create mode 100644 test/basicserver/testfiles/srv1b.conf create mode 100644 test/basicserver/testfiles/srv2.conf create mode 100644 test/basicserver/testfiles/srv3.conf create mode 100644 test/basicserver/testfiles/srv4.conf create mode 100644 test/basicserver/testprotocol.txt create mode 100644 test/bbackupd/testbbackupd.cpp create mode 100644 test/bbackupd/testextra create mode 100644 test/bbackupd/testfiles/accounts.txt create mode 100644 test/bbackupd/testfiles/bbackupd-exclude.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-snapshot.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-symlink.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd-temploc.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd.conf.in create mode 100644 test/bbackupd/testfiles/bbackupd.keys create mode 100644 test/bbackupd/testfiles/bbstored.conf create mode 100644 test/bbackupd/testfiles/clientCerts.pem create mode 100644 test/bbackupd/testfiles/clientPrivKey.pem create mode 100644 test/bbackupd/testfiles/clientTrustedCAs.pem create mode 100755 test/bbackupd/testfiles/extcheck1.pl.in create mode 100755 test/bbackupd/testfiles/extcheck2.pl.in create mode 100755 test/bbackupd/testfiles/notifyscript.pl.in create mode 100644 test/bbackupd/testfiles/raidfile.conf create mode 100644 test/bbackupd/testfiles/serverCerts.pem create mode 100644 test/bbackupd/testfiles/serverPrivKey.pem create mode 100644 test/bbackupd/testfiles/serverTrustedCAs.pem create mode 100644 test/bbackupd/testfiles/spacetest1.tgz create mode 100644 test/bbackupd/testfiles/spacetest2.tgz create mode 100755 test/bbackupd/testfiles/syncallowscript.pl.in create mode 100644 test/bbackupd/testfiles/test2.tgz create mode 100644 test/bbackupd/testfiles/test3.tgz create mode 100644 test/bbackupd/testfiles/test_base.tgz create mode 100644 test/bbackupd/testfiles/testexclude.tgz create mode 100644 test/common/testcommon.cpp create mode 100644 test/common/testfiles/config1.txt create mode 100644 test/common/testfiles/config10.txt create mode 100644 test/common/testfiles/config11.txt create mode 100644 test/common/testfiles/config12.txt create mode 100644 test/common/testfiles/config13.txt create mode 100644 test/common/testfiles/config14.txt create mode 100644 test/common/testfiles/config15.txt create mode 100644 test/common/testfiles/config16.txt create mode 100644 test/common/testfiles/config2.txt create mode 100644 test/common/testfiles/config3.txt create mode 100644 test/common/testfiles/config4.txt create mode 100644 test/common/testfiles/config5.txt create mode 100644 test/common/testfiles/config6.txt create mode 100644 test/common/testfiles/config7.txt create mode 100644 test/common/testfiles/config8.txt create mode 100644 test/common/testfiles/config9.txt create mode 100644 test/common/testfiles/config9b.txt create mode 100644 test/common/testfiles/config9c.txt create mode 100644 test/common/testfiles/config9d.txt create mode 100644 test/common/testfiles/fdgetlinetest.txt create mode 100644 test/compress/testcompress.cpp create mode 100644 test/crypto/testcrypto.cpp create mode 100644 test/httpserver/testfiles/httpserver.conf create mode 100644 test/httpserver/testfiles/photos/puppy.jpg create mode 100644 test/httpserver/testfiles/s3simulator.conf create mode 100755 test/httpserver/testfiles/testrequests.pl create mode 100644 test/httpserver/testhttpserver.cpp create mode 100644 test/raidfile/testextra create mode 100644 test/raidfile/testfiles/raidfile.conf create mode 100644 test/raidfile/testraidfile.cpp create mode 100644 test/s3store/testextra create mode 100644 test/s3store/testfiles/bbackupd.conf create mode 100644 test/s3store/testfiles/bbackupd.keys create mode 100644 test/s3store/testfiles/clientTrustedCAs.pem create mode 100644 test/s3store/testfiles/s3simulator.conf create mode 100644 test/s3store/testfiles/serverCerts.pem create mode 100644 test/s3store/testfiles/serverPrivKey.pem create mode 100644 test/s3store/testfiles/serverReq.pem create mode 100644 test/s3store/testfiles/store/subdir/dirs/create-me.txt create mode 100644 test/s3store/tests3store.cpp create mode 100644 test/win32/Makefile create mode 100644 test/win32/testlibwin32.cpp create mode 100644 test/win32/timezone.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..e6eb260e --- /dev/null +++ b/.gitignore @@ -0,0 +1,83 @@ +# built from .in files by autoconf/CMake +bin/bbackupd/bbackupd-config +bin/bbstored/bbstored-certs +bin/bbstored/bbstored-config +contrib/debian/bbackupd +contrib/debian/bbstored +contrib/mac_osx/org.boxbackup.bbackupd.plist +contrib/mac_osx/org.boxbackup.bbstored.plist +contrib/redhat/bbackupd +contrib/redhat/bbstored +contrib/suse/bbackupd +contrib/suse/bbstored +contrib/solaris/bbackupd-manifest.xml +contrib/solaris/bbackupd-smf-method +contrib/solaris/bbstored-manifest.xml +contrib/solaris/bbstored-smf-method +contrib/windows/installer/boxbackup.mpi +infrastructure/BoxPlatform.pm +infrastructure/makebuildenv.pl +infrastructure/makedistribution.pl +infrastructure/makeparcels.pl +lib/bbackupquery/makedocumentation.pl +lib/common/BoxConfig.h +lib/common/BoxConfig.h.in +lib/common/BoxPortsAndFiles.h +lib/common/BoxVersion.h +lib/common/makeexception.pl +lib/raidfile/raidfile-config +lib/server/makeprotocol.pl +runtest.pl +test/backupstorefix/testfiles/testbackupstorefix.pl +test/bbackupd/testfiles/bbackupd.conf +test/bbackupd/testfiles/bbackupd-exclude.conf +test/bbackupd/testfiles/bbackupd-snapshot.conf +test/bbackupd/testfiles/bbackupd-symlink.conf +test/bbackupd/testfiles/bbackupd-temploc.conf +test/bbackupd/testfiles/extcheck1.pl +test/bbackupd/testfiles/extcheck2.pl +test/bbackupd/testfiles/notifyscript.pl +test/bbackupd/testfiles/syncallowscript.pl + +# from svn:ignore +aclocal.m4 +autom4te.cache +config.log +config.log.features +config.status +config.env +configure +configure.lineno +debug +release +parcels +ExceptionCodes.txt +local +Makefile +.hg +*.log +test/*/*.memleaks + +# built by makebuildenv.pl +test/*/_main.cpp +test/*/_t +test/*/_t-gdb +bin/*/autogen_*.cpp +bin/*/autogen_*.h +lib/*/autogen_*.cpp +lib/*/autogen_*.h +test/*/autogen_*.cpp +test/*/autogen_*.h + +# qdbm +qdbm/*.o +qdbm/libqdbm.a +qdbm/qdbm.pc +qdbm/qdbm.spec +qdbm/LTmakefile + +# development +*.orig +*.patch +.*.swo +.*.swp diff --git a/.hgignore b/.hgignore new file mode 100644 index 00000000..c21cefc6 --- /dev/null +++ b/.hgignore @@ -0,0 +1,37 @@ +.svn +BoxConfig.h +BoxConfig.h.in +BoxPlatform.pm +BoxPortsAndFiles.h +ExceptionCodes.txt +Makefile +_main.cpp +_t +_t-gdb +aclocal.m4 +autogen_* +autom4te.cache +bbackupd-config +bbackupd.conf +bbstored-certs +bbstored-config +config.log +config.status +configure +debug +extcheck1.pl +extcheck2.pl +local +makebuildenv.pl +makedistribution.pl +makedocumentation.pl +makeexception.pl +makeparcels.pl +makeprotocol.pl +notifyscript.pl +parcels +raidfile-config +release +runtest.pl +syncallowscript.pl +testbackupstorefix.pl diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..3f4e75ef --- /dev/null +++ b/.travis.yml @@ -0,0 +1,36 @@ +language: cpp + +compiler: + - gcc + - clang + +cache: + - apt + - ccache + +sudo: false + +addons: + apt: + packages: + - libdb-dev + - libreadline-dev + - libssl-dev + - libwww-perl + - xsltproc + - zlib1g-dev + +before_script: + - ccache -s + - ./bootstrap + - ./configure CC="ccache $CC" CXX="ccache $CXX" + - grep CXX config.status + - make + +env: + - TEST_TARGET=debug + - TEST_TARGET=release + +script: + - ./runtest.pl ALL $TEST_TARGET + - ccache -s diff --git a/BUGS.txt b/BUGS.txt new file mode 100644 index 00000000..5425d81a --- /dev/null +++ b/BUGS.txt @@ -0,0 +1,16 @@ +================================================================================================================ +Bugs +================================================================================================================ + +* need a test to check that small files aren't tracked +* things like object ids don't have proper typedefs +* if a file changes while it's being streamed to the server, bad things will happen (exception in bbackupd, or corrupt file on server) +* if bbackupd gets an error then a signal, it may not wait it's full 100 seconds before retrying. And then won't stop the cycle... +* bbackupquery restore, if not root, then won't do file ownership properly, but won't alert the user to this fact +* empty (real) directories in the store aren't deleted when they're empty (and will never be used again) -- uses up disc space unnecessarily +* need unit tests for SSL keepalives and state saving (serialisation) +* make Archive derive from Protocol +* more automated tests for win32 +* change off_t to box_off_t in preparation for win32 large file support +* support large files on win32 by using native *i64 functions instead of posix +* when storage is implemented, ensure that no write operations can happen to an account that's opened read-only (BackupStoreCheck without fix) diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 00000000..8adb524c --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,491 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2015, 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 follows: +--------------------------------------------------------------------- +The Box Backup GPL is based on the GPLv2 (GNU General Public License +version 2 or later) as published by the Free Software Foundation. +However, it includes optional exceptions which allow linking with +OpenSSL and Microsoft VSS, listed below. This means that Box Backup +is not under pure GPLv2 as published by the Free Software Foundation. +These are the only differences between the Box Backup GPL license and +the original GPL from the Free Software Foundation. + +Please note that while the Box Backup GPL is similar in spirit to the +original GPL, it is not fully compatible. Because of these optional +exceptions, you may not include or combine fully GPL source code +(such as the GNU readline library) with Box Backup, or distribute the +resulting binaries, under the Box Backup GPL license, without +specific permission from the authors of such code. You may do so +under the pure GPL license, by omitting the optional exclusion clauses +below, however you may not legally link such code with OpenSSL or +Microsoft VSS, which may limit the usefulness or functionality of the +resulting code. + +The license exemption section below was based on the license of the +Bacula project +[http://bacula.git.sourceforge.net/git/gitweb.cgi?p=bacula/bacula;a=blob_plain;f=bacula/LICENSE;hb=HEAD]. + +For the most part, Box Backup is licensed under the GPL version 2 +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]. +The sections that follow are optional exemptions to the GPL version 2 +license, that apply to code that is copyrighted by Ben Summers and +Contributors. + +Linking: +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any software that can be downloaded from +the OpenSSL website [http://www.openssl.org] under either the +"OpenSSL License" or the "Original SSLeay License", and to distribute +the linked executables under the terms of the "Box Backup GPL" license. + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK +or Microsoft Windows Software Development Kit (SDK), including +vssapi.lib, that can be downloaded from the Microsoft website +[*.microsoft.com], and to distribute the linked executables under the +terms of the "Box Backup GPL" license. + +The sections above are optional, and you may distribute the code that +falls under this license under the original GPLv2 license by omitting +them. The original GPLv2 license follows. + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +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 give any other recipients of the Program a copy of this License +along with the Program. + +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 Program or any portion +of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +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 Program, 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 Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) 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; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, 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 executable. However, as a +special exception, the source code 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. + +If distribution of executable or 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 counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program 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. + + 5. 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 Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program 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 to +this License. + + 7. 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 Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program 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 Program. + +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. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program 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. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 Program +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 Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, 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 + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "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 PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. 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 PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), 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 Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. 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. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. +--------------------------------------------------------------------- + +Unless stated otherwise in the file or in the list of directories above, +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 BSD license follows: +--------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the Box Backup nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. +--------------------------------------------------------------------- +[http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29] + diff --git a/LICENSE-DUAL.txt b/LICENSE-DUAL.txt new file mode 100644 index 00000000..3e555cfc --- /dev/null +++ b/LICENSE-DUAL.txt @@ -0,0 +1,59 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2015, Ben Summers and contributors. +All rights reserved. + +Note that this project uses mixed licensing. Any file with this license +attached, or where the code LICENSE-DUAL appears on the first line, falls +under this license. See the file COPYING.txt for more information. + +This file is dual licensed. You may use and distribute it 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. + +The BSD license option follows: + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the Box Backup nor the names of its contributors may + be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER 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. + +[http://en.wikipedia.org/wiki/BSD_licenses#3-clause_license_.28.22New_BSD_License.22.29] + +The GPL license option follows: + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4] diff --git a/LICENSE-GPL.txt b/LICENSE-GPL.txt new file mode 100644 index 00000000..77d7e79b --- /dev/null +++ b/LICENSE-GPL.txt @@ -0,0 +1,41 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2015, Ben Summers and contributors. +All rights reserved. + +Note that this project uses mixed licensing. Any file with this license +attached, or where the code LICENSE-GPL appears on the first line, falls +under the "Box Backup GPL" license. See the file COPYING.txt for more +information about this license. + +--------------------------------------------------------------------- +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +[http://www.gnu.org/licenses/old-licenses/gpl-2.0.html#SEC4] + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any software that can be downloaded from +the OpenSSL website [http://www.openssl.org] under either the +"OpenSSL License" or the "Original SSLeay License", and to distribute +the linked executables under the terms of the "Box Backup GPL" license. + +As a special exception to the GPLv2, the Box Backup Project gives +permission to link any code falling under this license (the Box Backup +GPL) with any version of Microsoft's Volume Shadow Copy Service 7.2 SDK +or Microsoft Windows Software Development Kit (SDK), including +vssapi.lib, that can be downloaded from the Microsoft website +[*.microsoft.com], and to distribute the linked executables under the +terms of the "Box Backup GPL" license. diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..dd4e26e1 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,79 @@ +Box Backup, http://www.boxbackup.org/ + +Copyright (c) 2003-2015, 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] diff --git a/README.md b/README.md new file mode 100644 index 00000000..6d3b5261 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# Box Backup + +[![Travis Build Status](https://travis-ci.org/boxbackup/boxbackup.svg?branch=master)](https://travis-ci.org/boxbackup/boxbackup) +[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/ussek6c8mvgxqj2k/branch/master?svg=true)](https://ci.appveyor.com/project/qris/boxbackup/branch/master) + +Box Backup is an open source, completely automatic, secure, encrypted on-line backup system. + +It has the following key features: + +* All backed up data is stored on the server in files on a filesystem - no tape, archive or other special devices are required. +* The server is trusted only to make files available when they are required - all data is encrypted and can be decoded only by the original client. This makes it ideal for backing up over an untrusted network (such as the Internet), or where the server is in an uncontrolled environment. +* A backup daemon runs on systems to be backed up, and copies encrypted data to the server when it notices changes - so backups are continuous and up-to-date (although traditional snapshot backups are possible too). +* Only changes within files are sent to the server, just like rsync, minimising the bandwidth used between clients and server. This makes it particularly suitable for backing up between distant locations, or over the Internet. +* It behaves like tape - old file versions and deleted files are available. +* Old versions of files on the server are stored as changes from the current version, minimising the storage space required on the server. Files are the server are also compressed to minimise their size. +* Choice of backup behaviour - it can be optimised for document or server backup. +* It is designed to be easy and cheap to run a server. It has a portable implementation, and optional RAID implemented in userland for reliability without complex server setup or expensive hardware. + +Please see the [website](https://www.boxbackup.org) for more information, including installation instructions. + +Box Backup is distributed under a [mixed BSD/GPL license](https://github.com/boxbackup/boxbackup/blob/master/LICENSE.txt). + diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 00000000..c29321a1 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1,2 @@ +USE_SVN_VERSION +boxbackup diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..cbca569f --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,119 @@ +version: 1.0.{build}-{branch} + +clone_depth: 1 + +# Do not build on tags (GitHub only) +skip_tags: true + +os: Windows Server 2012 + +platform: +# - x86 +# - x64 + - Win32 + +configuration: + - Debug + - Release + +environment: + VisualStudioVersion: 10.0 + Generator: Visual Studio 10 + OPENSSL_VERSION: 1.0.2f + PCRE_VERSION: 8.38 + +cache: + - '..\zlib-1.2.8' + - '..\zlib-%PLATFORM%' + - '..\openssl-%OPENSSL_VERSION%.tar.gz' + - '..\openssl-%OPENSSL_VERSION%' + - '..\openssl-%PLATFORM%' + - '..\pcre-%PCRE_VERSION%.zip' + - '..\pcre-%PCRE_VERSION%' + - '..\pcre-%PLATFORM%' + - 'infrastructure\cmake\build' + +init: +# Uncomment the following two lines to enable RDP access to the virtual machine for debugging. +# - reg add "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp" /v UserAuthentication /t REG_DWORD /d 0 /f +# - ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) + +build: + parallel: true + project: infrastructure/cmake/build/BoxBackup.sln + +install: + # Show compiled files restored from cache + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\bin_bbackupd.dir + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\bin_bbackupd.dir\%CONFIGURATION% + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\%PLATFORM% + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\%PLATFORM%\%CONFIGURATION% + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\%PLATFORM%\%CONFIGURATION%\ALL_BUILD + + - cinst strawberryperl 7zip.commandline cmake + + # - dir "C:\Program Files\Microsoft SDKs\Windows" + # - dir "C:\Program Files\Microsoft SDKs\Windows\v7.1" + # - dir "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin" + + - '"C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x86' + + - cd %APPVEYOR_BUILD_FOLDER%\.. + - if not exist zlib128.zip appveyor DownloadFile "http://zlib.net/zlib128.zip" + - 7za x -aoa zlib128.zip + - cd zlib-1.2.8 + - cmake -G "%Generator%" -A %PLATFORM% -DCMAKE_INSTALL_PREFIX="..\zlib-%PLATFORM%" . + # We need to build both versions, debug and release, because cmake requires both to be + # present to generate its multi-configuration project files for Visual Studio/MSBuild. + - msbuild INSTALL.vcxproj /m /p:Configuration=Debug /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild INSTALL.vcxproj /m /p:Configuration=Release /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + + - cd %APPVEYOR_BUILD_FOLDER%\.. + - if not exist openssl-%OPENSSL_VERSION%.tar.gz appveyor DownloadFile "https://www.openssl.org/source/openssl-%OPENSSL_VERSION%.tar.gz" + - 7za x -aoa openssl-%OPENSSL_VERSION%.tar.gz + - 7za x -aoa openssl-%OPENSSL_VERSION%.tar + - cd openssl-%OPENSSL_VERSION% + - perl Configure debug-VC-WIN32 no-asm --prefix="%APPVEYOR_BUILD_FOLDER%\..\openssl-%PLATFORM%" + - ms\do_ms + - nmake /s /f ms\nt.mak + - nmake /s /f ms\nt.mak install + + - cd %APPVEYOR_BUILD_FOLDER%\.. + - if not exist pcre-%PCRE_VERSION%.zip appveyor DownloadFile "http://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-%PCRE_VERSION%.zip" + - 7za x -aoa pcre-%PCRE_VERSION%.zip + - cd %APPVEYOR_BUILD_FOLDER%\.. + - cd pcre-%PCRE_VERSION% + - cmake -G "%Generator%" -A %PLATFORM% -DCMAKE_INSTALL_PREFIX="..\pcre-%PLATFORM%" . + - dir + # We need to build both versions, debug and release, because cmake requires both to be + # present to generate its multi-configuration project files for Visual Studio/MSBuild. + - msbuild INSTALL.vcxproj /m /p:Configuration=Debug /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild INSTALL.vcxproj /m /p:Configuration=Release /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - cd %APPVEYOR_BUILD_FOLDER%\.. + - dir + - dir pcre-%PLATFORM% + - dir pcre-%PLATFORM%\bin + - dir pcre-%PLATFORM%\lib + + - cd %APPVEYOR_BUILD_FOLDER% + - cd infrastructure\cmake\build + - cmake -G "%Generator%" -A %PLATFORM% .. + - cd %APPVEYOR_BUILD_FOLDER% + + # Show files after build + - dir %APPVEYOR_BUILD_FOLDER%\..\zlib-1.2.8 + - dir %APPVEYOR_BUILD_FOLDER%\..\zlib-%PLATFORM% + - dir %APPVEYOR_BUILD_FOLDER%\..\openssl-%OPENSSL_VERSION% + - dir %APPVEYOR_BUILD_FOLDER%\..\openssl-%PLATFORM% + - dir %APPVEYOR_BUILD_FOLDER%\..\pcre-%PCRE_VERSION% + - dir %APPVEYOR_BUILD_FOLDER%\..\pcre-%PLATFORM% + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\bin_bbackupd.dir + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\%PLATFORM% + - dir %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build\%PLATFORM%\%CONFIGURATION% + +test_script: + - cd %APPVEYOR_BUILD_FOLDER%\infrastructure\cmake\build + - ctest -C %CONFIGURATION% -V + diff --git a/bin/bbackupctl/bbackupctl.cpp b/bin/bbackupctl/bbackupctl.cpp new file mode 100644 index 00000000..0e0c1e9c --- /dev/null +++ b/bin/bbackupctl/bbackupctl.cpp @@ -0,0 +1,394 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupctl.cpp +// Purpose: bbackupd daemon control program +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include "box_getopt.h" +#include "MainHelper.h" +#include "BackupDaemon.h" +#include "BoxPortsAndFiles.h" +#include "BackupDaemonConfigVerify.h" +#include "Socket.h" +#include "SocketStream.h" +#include "IOStreamGetLine.h" + +#ifdef WIN32 + #include "WinNamedPipeStream.h" +#endif + +#include "MemLeakFindOn.h" + +enum Command +{ + Default, + WaitForSyncStart, + WaitForSyncEnd, + SyncAndWaitForEnd, + NoCommand, +}; + +void PrintUsageAndExit(int ret) +{ + std::cout << + "Usage: bbackupctl [options] \n" + "\n" + "Options:\n" << + Logging::OptionParser::GetUsageString() << + "\n" + "Commands are:\n" + " status -- report daemon status without changing anything\n" + " sync -- start a synchronisation (backup) run now\n" + " force-sync -- force the start of a synchronisation run, " + "even if SyncAllowScript says no\n" + " reload -- reload daemon configuration\n" + " terminate -- terminate daemon now\n" + " wait-for-sync -- wait until the next sync starts, then exit\n" + " wait-for-end -- wait until the next sync finishes, then exit\n" + " sync-and-wait -- start sync, wait until it finishes, then exit\n" + ; + exit(ret); +} + +int main(int argc, const char *argv[]) +{ + int returnCode = 0; + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupctl.memleaks", + "bbackupctl") + + MAINHELPER_START + + Logging::SetProgramName("bbackupctl"); + + // Filename for configuration file? + std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; + + // See if there's another entry on the command line + int c; + std::string options("c:"); + options += Logging::OptionParser::GetOptionString(); + Logging::OptionParser LogLevel; + + while((c = getopt(argc, (char * const *)argv, options.c_str())) != -1) + { + switch(c) + { + case 'c': + // store argument + configFilename = optarg; + break; + + default: + int ret = LogLevel.ProcessOption(c); + if(ret != 0) + { + PrintUsageAndExit(ret); + } + } + } + // Adjust arguments + argc -= optind; + argv += optind; + + // Check there's a command + if(argc != 1) + { + PrintUsageAndExit(2); + } + + Logging::FilterConsole(LogLevel.GetCurrentLevel()); + + // Read in the configuration file + BOX_INFO("Using configuration file " << configFilename); + + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_ERROR("Invalid configuration file: " << errs); + return 1; + } + // Easier coding + const Configuration &conf(*config); + + // Check there's a socket defined in the config file + if(!conf.KeyExists("CommandSocket")) + { + BOX_ERROR("Daemon isn't using a control socket, " + "could not execute command.\n" + "Add a CommandSocket declaration to the " + "bbackupd.conf file."); + return 1; + } + + // Connect to socket + +#ifndef WIN32 + SocketStream connection; +#else /* WIN32 */ + WinNamedPipeStream connection; +#endif /* ! WIN32 */ + + try + { +#ifdef WIN32 + std::string socket = conf.GetKeyValue("CommandSocket"); + connection.Connect(socket); +#else + connection.Open(Socket::TypeUNIX, conf.GetKeyValue("CommandSocket").c_str()); +#endif + } + catch(...) + { + BOX_ERROR("Failed to connect to daemon control socket.\n" + "Possible causes:\n" + " * Daemon not running\n" + " * Daemon busy syncing with store server\n" + " * Another bbackupctl process is communicating with the daemon\n" + " * Daemon is waiting to recover from an error" + ); + + return 1; + } + + // For receiving data + IOStreamGetLine getLine(connection); + + // Wait for the configuration summary + std::string configSummary; + if(!getLine.GetLine(configSummary, false, PROTOCOL_DEFAULT_TIMEOUT)) + { + BOX_ERROR("Failed to receive configuration summary " + "from daemon"); + return 1; + } + + // Was the connection rejected by the server? + if(getLine.IsEOF()) + { + BOX_ERROR("Server rejected the connection. Are you running " + "bbackupctl as the same user as the daemon?"); + return 1; + } + + // Decode it + int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; + if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", &autoBackup, + &updateStoreInterval, &minimumFileAge, &maxUploadWait) != 4) + { + BOX_ERROR("Config summary didn't decode."); + return 1; + } + // Print summary? + BOX_TRACE("Daemon configuration summary:\n" + " AutomaticBackup = " << + (autoBackup?"true":"false") << "\n" + " UpdateStoreInterval = " << updateStoreInterval << + " seconds\n" + " MinimumFileAge = " << minimumFileAge << " seconds\n" + " MaxUploadWait = " << maxUploadWait << " seconds"); + + std::string stateLine; + if(!getLine.GetLine(stateLine, false, PROTOCOL_DEFAULT_TIMEOUT) || getLine.IsEOF()) + { + BOX_ERROR("Failed to receive state line from daemon"); + return 1; + } + + // Decode it + int currentState; + if(::sscanf(stateLine.c_str(), "state %d", ¤tState) != 1) + { + BOX_ERROR("Received invalid state line from daemon"); + return 1; + } + + BOX_TRACE("Current state: " << + BackupDaemon::GetStateName(currentState)); + + Command command = Default; + std::string commandName(argv[0]); + + if(commandName == "wait-for-sync") + { + command = WaitForSyncStart; + } + else if(commandName == "wait-for-end") + { + command = WaitForSyncEnd; + } + else if(commandName == "sync-and-wait") + { + command = SyncAndWaitForEnd; + } + else if(commandName == "status") + { + BOX_NOTICE("state " << + BackupDaemon::GetStateName(currentState)); + command = NoCommand; + } + + switch(command) + { + case WaitForSyncStart: + case WaitForSyncEnd: + { + // Check that it's in automatic mode, + // because otherwise it'll never start + + if(!autoBackup) + { + BOX_ERROR("Daemon is not in automatic mode, " + "sync will never start!"); + return 1; + } + } + break; + + case SyncAndWaitForEnd: + { + // send a sync command + commandName = "force-sync"; + std::string cmd = commandName + "\n"; + connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); + connection.WriteAllBuffered(); + + if(currentState != 0) + { + BOX_INFO("Waiting for current sync/error state " + "to finish..."); + } + } + break; + + default: + { + // Normal case, just send the command given, plus a + // quit command. + std::string cmd = commandName + "\n"; + connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); + } + // fall through + + case NoCommand: + { + // Normal case, just send the command given plus a + // quit command. + std::string cmd = "quit\n"; + connection.Write(cmd, PROTOCOL_DEFAULT_TIMEOUT); + } + } + + // Read the response + std::string line; + bool syncIsRunning = false; + bool finished = false; + + while(command != NoCommand && !finished && !getLine.IsEOF() && + getLine.GetLine(line, false, PROTOCOL_DEFAULT_TIMEOUT)) + { + BOX_TRACE("Received line: " << line); + + if(line.substr(0, 6) == "state ") + { + std::string state_str = line.substr(6); + int state_num; + if(sscanf(state_str.c_str(), "%d", &state_num) == 1) + { + BOX_INFO("Daemon state changed to: " << + BackupDaemon::GetStateName(state_num)); + } + else + { + BOX_WARNING("Failed to parse line: " << line); + } + } + + switch(command) + { + case WaitForSyncStart: + { + // Need to wait for the state change... + if(line == "start-sync") + { + // And we're done + finished = true; + } + } + break; + + case WaitForSyncEnd: + case SyncAndWaitForEnd: + { + if(line == "start-sync") + { + BOX_TRACE("Sync started..."); + syncIsRunning = true; + } + else if(line == "finish-sync") + { + if (syncIsRunning) + { + // And we're done + BOX_TRACE("Sync finished."); + finished = true; + } + else + { + BOX_TRACE("Previous sync finished."); + } + // daemon must still be busy + } + } + break; + + default: + { + // Is this an OK or error line? + if(line == "ok") + { + BOX_TRACE("Control command " + "sent: " << + commandName); + finished = true; + } + else if(line == "error") + { + BOX_ERROR("Control command failed: " << + commandName << ". Check " + "command spelling."); + returnCode = 1; + finished = true; + } + } + } + } + + // Send a quit command to finish nicely + connection.Write("quit\n", 5, PROTOCOL_DEFAULT_TIMEOUT); + + MAINHELPER_END + +#if defined WIN32 && ! defined BOX_RELEASE_BUILD + closelog(); +#endif + + return returnCode; +} diff --git a/bin/bbackupd/bbackupd-config.in b/bin/bbackupd/bbackupd-config.in new file mode 100755 index 00000000..1fc224c2 --- /dev/null +++ b/bin/bbackupd/bbackupd-config.in @@ -0,0 +1,601 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +sub error_print_usage +{ + print <<__E; + +Setup bbackupd config utility. + +Bad command line parameters. +Usage: + bbackupd-config config-dir backup-mode account-num server-hostname + working-dir [backup directories] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + backup-mode is lazy or snapshot: + lazy mode runs continously, uploading files over a specified age + snapshot mode uploads a snapshot of the filesystem when instructed + explicitly, using bbackupctl sync + account-num (hexdecimal) and server-hostname + are supplied by the server administrator + working-dir is usually @localstatedir_expanded@/bbackupd + backup directories is list of directories to back up + +__E + print "=========\nERROR:\n",$_[0],"\n\n" if $_[0] ne ''; + exit(1); +} + +# check and get command line parameters +if($#ARGV < 4) +{ + error_print_usage(); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# default locations +my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbackupd.conf'; + +# command line parameters +my ($config_dir,$backup_mode,$account_num,$server,$working_dir,@tobackup) = @ARGV; + +# check backup mode is valid +if($backup_mode ne 'lazy' && $backup_mode ne 'snapshot') +{ + error_print_usage("ERROR: backup mode must be 'lazy' or 'snapshot'"); +} + +# check server exists +{ + my @r = gethostbyname($server); + if($#r < 0) + { + error_print_usage("Backup server specified as '$server', but it could not found.\n(A test DNS lookup failed -- check arguments)"); + } +} + +if($working_dir !~ m~\A/~) +{ + error_print_usage("Working directory $working_dir is not specified as an absolute path"); +} + +# ssl stuff +my $private_key = "$config_dir/bbackupd/$account_num-key.pem"; +my $certificate_request = "$config_dir/bbackupd/$account_num-csr.pem"; +my $certificate = "$config_dir/bbackupd/$account_num-cert.pem"; +my $ca_root_cert = "$config_dir/bbackupd/serverCA.pem"; + +# encryption keys +my $enc_key_file = "$config_dir/bbackupd/$account_num-FileEncKeys.raw"; + +# other files +my $config_file = "$config_dir/bbackupd.conf"; +my $notify_script = "$config_dir/bbackupd/NotifySysadmin.sh"; + +# check that the directories are allowable +for(@tobackup) +{ + if($_ eq '/') + { + die "It is not recommended that you backup the root directory of your disc"; + } + if($_ !~ m/\A\//) + { + die "Directory $_ is not specified as an absolute path"; + } + if(!-d $_) + { + die "$_ is not a directory"; + } +} + +# summarise configuration + +print <<__E; + +Setup bbackupd config utility. + +Configuration: + Writing configuration file: $config_file + Account: $account_num + Server hostname: $server + Directories to back up: +__E +print ' ',$_,"\n" for(@tobackup); +print <<__E; + +Note: If other file systems are mounted inside these directories, then +they will NOT be backed up. You will have to create separate locations for +any mounted filesystems inside your backup locations. + +__E + +# create directories +if(!-d $config_dir) +{ + printf "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +if(!-d "$config_dir/bbackupd") +{ + printf "Creating $config_dir/bbackupd\n"; + mkdir "$config_dir/bbackupd",0700 or die "Can't create $config_dir/bbackupd"; +} + +if(!-d "$working_dir") +{ + printf "Creating $working_dir\n"; + if(!mkdir($working_dir,0700)) + { + die "Couldn't create $working_dir -- create this manually and try again\n"; + } +} + +# generate the private key for the server +if(!-f $private_key) +{ + print "Generating private key...\n"; + if(system("openssl genrsa -out $private_key 2048") != 0) + { + die "Couldn't generate private key." + } +} + +# generate a certificate request +if(!-f $certificate_request) +{ + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); + print CSR <<__E; +. +. +. +. +. +BACKUP-$account_num +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $certificate_request +} + +# generate the key material for the file +if(!-f $enc_key_file) +{ + print "Generating keys for file backup\n"; + if(system("openssl rand -out $enc_key_file 1024") != 0) + { + die "Couldn't generate file backup keys." + } +} + +# write the notify when store full script +print "Writing notify script $notify_script\n"; +open NOTIFY,">$notify_script" or die "Can't open for writing"; + +my $hostname = `hostname`; chomp $hostname; +my $current_username = `whoami`; chomp $current_username; +my $sendmail = `whereis sendmail`; chomp $sendmail; +$sendmail =~ s/\n.\Z//s; +# for Linux style whereis +$sendmail = $1 if $sendmail =~ /^sendmail:\s+([\S]+)/; +# last ditch guess +$sendmail = 'sendmail' if $sendmail !~ m/\S/; + +print NOTIFY <<__EOS; +#!/bin/sh + +# 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. + +SUBJECT="BACKUP PROBLEM on host $hostname" +SENDTO="$current_username" + +if [ "\$1" = "" ]; then + echo "Usage: \$0 " >&2 + exit 2 +elif [ "\$1" = store-full ]; then + $sendmail \$SENDTO <$config_file" or die "Can't open config file for writing"; +print CONFIG <<__E; + +StoreHostname = $server +AccountNumber = 0x$account_num +KeysFile = $enc_key_file + +CertificateFile = $certificate +PrivateKeyFile = $private_key +TrustedCAsFile = $ca_root_cert + +DataDirectory = $working_dir + + +# 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 = $notify_script + +__E + +if($backup_mode eq 'lazy') +{ + # lazy mode configuration + print CONFIG <<__E; + +# 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 + +__E +} +else +{ + # snapshot configuration + print CONFIG <<__E; + +# This configuration file is written for snapshot mode. +# You will need to run bbackupctl to instruct the daemon to upload files. + +AutomaticBackup = no +UpdateStoreInterval = 0 +MinimumFileAge = 0 +MaxUploadWait = 0 + +__E +} + +print CONFIG <<__E; + +# 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 = $working_dir/bbackupd.sock + +# 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 = $working_dir/bbackupd.state + +Server +{ + PidFile = $working_dir/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. + +BackupLocations +{ +__E + +# write the dirs to backup +for my $d (@tobackup) +{ + $d =~ m/\A.(.+)\Z/; + my $n = $1; + $n =~ tr`/`-`; + + my $excludekeys = ''; + if(substr($enc_key_file, 0, length($d)+1) eq $d.'/') + { + $excludekeys = "\t\tExcludeFile = $enc_key_file\n"; + print <<__E; + +NOTE: Keys file has been explicitly excluded from the backup. + +__E + } + + print CONFIG <<__E + $n + { + Path = $d +$excludekeys } +__E +} + +print CONFIG "}\n\n"; +close CONFIG; + +# explain to the user what they need to do next +my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; +my $ctl_daemon_args = ($config_file eq $default_config_location)?'':" -c $config_file"; + +print <<__E; + +=================================================================== + +bbackupd basic configuration complete. + +What you need to do now... + +1) Make a backup of $enc_key_file + This should be a secure offsite backup. + Without it, you cannot restore backups. Everything else can + be replaced. But this cannot. + KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. + +2) Send $certificate_request + to the administrator of the backup server, and ask for it to + be signed. + +3) The administrator will send you two files. Install them as + $certificate + $ca_root_cert + after checking their authenticity. + +4) You may wish to read the configuration file + $config_file + and adjust as appropriate. + + There are some notes in it on excluding files you do not + wish to be backed up. + +5) Review the script + $notify_script + and check that it will email the right person when the store + becomes full. This is important -- when the store is full, no + more files will be backed up. You want to know about this. + +6) Start the backup daemon with the command + @sbindir_expanded@/bbackupd$daemon_args + in /etc/rc.local, or your local equivalent. + Note that bbackupd must run as root. +__E +if($backup_mode eq 'snapshot') +{ + print <<__E; + +7) Set up a cron job to run whenever you want a snapshot of the + file system to be taken. Run the command + @bindir_expanded@/bbackupctl -q$ctl_daemon_args sync +__E +} +print <<__E; + +=================================================================== + +Remember to make a secure, offsite backup of your backup keys, +as described in step 1 above. If you do not, you have no backups. + +__E + diff --git a/bin/bbackupd/bbackupd.cpp b/bin/bbackupd/bbackupd.cpp new file mode 100644 index 00000000..5977615f --- /dev/null +++ b/bin/bbackupd/bbackupd.cpp @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupd.cpp +// Purpose: main file for backup daemon +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupDaemon.h" +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#ifdef WIN32 + #include "Win32ServiceFunctions.h" + #include "Win32BackupService.h" + + extern Win32BackupService* gpDaemonService; +#endif + +int main(int argc, const char *argv[]) +{ + int ExitCode = 0; + + MAINHELPER_START + + Logging::SetProgramName("bbackupd"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + +#ifdef WIN32 + + EnableBackupRights(); + + gpDaemonService = new Win32BackupService(); + ExitCode = gpDaemonService->Daemon::Main( + BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, + argc, argv); + delete gpDaemonService; + +#else // !WIN32 + + { + BackupDaemon daemon; + ExitCode = daemon.Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE, + argc, argv); + } + +#endif // WIN32 + + MAINHELPER_END + + return ExitCode; +} diff --git a/bin/bbackupd/win32/NotifySysAdmin.vbs b/bin/bbackupd/win32/NotifySysAdmin.vbs new file mode 100644 index 00000000..712d92da --- /dev/null +++ b/bin/bbackupd/win32/NotifySysAdmin.vbs @@ -0,0 +1,113 @@ +Dim hostname +Dim account +Dim from +Dim sendto +Dim subjtmpl +Dim subject +Dim body +Dim smtpserver + +Set WshNet = CreateObject("WScript.Network") +hostname = WshNet.ComputerName + +account = "0x1" +from = "boxbackup@" & hostname +sendto = "admin@example.com" +smtpserver = "smtp.example.com" +subjtmpl = "BACKUP PROBLEM on host " & hostname + +Set args = WScript.Arguments + +If args(0) = "store-full" Then + subject = subjtmpl & " (store full)" + body = "The store account for "&hostname&" is full." & vbCrLf & _ + vbCrLf & _ + "=============================" & vbCrLf & _ + "FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "=============================" & vbCrLf & _ + vbCrLf & _ + "Please adjust the limits on account "&account&" on server "&hostname&"." _ + & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "read-error" Then + subject = subjtmpl & " (read errors)" + body = "Errors occurred reading some files or directories " & _ + "for backup on " & hostname & "." & vbCrLf & _ + vbCrLf & _ + "===================================" & vbCrLf & _ + "THESE FILES ARE NOT BEING BACKED UP" & vbCrLf & _ + "===================================" & vbCrLf & vbCrLf & _ + "Check the logs on "&hostname&" for the files and " & _ + "directories which caused" & vbCrLf & _ + "these errors, and take appropriate action." & vbCrLf & _ + vbCrLf & _ + "Other files are being backed up." & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "backup-error" Then + subject = subjtmpl & " (read errors)" + body = "An error occurred during the backup on "&hostname&"." _ + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & _ + vbCrLf & _ + "Check the logs on "&hostname&" for more " & _ + "information about the error, " & vbCrLf & _ + "and take appropriate action." & vbCrLf + SendMail from,sendto,subject,body +ElseIf args(0) = "backup-start" Or args(0) = "backup-finish" _ + Or args(0) = "backup-ok" Then + ' do nothing for these messages by default +Else + subject = subjtmpl & " (unknown)" + body = "The backup daemon on "&hostname&" reported an unknown error." _ + & vbCrLf & vbCrLf & _ + "==========================" & vbCrLf & _ + "FILES MAY NOT BE BACKED UP" & vbCrLf & _ + "==========================" & vbCrLf & vbCrLf & _ + "Please check the logs on "&hostname&"." & vbCrLf + SendMail from,sendto,subject,body +End If + +Function CheckSMTPSvc() + Set objWMISvc = GetObject("winmgmts:" _ + & "{impersonationLevel=impersonate}!\\.\root\cimv2") + Set colSMTPSvc = objWMISvc.ExecQuery("Select * From Win32_Service " _ + & "Where Name='SMTPSVC'") + If colSMTPSvc.Count > 0 Then + CheckSMTPSvc = True + Else + CheckSMTPSvc = False + End If +End Function + +Sub SendMail(from,sendto,subject,body) + Set objEmail = CreateObject("CDO.Message") + Set WshShell = CreateObject("WScript.Shell") + Dim cdoschema + cdoschema = "http://schemas.microsoft.com/cdo/configuration/" + + With objEmail + .From = from + .To = sendto + .Subject = subject + .TextBody = body + If CheckSMTPSvc = False Then + .Configuration.Fields.Item(cdoschema & "sendusing") = 2 + .Configuration.Fields.Item(cdoschema & "smtpserver") = smtpserver + .Configuration.Fields.Item(cdoschema & "smtpserverport") = 25 + .Configuration.Fields.Update + End If + End With + On Error Resume Next + rc = objEmail.Send + If rc Then + WshShell.Exec "eventcreate /L Application /ID 201 /T WARNING " _ + & "/SO ""Box Backup"" /D """ & args(0) _ + & " notification sent to " & sendto & ".""" + Else + WshShell.Exec "eventcreate /L Application /ID 202 /T ERROR " _ + & "/SO ""Box Backup"" /D ""Failed to send " & args(0) _ + & " notification to " & sendto & ".""" + End If +End Sub diff --git a/bin/bbackupd/win32/bbackupd.conf b/bin/bbackupd/win32/bbackupd.conf new file mode 100644 index 00000000..b0793b29 --- /dev/null +++ b/bin/bbackupd/win32/bbackupd.conf @@ -0,0 +1,238 @@ + +StoreHostname = yourhost +AccountNumber = 0x1 +KeysFile = C:\Program Files\Box Backup\1-FileEncKeys.raw + +CertificateFile = C:\Program Files\Box Backup\1-cert.pem +PrivateKeyFile = C:\Program Files\Box Backup\1-key.pem +TrustedCAsFile = C:\Program Files\Box Backup\serverCA.pem + +DataDirectory = C:\Program Files\Box Backup\bbackupd + +# If you do not install it in the default location - also do not forget to +# change the pid file location (below) + +# 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. +# +# NOTE: You need to edit the following variables in the script before +# enabling it: +# +# account = "accountnumber" +# sendto = "your@email.address" +# smtpserver = "your.smtp.server" +# +# You do not need to set smtpserver if the client has the SMTP Service +# installed, the script will connect directly to the SMTP service. + +NotifyScript = cscript "C:\Program Files\Box Backup\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 = pipe + +# 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 = C:\Program Files\Box Backup\bbackupd\bbackupd.state + +Server +{ + PidFile = C:\Program Files\Box Backup\bbackupd\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. +# +# If a directive ends in Regex, then it is a regular expression rather than a +# explicit full pathname. See: +# +# http://www.boxbackup.org/trac/wiki/Win32Regex +# +# for more information about regular expressions on Windows. +# +# 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/ +# +# Here are some more examples of possible regular expressions for Windows: +# +# ExcludeDir = C:\Documents and Settings\Owner +# ExcludeFilesRegex = \.(mp3|MP3)$ +# AlwaysIncludeFile = C:\Documents and Settings\Owner\My Documents\My Music\veryimportant.mp3 +# ExcludeFilesRegex = \.pst$ +# AlwaysIncludeFilesRegex = \.*backup.*\.pst$ +# ExcludeFilesRegex = \.avi$ +# ExcludeDirsRegex = \\Temporary Internet Files$ +# ExcludeFilesRegex = \\pagefile\.sys$ +# ExcludeDirsRegex = \\pagefile\.sys$ +# ExcludeFilesRegex = \\boot\.ini$ +# ExcludeFilesRegex = \\NTDETECT\.COM$ +# ExcludeFilesRegex = \\UsrClass\.dat\.LOG$ +# ExcludeDirsRegex = \\System Volume Information$ +# ExcludeFilesRegex = \\ntldr$ +# ExcludeDirsRegex = \\Local Settings\\.*\\Cache$ +# ExcludeFilesRegex = \\thumbs\.db$ +# ExcludeFilesRegex = \\~.* +# ExcludeFilesRegex = \\Perflib.* +# ExcludeDirsRegex = \\Application Data$ +# ExcludeFilesRegex = \.bk[~!0-9]$ +# ExcludeFilesRegex = \.iso$ +# ExcludeFilesRegex = \.mpe?[2345g]$ +# ExcludeFilesRegex = \.qbw$ +# AlwaysIncludeFilesRegex = \.qbb$ +# ExcludeFilesRegex = \.tif[f]$ +# ExcludeFilesRegex = \.wmv$ +# ExcludeFilesRegex = \.avi$ +# ExcludeFilesRegex = \.(avi|iso|mp(e)?[g345]|bk[~!1-9]|[mt]bk)$ + +BackupLocations +{ + MyDocuments + { + Path = C:\Documents and Settings\ + } +} + diff --git a/bin/bbackupd/win32/installer.iss b/bin/bbackupd/win32/installer.iss new file mode 100644 index 00000000..e69de29b diff --git a/bin/bbackupobjdump/bbackupobjdump.cpp b/bin/bbackupobjdump/bbackupobjdump.cpp new file mode 100644 index 00000000..5b6c44f7 --- /dev/null +++ b/bin/bbackupobjdump/bbackupobjdump.cpp @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupobjdump.cpp +// Purpose: Dump contents of backup objects +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "MainHelper.h" +#include "FileStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "BackupStoreObjectMagic.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: int main(int, const char *[]) +// Purpose: Main fn for bbackupobjdump +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + if(argc != 2) + { + ::printf("Input file not specified.\nUsage: bbackupobjdump \n"); + return 1; + } + + // Open file + FileStream file(argv[1]); + + // Read magic number + uint32_t signature; + if(file.Read(&signature, sizeof(signature)) != sizeof(signature)) + { + // Too short, can't read signature from it + return false; + } + // Seek back to beginning + file.Seek(0, IOStream::SeekType_Absolute); + + // Then... check depending on the type + switch(ntohl(signature)) + { + case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: +#endif + BackupStoreFile::DumpFile(stdout, false, file); + break; + + case OBJECTMAGIC_DIR_MAGIC_VALUE: + { + BackupStoreDirectory dir; + dir.ReadFromStream(file, IOStream::TimeOutInfinite); + dir.Dump(stdout, false); + if(dir.CheckAndFix()) + { + ::printf("Directory didn't pass checking\n"); + } + } + break; + + default: + ::printf("File does not appear to be a valid box backup object.\n"); + break; + } + + MAINHELPER_END +} + diff --git a/bin/bbackupquery/bbackupquery.cpp b/bin/bbackupquery/bbackupquery.cpp new file mode 100644 index 00000000..e10c48fe --- /dev/null +++ b/bin/bbackupquery/bbackupquery.cpp @@ -0,0 +1,543 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbackupquery.cpp +// Purpose: Backup query utility +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#ifdef HAVE_SYS_TYPES_H + #include +#endif + +#ifdef HAVE_LIBREADLINE + #ifdef HAVE_READLINE_READLINE_H + #include + #elif defined(HAVE_EDITLINE_READLINE_H) + #include + #elif defined(HAVE_READLINE_H) + #include + #endif +#endif + +#ifdef HAVE_READLINE_HISTORY + #ifdef HAVE_READLINE_HISTORY_H + #include + #elif defined(HAVE_HISTORY_H) + #include + #endif +#endif + +#include + +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupDaemonConfigVerify.h" +#include "SocketStreamTLS.h" +#include "Socket.h" +#include "TLSContext.h" +#include "SSLLib.h" +#include "BackupStoreConstants.h" +#include "BackupStoreException.h" +#include "autogen_BackupProtocol.h" +#include "BackupQueries.h" +#include "FdGetLine.h" +#include "BackupClientCryptoKeys.h" +#include "BannerText.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +void PrintUsageAndExit() +{ + std::ostringstream out; + out << + "Usage: bbackupquery [options] [command]...\n" + "\n" + "Options:\n" + " -w Read/write mode, allow changes to store\n" +#ifdef WIN32 + " -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 + " -c 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 Write logging output to specified file as well as console\n" + " -O Set file verbosity to error/warning/notice/info/trace/everything\n" + " -l Write protocol debugging logs to specified file\n" + << + Logging::OptionParser::GetUsageString() + << + "\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 completions; +static std::auto_ptr 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 addOpts = + CompleteOptions(*sapCmd, text, + *pProtocol, *pConfig, + *pQueries); + + for(std::vector::iterator + i = addOpts.begin(); + i != addOpts.end(); i++) + { + completions.push_back(*i); + } + } + } + } + + if(state < 0 || state >= (int) completions.size()) + { + rl_attempted_completion_over = 1; + sapCmd.reset(); + 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; + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbackupquery.memleaks", + "bbackupquery") + MAINHELPER_START + +#ifdef WIN32 + WSADATA info; + + // Under Win32 we must initialise the Winsock library + // before using it. + + if (WSAStartup(0x0101, &info) == SOCKET_ERROR) + { + // throw error? perhaps give it its own id in the future + THROW_EXCEPTION(BackupStoreException, Internal) + } +#endif + + // Really don't want trace statements happening, even in debug mode + #ifndef BOX_RELEASE_BUILD + BoxDebugTraceOn = false; + #endif + + FILE *logFile = 0; + + // Filename for configuration file? + std::string configFilename = BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE; + + // Flags + bool readWrite = false; + + Logging::SetProgramName("bbackupquery"); + +#ifdef WIN32 + #define WIN32_OPTIONS "u" + bool unicodeConsole = false; +#else + #define WIN32_OPTIONS +#endif + +#ifdef HAVE_LIBREADLINE + #define READLINE_OPTIONS "E" + bool useReadline = true; +#else + #define READLINE_OPTIONS +#endif + + std::string options("wc:l:o:O:" WIN32_OPTIONS READLINE_OPTIONS); + options += Logging::OptionParser::GetOptionString(); + Logging::OptionParser LogLevel; + + std::string fileLogFile; + Log::Level fileLogLevel = Log::INVALID; + + // See if there's another entry on the command line + int c; + while((c = getopt(argc, (char * const *)argv, options.c_str())) != -1) + { + switch(c) + { + case 'w': + // Read/write mode + readWrite = true; + break; + + case 'c': + // store argument + configFilename = optarg; + break; + + case 'l': + // open log file + logFile = ::fopen(optarg, "w"); + if(logFile == 0) + { + BOX_LOG_SYS_ERROR("Failed to open log file " + "'" << optarg << "'"); + } + break; + + case 'o': + fileLogFile = optarg; + fileLogLevel = Log::EVERYTHING; + break; + + case 'O': + { + fileLogLevel = Logging::GetNamedLevel(optarg); + if (fileLogLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level"); + return 2; + } + } + break; + +#ifdef WIN32 + case 'u': + unicodeConsole = true; + break; +#endif + +#ifdef HAVE_LIBREADLINE + case 'E': + useReadline = false; + break; +#endif + + default: + int ret = LogLevel.ProcessOption(c); + if (ret != 0) + { + PrintUsageAndExit(); + } + } + } + // Adjust arguments + argc -= optind; + argv += optind; + + Logging::GetConsole().Filter(LogLevel.GetCurrentLevel()); + + std::auto_ptr fileLogger; + if (fileLogLevel != Log::INVALID) + { + fileLogger.reset(new FileLogger(fileLogFile, fileLogLevel, + true)); // open in append mode + } + + BOX_NOTICE(BANNER_TEXT("Backup Query Tool")); + +#ifdef WIN32 + if (unicodeConsole) + { + if (!SetConsoleCP(CP_UTF8)) + { + BOX_ERROR("Failed to set input codepage: " << + GetErrorMessage(GetLastError())); + } + + if (!SetConsoleOutputCP(CP_UTF8)) + { + BOX_ERROR("Failed to set output codepage: " << + GetErrorMessage(GetLastError())); + } + + // enable input of Unicode characters + if (_fileno(stdin) != -1 && + _setmode(_fileno(stdin), _O_TEXT) == -1) + { + perror("Failed to set the console input to " + "binary mode"); + } + } +#endif // WIN32 + + // Read in the configuration file + BOX_INFO("Using configuration file " << configFilename); + + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupDaemonConfigVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_FATAL("Invalid configuration file: " << errs); + return 1; + } + // Easier coding + const Configuration &conf(*config); + + // Setup and connect + // 1. TLS context + SSLLib::Initialise(); + // Read in the certificates creating a TLS context + TLSContext tlsContext; + std::string certFile(conf.GetKeyValue("CertificateFile")); + std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); + std::string caFile(conf.GetKeyValue("TrustedCAsFile")); + tlsContext.Initialise(false /* as client */, certFile.c_str(), keyFile.c_str(), caFile.c_str()); + + // Initialise keys + BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile").c_str()); + + // 2. Connect to server + BOX_INFO("Connecting to store..."); + SocketStreamTLS *socket = new SocketStreamTLS; + std::auto_ptr apSocket(socket); + socket->Open(tlsContext, Socket::TypeINET, + conf.GetKeyValue("StoreHostname").c_str(), + conf.GetKeyValueInt("StorePort")); + + // 3. Make a protocol, and handshake + BOX_INFO("Handshake with store..."); + std::auto_ptr + apConnection(new BackupProtocolClient(apSocket)); + BackupProtocolClient& connection(*(apConnection.get())); + connection.Handshake(); + + // logging? + if(logFile != 0) + { + connection.SetLogToFile(logFile); + } + + // 4. Log in to server + BOX_INFO("Login to store..."); + // Check the version of the server + { + std::auto_ptr 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.GetKeyValueUint32("AccountNumber"), + (readWrite)?0:(BackupProtocolLogin::Flags_ReadOnly)); + + // 5. Tell user. + BOX_INFO("Login complete."); + BOX_INFO("Type \"help\" for a list of commands."); + + // Set up a context for our work + BackupQueries context(connection, conf, readWrite); + + // Start running commands... first from the command line + { + int c = 0; + while(c < argc && !context.Stop()) + { + BackupQueries::ParsedCommand cmd(argv[c++], true); + context.DoCommand(cmd); + } + } + + // Get commands from input + +#ifdef HAVE_LIBREADLINE + if(useReadline) + { + // 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; + } + + std::string last_cmd; +#endif + + std::auto_ptr apGetLine; + if(fileno(stdin) >= 0) + { + apGetLine.reset(new FdGetLine(fileno(stdin))); + } + + while(!context.Stop() && fileno(stdin) >= 0) + { + std::string cmd_str; + + #ifdef HAVE_LIBREADLINE + if(useReadline) + { + char *cmd_ptr = readline("query > "); + + if(cmd_ptr == NULL) + { + // Ctrl-D pressed -- terminate now + puts(""); + break; + } + + cmd_str = cmd_ptr; + free(cmd_ptr); + } + else + #endif // HAVE_LIBREADLINE + { + printf("query > "); + fflush(stdout); + + try + { + cmd_str = apGetLine->GetLine(); + } + catch(CommonException &e) + { + if(e.GetSubType() == CommonException::GetLineEOF) + { + break; + } + throw; + } + } + + BackupQueries::ParsedCommand cmd_parsed(cmd_str, false); + if (cmd_parsed.IsEmpty()) + { + continue; + } + + context.DoCommand(cmd_parsed); + + #ifdef HAVE_READLINE_HISTORY + if(last_cmd != cmd_str) + { + add_history(cmd_str.c_str()); + last_cmd = cmd_str; + } + #endif // HAVE_READLINE_HISTORY + } + + // Done... stop nicely + BOX_INFO("Logging off..."); + connection.QueryFinished(); + BOX_INFO("Session finished."); + + // Return code + returnCode = context.GetReturnCode(); + + // Close log file? + if(logFile) + { + ::fclose(logFile); + } + + // Let everything be cleaned up on exit. + +#ifdef WIN32 + // Clean up our sockets + // FIXME we should do this, but I get an abort() when I try + // WSACleanup(); +#endif + + MAINHELPER_END + + return returnCode; +} + diff --git a/bin/bbstoreaccounts/bbstoreaccounts.cpp b/bin/bbstoreaccounts/bbstoreaccounts.cpp new file mode 100644 index 00000000..f2e3df95 --- /dev/null +++ b/bin/bbstoreaccounts/bbstoreaccounts.cpp @@ -0,0 +1,290 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbstoreaccounts +// Purpose: backup store administration tool +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include + +#include "box_getopt.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreCheck.h" +#include "BackupStoreConfigVerify.h" +#include "BackupStoreInfo.h" +#include "BoxPortsAndFiles.h" +#include "HousekeepStoreAccount.h" +#include "MainHelper.h" +#include "NamedLock.h" +#include "RaidFileController.h" +#include "StoreStructure.h" +#include "UnixUser.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#include + +void PrintUsageAndExit() +{ + printf( +"Usage: bbstoreaccounts [-c config_file] action account_id [args]\n" +"Account ID is integer specified in hex\n" +"\n" +"Commands (and arguments):\n" +" create \n" +" Creates the specified account number (in hex with no 0x) on the\n" +" specified raidfile disc set number (see raidfile.conf for valid\n" +" set numbers) with the specified soft and hard limits (in blocks\n" +" if suffixed with B, MB with M, GB with G)\n" +" info [-m] \n" +" Prints information about the specified account including number\n" +" of blocks used. The -m option enable machine-readable output.\n" +" enabled \n" +" Sets the account as enabled or disabled for new logins.\n" +" setlimit \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" +" delete [yes]\n" +" Deletes the specified account. Prompts for confirmation unless\n" +" the optional 'yes' parameter is provided.\n" +" check [fix] [quiet]\n" +" Checks the specified account for errors. If the 'fix' option is\n" +" 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 \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 \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); +} + +int main(int argc, const char *argv[]) +{ + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT("bbstoreaccounts.memleaks", + "bbstoreaccounts") + + MAINHELPER_START + + Logging::SetProgramName("bbstoreaccounts"); + + // Filename for configuration file? + std::string configFilename = BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE; + int logLevel = Log::EVERYTHING; + bool machineReadableOutput = false; + + // See if there's another entry on the command line + int c; + while((c = getopt(argc, (char * const *)argv, "c:W:m")) != -1) + { + switch(c) + { + case 'c': + // store argument + 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 + machineReadableOutput = true; + break; + + case '?': + default: + PrintUsageAndExit(); + } + } + + Logging::FilterConsole((Log::Level) logLevel); + Logging::FilterSyslog (Log::NOTHING); + + // Adjust arguments + argc -= optind; + argv += optind; + + // Read in the configuration file + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + (configFilename, &BackupConfigFileVerify, errs)); + + if(config.get() == 0 || !errs.empty()) + { + BOX_ERROR("Invalid configuration file " << configFilename << + ":" << errs); + } + + // Initialise the raid file controller + RaidFileController &rcontroller(RaidFileController::GetController()); + rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str()); + + // Then... check we have two arguments + if(argc < 2) + { + PrintUsageAndExit(); + } + + // Get the id + int32_t id; + if(::sscanf(argv[1], "%x", &id) != 1) + { + PrintUsageAndExit(); + } + + std::string command = argv[0]; + BackupStoreAccountsControl control(*config, machineReadableOutput); + + // Now do the command. + if(command == "create") + { + // which disc? + int32_t discnum; + int32_t softlimit; + int32_t hardlimit; + if(argc < 5 + || ::sscanf(argv[2], "%d", &discnum) != 1) + { + BOX_ERROR("create requires raid file disc number, " + "soft and hard limits."); + return 1; + } + + // Decode limits + int blocksize = control.BlockSizeOfDiscSet(discnum); + softlimit = control.SizeStringToBlocks(argv[3], blocksize); + hardlimit = control.SizeStringToBlocks(argv[4], blocksize); + control.CheckSoftHardLimits(softlimit, hardlimit); + + // Create the account... + return control.CreateAccount(id, discnum, softlimit, hardlimit); + } + else if(command == "info") + { + // Print information on this account + return control.PrintAccountInfo(id); + } + 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 control.SetAccountEnabled(id, enabled); + } + else if(command == "setlimit") + { + // Change the limits on this account + if(argc < 4) + { + BOX_ERROR("setlimit requires soft and hard limits."); + return 1; + } + + return control.SetLimit(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 control.SetAccountName(id, argv[2]); + } + else if(command == "delete") + { + // Delete an account + bool askForConfirmation = true; + if(argc >= 3 && (::strcmp(argv[2], "yes") == 0)) + { + askForConfirmation = false; + } + return control.DeleteAccount(id, askForConfirmation); + } + else if(command == "check") + { + bool fixErrors = false; + bool quiet = false; + + // Look at other options + for(int o = 2; o < argc; ++o) + { + if(::strcmp(argv[o], "fix") == 0) + { + fixErrors = true; + } + else if(::strcmp(argv[o], "quiet") == 0) + { + quiet = true; + } + else + { + BOX_ERROR("Unknown option " << argv[o] << "."); + return 2; + } + } + + // Check the account + return control.CheckAccount(id, fixErrors, quiet); + } + else if(command == "housekeep") + { + return control.HousekeepAccountNow(id); + } + else + { + BOX_ERROR("Unknown command '" << command << "'."); + return 1; + } + + return 0; + + MAINHELPER_END +} + diff --git a/bin/bbstored/bbstored-certs.in b/bin/bbstored/bbstored-certs.in new file mode 100755 index 00000000..85560748 --- /dev/null +++ b/bin/bbstored/bbstored-certs.in @@ -0,0 +1,319 @@ +#!@PERL@ +use strict; + +# validity period for root certificates -- default is 2038, the best we can do for now +my $root_sign_period = int(((1<<31) - time()) / 86400); + +# but less so for client certificates +my $sign_period = '5000'; + +# check and get command line parameters +if($#ARGV < 1) +{ + print <<__E; + +bbstored certificates utility. + +Bad command line parameters. +Usage: + bbstored-certs certs-dir command [arguments] + +certs-dir is the directory holding the root keys and certificates for the backup system +command is the action to perform, taking parameters. + +Commands are + + init + -- generate initial root certificates (certs-dir must not already exist) + sign certificate-name + -- sign a client certificate + sign-server certificate-name + -- sign a server certificate + +Signing requires confirmation that the certificate is correct and should be signed. + +__E + exit(1); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# directory structure: +# +# roots/ +# clientCA.pem -- root certificate for client (used on server) +# serverCA.pem -- root certificate for servers (used on clients) +# keys/ +# clientRootKey.pem -- root key for clients +# serverRootKey.pem -- root key for servers +# servers/ +# hostname.pem -- certificate for server 'hostname' +# clients/ +# account.pem -- certficiate for account 'account' (ID in hex) +# + + +# check parameters +my ($cert_dir,$command,@args) = @ARGV; + +# check directory exists +if($command ne 'init') +{ + if(!-d $cert_dir) + { + die "$cert_dir does not exist"; + } +} + +# run command +if($command eq 'init') {&cmd_init;} +elsif($command eq 'sign') {&cmd_sign;} +elsif($command eq 'sign-server') {&cmd_sign_server;} +else +{ + die "Unknown command $command" +} + +sub cmd_init +{ + # create directories + unless(mkdir($cert_dir,0700) + && mkdir($cert_dir.'/roots',0700) + && mkdir($cert_dir.'/keys',0700) + && mkdir($cert_dir.'/servers',0700) + && mkdir($cert_dir.'/clients',0700)) + { + die "Failed to create directory structure" + } + + # create root keys and certrs + cmd_init_create_root('client'); + cmd_init_create_root('server'); +} + +sub cmd_init_create_root +{ + my $entity = $_[0]; + + my $cert = "$cert_dir/roots/".$entity.'CA.pem'; + my $serial = "$cert_dir/roots/".$entity.'CA.srl'; + my $key = "$cert_dir/keys/".$entity.'RootKey.pem'; + my $csr = "$cert_dir/keys/".$entity.'RootCSR.pem'; + + # generate key + if(system("openssl genrsa -out $key 2048") != 0) + { + die "Couldn't generate private key." + } + + # make CSR + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $key -sha1 -out $csr"); + print CSR <<__E; +. +. +. +. +. +Backup system $entity root +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $csr; + + # sign it to make a self-signed root CA key + if(system("openssl x509 -req -in $csr -sha1 -extensions v3_ca -signkey $key -out $cert -days $root_sign_period") != 0) + { + die "Couldn't generate root certificate." + } + + # write the initial serial number + open SERIAL,">$serial" or die "Can't open $serial for writing"; + print SERIAL "00\n"; + close SERIAL; +} + +sub cmd_sign +{ + my $csr = $args[0]; + + if(!-f $csr) + { + die "$csr does not exist"; + } + + # get the common name specified in this certificate + my $common_name = get_csr_common_name($csr); + + # look OK? + unless($common_name =~ m/\ABACKUP-([A-Fa-f0-9]+)\Z/) + { + die "The certificate presented does not appear to be a backup client certificate" + } + + my $acc = $1; + + # check against filename + if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) + { + die "Certificate request filename does not match name in certificate ($common_name)" + } + + print <<__E; + +This certificate is for backup account + + $acc + +Ensure this matches the account number you are expecting. The filename is + + $csr + +which should include this account number, and additionally, you should check +that you received it from the right person. + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +__E + + return unless get_confirmation(); + + # out certificate + my $out_cert = "$cert_dir/clients/$acc"."-cert.pem"; + + # sign it! + if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/clientCA.pem -CAkey $cert_dir/keys/clientRootKey.pem -out $out_cert -days $sign_period") != 0) + { + die "Signing failed" + } + + # tell user what to do next + print <<__E; + + +Certificate signed. + +Send the files + + $out_cert + $cert_dir/roots/serverCA.pem + +to the client. + +__E +} + +sub cmd_sign_server +{ + my $csr = $args[0]; + + if(!-f $csr) + { + die "$csr does not exist"; + } + + # get the common name specified in this certificate + my $common_name = get_csr_common_name($csr); + + # look OK? + if($common_name !~ m/\A[-a-zA-Z0-9.]+\Z/) + { + die "Invalid server name" + } + + print <<__E; + +This certificate is for backup server + + $common_name + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +__E + + return unless get_confirmation(); + + # out certificate + my $out_cert = "$cert_dir/servers/$common_name"."-cert.pem"; + + # sign it! + if(system("openssl x509 -req -in $csr -sha1 -extensions usr_crt -CA $cert_dir/roots/serverCA.pem -CAkey $cert_dir/keys/serverRootKey.pem -out $out_cert -days $sign_period") != 0) + { + die "Signing failed" + } + + # tell user what to do next + print <<__E; + + +Certificate signed. + +Install the files + + $out_cert + $cert_dir/roots/clientCA.pem + +on the server. + +__E +} + + +sub get_csr_common_name +{ + my $csr = $_[0]; + + open CSRTEXT,"openssl req -text -in $csr |" or die "Can't open openssl for reading"; + + my $subject; + while() + { + $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ + } + close CSRTEXT; + + if($subject eq '') + { + die "No subject found in CSR $csr" + } + + return $subject +} + +sub get_confirmation() +{ + my $line = ; + chomp $line; + if(lc $line ne 'yes') + { + print "CANCELLED\n"; + return 0; + } + + return 1; +} + + + + + diff --git a/bin/bbstored/bbstored-config.in b/bin/bbstored/bbstored-config.in new file mode 100755 index 00000000..83305c4f --- /dev/null +++ b/bin/bbstored/bbstored-config.in @@ -0,0 +1,245 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +# check and get command line parameters +if($#ARGV < 2) +{ + print <<__E; + +Setup bbstored config utility. + +Bad command line parameters. +Usage: + bbstored-config config-dir server-hostname username [raidfile-config] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + server-hostname is the hostname that clients will use to connect to + this server + username is the user to run the server under + raidfile-config is optional. Use if you have a non-standard + raidfile.conf file. + +__E + exit(1); +} + +# check for OPENSSL_CONF environment var being set +if(exists $ENV{'OPENSSL_CONF'}) +{ + print <<__E; + +--------------------------------------- + +WARNING: + You have the OPENSSL_CONF environment variable set. + Use of non-standard openssl configs may cause problems. + +--------------------------------------- + +__E +} + +# default locations +my $default_config_location = '@sysconfdir_expanded@/boxbackup/bbstored.conf'; + +# command line parameters +my ($config_dir,$server,$username,$raidfile_config) = @ARGV; + +$raidfile_config = $config_dir . '/raidfile.conf' unless $raidfile_config ne ''; + +# check server exists, but don't bother checking that it's actually this machine. +{ + my @r = gethostbyname($server); + if($#r < 0) + { + die "Server '$server' not found. (check server name, test DNS lookup failed.)" + } +} + +# check this exists +if(!-f $raidfile_config) +{ + print "The RaidFile configuration file $raidfile_config doesn't exist.\nYou may need to create it with raidfile-config.\nWon't configure bbstored without it.\n"; + exit(1); +} + +# check that the user exists +die "You shouldn't run bbstored as root" if $username eq 'root'; +my $user_uid = 0; +(undef,undef,$user_uid) = getpwnam($username); +if($user_uid == 0) +{ + die "User $username doesn't exist\n"; +} + +# check that directories are writeable +open RAIDCONF,$raidfile_config or die "Can't open $raidfile_config"; +{ + my %done = (); + while() + { + next unless m/Dir\d\s*=\s*(.+)/; + my $d = $1; + $d = $d.'/backup' if -e $d.'/backup'; + print "Checking permissions on $d\n"; + my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($d); + my $req_perms = ($uid == $user_uid)?0700:0007; + if(($mode & $req_perms) != $req_perms) + { + print "$username doesn't appear to have the necessary permissions on $d\n"; + print "Either adjust permissions, or create a directory 'backup' inside the\n"; + print "directory specified in raidfile.conf which is writable.\n"; + exit(1); + } + } +} +close RAIDCONF; + +# ssl stuff +my $private_key = "$config_dir/bbstored/$server-key.pem"; +my $certificate_request = "$config_dir/bbstored/$server-csr.pem"; +my $certificate = "$config_dir/bbstored/$server-cert.pem"; +my $ca_root_cert = "$config_dir/bbstored/clientCA.pem"; + +# other files +my $config_file = "$config_dir/bbstored.conf"; +my $accounts_file = "$config_dir/bbstored/accounts.txt"; + +# summarise configuration + +print <<__E; + +Setup bbstored config utility. + +Configuration: + Writing configuration file: $config_file + Writing empty accounts file: $accounts_file + Server hostname: $server + RaidFile config: $raidfile_config + +__E + +# create directories +if(!-d $config_dir) +{ + print "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +if(!-d "$config_dir/bbstored") +{ + print "Creating $config_dir/bbstored\n"; + mkdir "$config_dir/bbstored",0755 or die "Can't create $config_dir/bbstored"; +} + +# create blank accounts file +if(!-f $accounts_file) +{ + print "Creating blank accounts file\n"; + open ACC,">$accounts_file"; + close ACC; +} + +# generate the private key for the server +if(!-f $private_key) +{ + print "Generating private key...\n"; + if(system("openssl genrsa -out $private_key 2048") != 0) + { + die "Couldn't generate private key." + } +} + +# generate a certificate request +if(!-f $certificate_request) +{ + die "Couldn't run openssl for CSR generation" unless + open(CSR,"|openssl req -new -key $private_key -sha1 -out $certificate_request"); + print CSR <<__E; +. +. +. +. +. +$server +. +. +. + +__E + close CSR; + print "\n\n"; + die "Certificate request wasn't created.\n" unless -f $certificate_request +} + +# write the configuration file +print "Writing configuration file $config_file\n"; +open CONFIG,">$config_file" or die "Can't open config file for writing"; +print CONFIG <<__E; + +RaidFileConf = $raidfile_config +AccountDatabase = $accounts_file + +# Uncomment this line to see exactly what commands are being received from clients. +# ExtendedLogging = yes + +# scan all accounts for files which need deleting every 15 minutes. + +TimeBetweenHousekeeping = 900 + +Server +{ + PidFile = @localstatedir_expanded@/run/bbstored.pid + User = $username + ListenAddresses = inet:$server + CertificateFile = $certificate + PrivateKeyFile = $private_key + TrustedCAsFile = $ca_root_cert +} + + +__E + +close CONFIG; + +# explain to the user what they need to do next +my $daemon_args = ($config_file eq $default_config_location)?'':" $config_file"; + +print <<__E; + +=================================================================== + +bbstored basic configuration complete. + +What you need to do now... + +1) Sign $certificate_request + using the bbstored-certs utility. + +2) Install the server certificate and root CA certificate as + $certificate + $ca_root_cert + +3) You may wish to read the configuration file + $config_file + and adjust as appropraite. + +4) Create accounts with bbstoreaccounts + +5) Start the backup store daemon with the command + @sbindir_expanded@/bbstored$daemon_args + in /etc/rc.local, or your local equivalent. + +=================================================================== + +__E + + + diff --git a/bin/bbstored/bbstored.cpp b/bin/bbstored/bbstored.cpp new file mode 100644 index 00000000..2c18b4fc --- /dev/null +++ b/bin/bbstored/bbstored.cpp @@ -0,0 +1,31 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: bbstored.cpp +// Purpose: main file for backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupStoreDaemon.h" +#include "MainHelper.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + Logging::SetProgramName("bbstored"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + + BackupStoreDaemon daemon; + + return daemon.Main(BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE, argc, argv); + + MAINHELPER_END +} + diff --git a/bin/s3simulator/s3simulator.cpp b/bin/s3simulator/s3simulator.cpp new file mode 100644 index 00000000..9a10635c --- /dev/null +++ b/bin/s3simulator/s3simulator.cpp @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: s3simulator.cpp +// Purpose: main file for S3 simulator daemon +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "S3Simulator.h" +#include "MainHelper.h" + +#include "MemLeakFindOn.h" + +int main(int argc, const char *argv[]) +{ + int ExitCode = 0; + + MAINHELPER_START + + Logging::SetProgramName("s3simulator"); + Logging::ToConsole(true); + Logging::ToSyslog (true); + + S3Simulator daemon; + ExitCode = daemon.Main("s3simulator.conf", argc, argv); + + MAINHELPER_END + + return ExitCode; +} diff --git a/bootstrap b/bootstrap new file mode 100755 index 00000000..b669c409 --- /dev/null +++ b/bootstrap @@ -0,0 +1,6 @@ +#!/bin/sh + +aclocal -I infrastructure/m4 +autoheader +autoconf +(cd qdbm; autoconf) diff --git a/cleanupforcvs.pl b/cleanupforcvs.pl new file mode 100755 index 00000000..3379a7ad --- /dev/null +++ b/cleanupforcvs.pl @@ -0,0 +1,196 @@ +#!/usr/bin/perl +use strict; + +my @del_macos_files; +my @bad_cpp; +my @test_main; +my @makefiles; +my @autogen_cpp; +my $cleaned = 1; +my $dist_archives_exist = 0; +my @bad_h; + +open EVERYTHING,'find . -type d \( -name docs \) -prune -o -type f |' or die "Can't open find for file listing"; + +my %exclude_from_memtest_checks = ('PollEmulator.cpp'=>1,'DebugMemLeakFinder.cpp'=>1,'MemLeakFinder.h'=>1,'MemLeakFindOn.h'=>1,'MemLeakFindOff.h'=>1,'Box.h'=>1); + +while() +{ + chomp; + next if -d; + if(m~/autogen_\w+\.(h|cpp)~) + { + push @autogen_cpp,$_ + } + if(m~/\._[^/]+\Z~ || m~/\.DS_Store\Z~) + { + # mac OS files we don't want + push @del_macos_files,$_ + } + elsif(m/\/(\w+\.cpp)/) + { + my $leafname = $1; + # check that Box.h is first include + open CPP,$_ or die "Can't open $_ for reading"; + + my $box_found = 0; + my $last_was_memteston = 0; + my $ok = 1; + + while(my $l = ) + { + if($l =~ m/#include\s+["<](.+?)[">]/) + { + my $inc_name = $1; + if($inc_name eq 'Box.h') + { + $box_found = 1; + } + else + { + # Box.h must be first include file in every cpp file + $ok = 0 unless $box_found; + } + # is it the mem test on thing? (ignoring the wire packing .h files) + if($inc_name ne 'BeginStructPackForWire.h' && $inc_name ne 'EndStructPackForWire.h') + { + $last_was_memteston = ($inc_name eq 'MemLeakFindOn.h'); + } + } + } + if(!exists $exclude_from_memtest_checks{$leafname}) + { + $ok = 0 unless $last_was_memteston; + } + push @bad_cpp,$_ unless $ok; + + close CPP; + } + elsif(m/\/(\w+\.h)/) + { + my $leafname = $1; + + open H,$_ or die "Can't open $_ for reading"; + + my $ok = 1; + my $memteston = 0; + + while(my $l = ) + { + if($l =~ m/#include\s+["<](.+?)[">]/) + { + if($1 eq 'MemLeakFindOn.h') + { + $memteston = 1; + } + elsif($1 eq 'MemLeakFindOff.h') + { + $memteston = 0; + } + else + { + # don't allow #include within mem test on + $ok = 0 unless !$memteston; + } + } + else + { + # strip comments + my $lsc = $l; + $lsc =~ s~//.+$~~; + if($lsc =~ m/\b(new|delete|malloc|free|realloc)\b/) + { + # only allow this if memory checking is ON + $ok = 0 unless $memteston; + } + } + } + # mem test must be off at the end of this .h file + $ok = 0 if $memteston; + + if($_ !~ /testfiles/ && !exists $exclude_from_memtest_checks{$leafname}) + { + push @bad_h,$_ unless $ok; + } + close H; + } + elsif(m~/Makefile\Z~) + { + push @makefiles,$_ + } + + if(m~/_(main\.cpp|t|t-gdb)\Z~) + { + push @test_main,$_ + } + + if(m~\./boxbackup~) + { + $dist_archives_exist = 1; + } +} + +close EVERYTHING; + +ask_about_delete(\@del_macos_files, "supurious MacOS X files"); +ask_about_delete(\@makefiles, "automatically generated Makefiles"); +ask_about_delete(\@test_main, "automatically generated test files"); +ask_about_delete(\@autogen_cpp, "automatically generated source files"); + +if($#bad_cpp >= 0) +{ + print "\n"; + print $_,"\n" for @bad_cpp; + print "There are some .cpp file where Box.h is not the first included file or MemLeakFindOn.h is not the last .h file included\n"; + $cleaned = 0; +} +if($#bad_h >= 0) +{ + print "\n"; + print $_,"\n" for @bad_h; + print "There are some .h files which use memory functions without memory leak finding on, or leave memory leak finding on at end\n"; + $cleaned = 0; +} + +if(-d 'debug') {print "debug directory exists\n"; $cleaned = 0;} +if(-d 'release') {print "release directory exists\n"; $cleaned = 0;} +if(-d 'parcels') {print "parcels directory exists\n"; $cleaned = 0;} +if($dist_archives_exist) {print "boxbackup* files/dirs exist\n"; $cleaned = 0;} + +if(!$cleaned) +{ + print <<__E; + +======================================================== + NOT CLEANED! +======================================================== +__E +} + + +sub ask_about_delete +{ + my ($del_r, $name) = @_; + return if $#$del_r < 0; + + print "\n"; + for(@$del_r) + { + print $_,"\n"; + } + print "Delete these ",$#$del_r + 1, " $name? "; + my $in = ; + chomp $in; + if($in eq 'yes') + { + print "Deleting...\n"; + unlink $_ for @$del_r + } + else + { + $cleaned = 0; + } +} + + + diff --git a/configure.ac b/configure.ac new file mode 100644 index 00000000..272044f7 --- /dev/null +++ b/configure.ac @@ -0,0 +1,173 @@ +# -*- Autoconf -*- +# Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT([Box Backup], 0.11, [boxbackup@boxbackup.org],[boxbackup]) +AC_CONFIG_SRCDIR([lib/common/Box.h]) +AC_CONFIG_AUX_DIR([infrastructure]) +AC_CONFIG_HEADERS([lib/common/BoxConfig.h]) +AC_CONFIG_SUBDIRS([qdbm]) + +touch install-sh +AC_CANONICAL_SYSTEM +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 +if test "x$ac_cv_cxx_exceptions" != "xyes" || \ + test "x$ac_cv_cxx_namespaces" != "xyes"; then + AC_MSG_ERROR([[basic compile checks failed, the C++ compiler is broken]]) +fi + +m4_include([infrastructure/m4/boxbackup_tests.m4]) + +## Get tmpdir +temp_directory_name="/tmp" +AC_ARG_WITH( + [tmp-dir], + [AC_HELP_STRING([--with-tmp-dir=DIR], [Directory for temporary files [/tmp]])], + [temp_directory_name="$withval"]) +AC_DEFINE_UNQUOTED([TEMP_DIRECTORY_NAME], ["$temp_directory_name"], [TMP directory name]) + +## Allow linking binaries with static libraries +AC_ARG_ENABLE( + [static-bin], + [AC_HELP_STRING([--enable-static-bin], [Link binaries with static libraries])]) +if test "x$enable_static_bin" = "xyes"; then + AC_CHECK_LIB([ssl],[SSL_read],,, [crypto]) + LIBS="-Wl,-Bstatic $LIBS -Wl,-Bdynamic" +fi + +# override default sysconfdir, for backwards compatibility +test "$sysconfdir" = '${prefix}/etc' && sysconfdir=/etc +test "$localstatedir" = '${prefix}/var' && localstatedir=/var + +## Kludge to allow makeparcels.pl to use bindir. This is not a good long term +## solution because it prevents use of "make exec_prefix=/some/dir" +saved_prefix=$prefix +saved_exec_prefix=$exec_prefix +test "x$prefix" = xNONE && prefix=$ac_default_prefix +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' +eval bindir_expanded=` eval "echo $bindir"` +eval sbindir_expanded=` eval "echo $sbindir"` +eval sysconfdir_expanded=` eval "echo $sysconfdir"` +eval localstatedir_expanded=`eval "echo $localstatedir"` +prefix=$saved_prefix +exec_prefix=$saved_exec_prefix +AC_SUBST([bindir_expanded]) +AC_SUBST([sbindir_expanded]) +AC_SUBST([sysconfdir_expanded]) +AC_SUBST([localstatedir_expanded]) + +## Figure out the client parcel directory and substitute it +build_dir=`dirname $0` +build_dir=`cd $build_dir && pwd` +client_parcel_dir=`$PERL infrastructure/parcelpath.pl backup-client $target_os` + +if test "$build_os" = "cygwin"; then + client_parcel_dir=`cygpath -wa $client_parcel_dir | sed -e 's|\\\|/|g'` + build_dir=` cygpath -wa $build_dir | sed -e 's|\\\|/|g'` +fi + +AC_SUBST([client_parcel_dir]) +AC_SUBST([build_dir]) + +## Figure out version and substitute it in +box_version=`$PERL infrastructure/printversion.pl` +AC_SUBST([box_version]) + +### Output files +AC_CONFIG_FILES([infrastructure/BoxPlatform.pm + contrib/mac_osx/org.boxbackup.bbackupd.plist + contrib/mac_osx/org.boxbackup.bbstored.plist + contrib/solaris/bbackupd-manifest.xml + contrib/solaris/bbstored-manifest.xml + lib/common/BoxPortsAndFiles.h + test/bbackupd/testfiles/bbackupd.conf + 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/bbstored/bbstored-certs + bin/bbstored/bbstored-config + contrib/debian/bbackupd + contrib/debian/bbstored + contrib/redhat/bbackupd + contrib/redhat/bbstored + contrib/suse/bbackupd + contrib/suse/bbstored + contrib/solaris/bbackupd-smf-method + contrib/solaris/bbstored-smf-method + contrib/windows/installer/boxbackup.mpi + infrastructure/makebuildenv.pl + infrastructure/makeparcels.pl + infrastructure/makedistribution.pl + lib/bbackupquery/makedocumentation.pl + lib/common/makeexception.pl + lib/raidfile/raidfile-config + lib/server/makeprotocol.pl + runtest.pl + test/backupstorefix/testfiles/testbackupstorefix.pl + test/bbackupd/testfiles/extcheck1.pl + test/bbackupd/testfiles/extcheck2.pl + test/bbackupd/testfiles/notifyscript.pl + test/bbackupd/testfiles/syncallowscript.pl]) +# TODO: Need to do contrib/cygwin/install-cygwin-service.pl but location varies +AC_OUTPUT + +# Configure the Box build system +echo +if ! $PERL ./infrastructure/makebuildenv.pl \ +|| ! $PERL ./infrastructure/makeparcels.pl; then + echo "Making infrastructure failed!" + exit 1 +fi + +# Write summary of important info +tee config.log.features < config.env <param("download")) +{ + my ($filename, $acct_no); + + if ($download eq "cert") + { + $acct_no = $cgi->param("account"); + $acct_no =~ tr/0-9a-fA-F//cd; + $filename = "$acct_no-cert.pem"; + } + elsif ($download eq "cacert") + { + $filename = "serverCA.pem"; + } + else + { + die "No such download method $download"; + } + + print $cgi->header(-type => "text/plain", + -"content-disposition" => "attachment; filename=$filename"); + + my $send_file; + + if ($download eq "cert") + { + $send_file = "$ca_dir/clients/$filename"; + } + elsif ($download eq "cacert") + { + $send_file = "$ca_dir/roots/serverCA.pem"; + } + + die "File does not exist: $send_file" + unless -f $send_file; + die "File is not readable by user " . getpwuid($UID) . + ": $send_file" unless -r $send_file; + + open SENDFILE, "< $send_file" or die "Failed to open file " . + "$send_file: $!"; + while (my $line = ) + { + print $line; + } + close SENDFILE; + exit 0; +} + +print $cgi->header(), $cgi->start_html(-title=>"Box Backup Certificates", + -style=>'bb.css'); +print $cgi->h1("Box Backup Certificates"); + +check_access($bbstored_conf_file, "BBStoreD configuration file"); + +my $bbstored_conf = Config::Scoped->new(file => $bbstored_conf_file)->parse(); + +$accounts_db_file ||= $bbstored_conf->{'Server'}{'AccountDatabase'}; +die "Missing AccountDatabase in $bbstored_conf_file" unless $accounts_db_file; +check_access($accounts_db_file, "Accounts Database"); + +$raidfile_conf_file ||= $bbstored_conf->{'Server'}{'RaidFileConf'}; +die "Missing RaidFileConf in $bbstored_conf_file" unless $raidfile_conf_file; +check_access($raidfile_conf_file, "RaidFile configuration file"); + +my $accounts_db = BoxBackup::Config::Accounts->new($accounts_db_file); + +check_executable($bbstoreaccounts, "bbstoreaccounts program"); + +sub error($) +{ + my ($message) = @_; + unless ($message =~ /^p($message); + } + print $cgi->div({-class=>"error"}, $message); + return 0; +} + +sub url +{ + my $cgi = shift @_; + my %params = @_; + my $uri = URI->new($cgi->url(-absolute=>1)); + foreach my $param (keys %params) + { + $uri->query_param($param, $params{$param}); + } + return $uri; +} + +sub create_account($) +{ + my ($cgi) = @_; + + my $upload = $cgi->upload('req'); + unless ($upload) + { + return error("Please attach a certificate request file."); + } + + my $tempfile = File::Temp->new("bbaccount-certreq-XXXXXX.pem"); + my $csr_data = ""; + + while (my $line = <$upload>) + { + print $tempfile $line; + $csr_data .= $line; + } + + my @accounts = $accounts_db->getAccountIDs(); + my $new_acc_no = $cgi->param('account'); + if (not $new_acc_no) + { + return error("Please enter an account number."); + } + + foreach my $account_no (@accounts) + { + if ($account_no == $new_acc_no) + { + return error("The account number $new_acc_no " . + "already exists, please use one which " . + "does not."); + } + } + + my $req = Convert::X509::Request->new($csr_data); + my $cn; + foreach my $part ($req->subject) + { + if ($part =~ /^cn=(.*)/i) + { + $cn = $1; + last; + } + } + + unless ($cn) + { + return error("The certificate request does not include a " . + "common name, which should be BACKUP-$new_acc_no."); + } + + unless ($cn eq "BACKUP-$new_acc_no") + { + return error("The certificate request includes the wrong " . + "common name. Expected " . + "BACKUP-$new_acc_no but found " . + "$cn."); + } + + my $out_cert_dir = "$ca_dir/clients"; + unless (-w $out_cert_dir) + { + return error("Cannot write to certificate directory " . + "$out_cert_dir as user " . + "" . getpwuid($UID) . "."); + } + + my $out_cert = "$out_cert_dir/$new_acc_no-cert.pem"; + if (-f $out_cert and not -w $out_cert) + { + return error("The certificate file $out_cert " . + "exists and is not writable as user " . + "$out_cert_dir as user " . + "" . getpwuid($UID) . "."); + } + + my $client_ca_cert_file = "$ca_dir/roots/clientCA.pem"; + unless (-r $client_ca_cert_file) + { + return error("The client CA certificate file " . + "$client_ca_cert_file " . + "is not readable by user " . + "" . getpwuid($UID) . "."); + } + + my $client_ca_key_file = "$ca_dir/keys/clientRootKey.pem"; + unless (-r $client_ca_key_file) + { + return error("The client CA key file " . + "$client_ca_key_file " . + "is not readable by user " . + "" . getpwuid($UID) . "."); + } + + my $serial_file = "$ca_dir/roots/clientCA.srl"; + unless (-w $serial_file) + { + return error("The certificate serial number file " . + "$serial_file " . + "is not writable by user " . + "" . getpwuid($UID) . "."); + } + + my $outputfile = File::Temp->new("bbaccounts-openssl-output-XXXXXX"); + + if (system("openssl x509 -req -in $tempfile -sha1 " . + "-extensions usr_crt " . + "-CA $client_ca_cert_file " . + "-CAkey $client_ca_key_file " . + "-out $out_cert -days $sign_period " . + ">$outputfile 2>&1") != 0) + { + open ERR, "< $outputfile" or die "$outputfile: $!"; + my $errors = join("", ); + close ERR; + return error($cgi->p("Failed to sign certificate:") . + $cgi->pre($errors)); + } + + my $cert_uri = url($cgi, download => "cert", account => $new_acc_no); + my $ca_uri = url($cgi, download => "cacert"); + + print $cgi->div({-class=>"success"}, + $cgi->p("Account created. Please download the following " . + "files:") . + $cgi->ul( + $cgi->li($cgi->a({href=>$cert_uri}, + "Client Certificate")), + $cgi->li($cgi->a({href=>$ca_uri}, + "CA Certificate")) + ) + ); + + return 1; +} + +if ($cgi->param("create")) +{ + print $cgi->h2("Account Creation"); + create_account($cgi); +} + +print $cgi->h2("Accounts"); +print $cgi->start_table({-border=>0, -class=>"numbers"}); + +print $cgi->Tr( + $cgi->th("Account"), + $cgi->th('Used'), $cgi->th('%'), + $cgi->th('Old files'), $cgi->th('%'), + $cgi->th('Deleted files'), $cgi->th('%'), + $cgi->th('Directories'), $cgi->th('%'), + $cgi->th('Soft limit'), $cgi->th('%'), + $cgi->th('Hard limit'), + $cgi->th('Actions') + ); + +sub human_format($) +{ + my ($kb) = @_; + die "bad format in value: expected number followed by kB, " . + "found '$kb'" unless $kb =~ /^(\d+) (kB)$/; + + my $value = $1; + my $units = $2; + + if ($value > 1024) + { + $value /= 1024; + $units = "MB"; + } + + if ($value > 1024) + { + $value /= 1024; + $units = "GB"; + } + + $value = sprintf("%.1f", $value); + return "$value $units"; +} + +sub bbstoreaccounts_format($) +{ + my ($kb) = @_; + die unless $kb =~ /^(\d+) (kB)$/; + + my $value = $1; + my $units = "K"; + + unless ($value % 1024) + { + $value /= 1024; + $units = "M"; + } + + unless ($value % 1024) + { + $value /= 1024; + $units = "G"; + } + + return "$value$units"; +} + +sub get_account_info($) +{ + my ($account) = @_; + + open BBSA, "$bbstoreaccounts -c $bbstored_conf_file -m info $account |" + or die "Failed to get account info for $account: $!"; + + my $account_info = {}; + + while (my $line = ) + { + unless ($line =~ m/([^:]*): (.*)/) + { + die "Bad format in bbstoreaccounts info output " . + "for account $account: '$line'"; + } + + my $name = $1; + my $value = $2; + + if ($value =~ /(.*), (.*)/) + { + $account_info->{$name} = [$1, $2]; + } + else + { + $account_info->{$name} = $value; + } + } + + return $account_info; +} + +sub format_account_info($) +{ + my ($values) = @_; + my $kb = $values->[0]; + my $pc = $values->[1]; + return $cgi->td(human_format($kb)), $cgi->td($values->[1]); +} + +my %account_numbers; + +my @accounts = $accounts_db->getAccountIDs(); +foreach my $i (@accounts) +{ + die "Duplicate account number $i" if $account_numbers{hex($i)}; + $account_numbers{hex($i)} = 1; + + # Find out what account is on what diskset. + my $disk = $accounts_db->getDisk($i); + + # store limits + my $account_info = get_account_info($i); + + print $cgi->Tr( + $cgi->td($i), + format_account_info($account_info->{'Used'}), + format_account_info($account_info->{'Old files'}), + format_account_info($account_info->{'Deleted files'}), + format_account_info($account_info->{'Directories'}), + format_account_info($account_info->{'Soft limit'}), + $cgi->td(human_format($account_info->{'Hard limit'}[0])), + $cgi->td($cgi->a({-href=>url($cgi, account=>$i)}, + "Edit")) + ); +} + +print $cgi->end_table(); + +my $account_no = $cgi->param("account"); +$account_no =~ tr/0-9a-fA-F//cd; + +if (not $cgi->param("showcreate")) +{ + print $cgi->start_form, + $cgi->submit(-name=>"showcreate", + -value=>"Create Account"), + $cgi->end_form(); +} + +if ($account_no) +{ + print $cgi->h2("Edit Account"); + my $account_info = get_account_info($account_no); + $cgi->param("account", $account_no); + $cgi->param("soft_limit", + bbstoreaccounts_format($account_info->{'Soft limit'}[0])); + $cgi->param("hard_limit", + bbstoreaccounts_format($account_info->{'Hard limit'}[0])); +} +elsif ($cgi->param("showcreate")) +{ + print $cgi->h2("Create Account"); +} + +if ($account_no or $cgi->param("showcreate")) +{ + my $new_account_no = 1; + while ($account_numbers{$new_account_no}) + { + $new_account_no++; + } + + my $disksets_conf = BoxBackup::Config::DiskSets->new($raidfile_conf_file); + my @disk_names = $disksets_conf->getListofDisks(); + my @disk_numbers; + my %disk_labels; + + foreach my $name (@disk_names) + { + my $num = $disksets_conf->getParamVal($name, "SetNumber"); + push @disk_numbers, $num; + $disk_labels{$num} = $name; + } + + print $cgi->start_multipart_form(), + $cgi->start_table(); + + if ($account_no) + { + print $cgi->Tr( + $cgi->th("Account Number"), + $cgi->td($account_no . + $cgi->hidden("account", $account_no)) + ); + } + else + { + print $cgi->Tr( + $cgi->th("Account Number"), + $account_no ? $account_no : + $cgi->td($cgi->textfield(-name => "account", + -default => sprintf("%x", $new_account_no))), + ); + } + + if (not $account_no) + { + print $cgi->Tr( + $cgi->th("Disk Set"), + $cgi->td($cgi->popup_menu(-name => "disk_set", + -values => \@disk_numbers, + -labels => \%disk_labels)) + ); + } + + print $cgi->Tr( + $cgi->th("Soft Limit"), + $cgi->td($cgi->textfield(-name => "soft_limit", + -default => "10G")) + ), + $cgi->Tr( + $cgi->th("Hard Limit"), + $cgi->td($cgi->textfield(-name => "hard_limit", + -default => "20G")) + ), + $cgi->Tr( + $cgi->th("Certificate Request"), + $cgi->td($cgi->filefield({ + -name => "req", + -default => "*.crt" + })) + ); + + if ($account_no) + { + print $cgi->Tr( + $cgi->th(), + $cgi->td($cgi->submit(-name => "update", + -value => "Update Account")) + ); + } + else + { + print $cgi->Tr( + $cgi->th(), + $cgi->td($cgi->submit(-name => "create", + -value => "Create Account")) + ); + } + + print $cgi->end_table(), $cgi->end_form(); +} + +print $cgi->end_html; + +exit 0; diff --git a/contrib/bbadmin/apache.conf b/contrib/bbadmin/apache.conf new file mode 100644 index 00000000..e22668ab --- /dev/null +++ b/contrib/bbadmin/apache.conf @@ -0,0 +1,14 @@ +Alias /bbadmin /var/www/localhost/bbadmin + + + AuthType basic + AuthName "Box Backup Web Management Interface" + AuthUserFile /etc/apache2/bbadmin.cgi.htpasswd + Require valid-user + + Allow from all + + Options ExecCGI + AddHandler cgi-script .cgi + DirectoryIndex accounts.cgi + diff --git a/contrib/bbadmin/bb.css b/contrib/bbadmin/bb.css new file mode 100644 index 00000000..76d48e93 --- /dev/null +++ b/contrib/bbadmin/bb.css @@ -0,0 +1,70 @@ +body +{ + background: #edeef3; +} + +table +{ + border-spacing: 0px; +} + +h1, th +{ + background: #e4e6ed; +} + +h1 +{ + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid #fff; +} + +td, th +{ + border-top: 1px solid #fff; + border-bottom: 1px solid #c4c4d5; + margin: 0px; + padding: 0.2em 0.5em; +} + +th +{ + text-align: left; +} + +table.numbers td +{ + text-align: right; +} + +div.error, div.success +{ + margin: 1em; +} + +div.error>*, div.success>* +{ + margin: 0.5em; +} + +div.error +{ + background: #fdd; + border: 2px solid #c00; +} + +div.success +{ + background: #dfd; + border: 2px solid #0c0; +} + +h2, table, p +{ + margin: 0.5em; +} + +form +{ + margin: 0.5em 0; +} diff --git a/contrib/bbreporter/LICENSE b/contrib/bbreporter/LICENSE new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/contrib/bbreporter/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/contrib/bbreporter/bbreporter.py b/contrib/bbreporter/bbreporter.py new file mode 100755 index 00000000..9c8253f1 --- /dev/null +++ b/contrib/bbreporter/bbreporter.py @@ -0,0 +1,538 @@ +#!/usr/bin/env python +# BoxBackupReporter - Simple script to report on backups that have been +# performed using BoxBackup. +# +# Copyright: (C) 2007 Three A IT Limited +# Author: Kenny Millington +# +# Credit: This script is based on the ideas of BoxReport.pl by Matt Brown of +# Three A IT Support Limited. +# +################################################################################ +# !! Important !! +# To make use of this script you need to run the boxbackup client with the -v +# commandline option and set LogAllFileAccess = yes in your bbackupd.conf file. +# +# Notes on lazy mode: +# If reporting on lazy mode backups you absolutely must ensure that +# logrotate (or similar) rotates the log files at the same rate at +# which you run this reporting script or you will report on the same +# backup sessions on each execution. +# +# Notes on --rotate and log rotation in general: +# The use-case for --rotate that I imagine is that you'll add a line like the +# following into your syslog.conf file:- +# +# local6.* -/var/log/box +# +# Then specifying --rotate to this script will make it rotate the logs +# each time you report on the backup so that you don't risk a backup session +# being spread across two log files (e.g. syslog and syslog.0). +# +# NB: To do this you'll need to prevent logrotate/syslog from rotating your +# /var/log/box file. On Debian based distros you'll need to edit two files. +# +# First: /etc/cron.daily/sysklogd, find the following line and make the +# the required change: +# Change: for LOG in `syslogd-listfiles` +# To: for LOG in `syslogd-listfiles -s box` +# +# Second: /etc/cron.weekly/sysklogd, find the following line and make the +# the required change: +# Change: for LOG in `syslogd-listfiles --weekly` +# To: for LOG in `syslogd-listfiles --weekly -s box` +# +# Alternatively, if suitable just ensure the backups stop before the +# /etc/cron.daily/sysklogd file runs (usually 6:25am) and report on it +# before the files get rotated. (If going for this option I'd just use +# the main syslog file instead of creating a separate log file for box +# backup since you know for a fact the syslog will get rotated daily.) +# +################################################################################ +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +# If sendmail is not in one of these paths, add the path. +SENDMAIL_PATHS = ["/usr/sbin/", "/usr/bin/", "/bin/" , "/sbin/"] + +# The name of the sendmail binary, you probably won't need to change this. +SENDMAIL_BIN = "sendmail" + +# Number of files to rotate around +ROTATE_COUNT = 7 + +# Import the required libraries +import sys, os, re, getopt, shutil, gzip + +class BoxBackupReporter: + class BoxBackupReporterError(Exception): + pass + + def __init__(self, config_file="/etc/box/bbackupd.conf", + log_file="/var/log/syslog", email_to=None, + email_from="report@boxbackup", rotate=False, + verbose=False, stats=False, sort=False, debug=False): + + # Config options + self.config_file = config_file + self.log_file = log_file + self.email_to = email_to + self.email_from = email_from + self.rotate_log_file = rotate + self.verbose_report = verbose + self.usage_stats = stats + self.sort_files = sort + self.debug = debug + + # Regex's + self.re_automatic_backup = re.compile(" *AutomaticBackup *= *no", re.I) + self.re_syslog = re.compile("(\S+) +(\S+) +([\d:]+) +(\S+) +([^:]+): +"+ + "(?:[A-Z]+:)? *([^:]+): *(.*)") + + # Initialise report + self.reset() + + def _debug(self, msg): + if self.debug: + sys.stderr.write("[bbreporter.py Debug]: %s\n" % msg) + + def reset(self): + # Reset report data to default values + self.hostname = "" + self.patched_files = [] + self.synced_files = [] + self.uploaded_files = [] + self.warnings = [] + self.errors = [] + self.stats = None + self.start_datetime = "Unknown" + self.end_datetime = "Unfinished" + self.report = "No report generated" + + def run(self): + try: + self._determine_operating_mode() + + if self.lazy_mode: + self._debug("Operating in LAZY MODE.") + else: + self._debug("Operating in SNAPSHOT MODE.") + + except IOError: + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Config file \"%s\" could not be read." % self.config_file) + + try: + self._parse_syslog() + except IOError: + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Log file \"%s\" could not be read." % self.log_file) + + self._parse_stats() + self._generate_report() + + def deliver(self): + # If we're not e-mailing the report then just dump it to stdout + # and return. + if self.email_to is None: + print self.report + # Now that we've delivered the report it's time to rotate the logs + # if we're requested to do so. + self._rotate_log() + return + + # Locate the sendmail binary + sendmail = self._locate_sendmail() + if(sendmail is None): + raise BoxBackupReporter.BoxBackupReporterError("Error: "+\ + "Could not find sendmail binary - Unable to send e-mail!") + + + # Set the subject based on whether we think we failed or not. + # (suffice it to say I consider getting an error and backing up + # no files a failure or indeed not finding a start time in the logs). + subject = "BoxBackup Reporter (%s) - " % self.hostname + if self.start_datetime == "Unknown" or\ + (len(self.patched_files) == 0 and len(self.synced_files) == 0 and\ + len(self.uploaded_files) == 0): + subject = subject + "FAILED" + else: + subject = subject + "SUCCESS" + + if len(self.errors) > 0: + subject = subject + " (with errors)" + + # Prepare the e-mail message. + mail = [] + mail.append("To: " + self.email_to) + mail.append("From: " + self.email_from) + mail.append("Subject: " + subject) + mail.append("") + mail.append(self.report) + + # Send the mail. + p = os.popen(sendmail + " -t", "w") + p.write("\r\n".join(mail)) + p.close() + + # Now that we've delivered the report it's time to rotate the logs + # if we're requested to do so. + self._rotate_log() + + def _determine_operating_mode(self): + # Scan the config file and determine if we're running in lazy or + # snapshot mode. + cfh = open(self.config_file) + + for line in cfh: + if not line.startswith("#"): + if self.re_automatic_backup.match(line): + self.lazy_mode = False + cfh.close() + return + + self.lazy_mode = True + cfh.close() + + def _parse_syslog(self): + lfh = open(self.log_file) + + patched_files = {} + uploaded_files = {} + synced_files = {} + + for line in lfh: + # Only run the regex if we find a box backup entry. + if line.find("Box Backup") > -1 or line.find("bbackupd") > -1: + raw_data = self.re_syslog.findall(line) + try: + data = raw_data[0] + except IndexError: + # If the regex didn't match it's not a message that we're + # interested in so move to the next line. + continue + + # Set the hostname, it shouldn't change in a log file + self.hostname = data[3] + + # If we find the backup-start event then set the start_datetime. + if data[6].find("backup-start") > -1: + # If we're not in lazy mode or the start_datetime hasn't + # been set then reset the data and set it. + # + # If we're in lazy mode and encounter a second backup-start + # we don't want to change the start_datetime likewise if + # we're not in lazy mode we do want to and we want to reset + # so we only capture the most recent session. + if not self.lazy_mode or self.start_datetime == "Unknown": + self._debug("Reset start dtime with old time: %s." % + self.start_datetime) + + # Reset ourselves + self.reset() + + # Reset our temporary variables which we store + # the files in. + patched_files = {} + uploaded_files = {} + synced_files = {} + + self.start_datetime = data[1]+" "+data[0]+ " "+data[2] + self._debug("Reset start dtime with new time %s." % + self.start_datetime) + + # If we find the backup-finish event then set the end_datetime. + elif data[6].find("backup-finish") > -1: + self.end_datetime = data[1] + " " + data[0] + " " + data[2] + self._debug("Set end dtime: %s" % self.end_datetime) + + # Only log the events if we have our start time. + elif self.start_datetime != "Unknown": + # We found a patch event, add the file to the patched_files. + if data[5] == "Uploading patch to file": + patched_files[data[6]] = "" + + # We found an upload event, add to uploaded files. + elif data[5] == "Uploading complete file": + uploaded_files[data[6]] = "" + + # We found another upload event. + elif data[5] == "Uploaded file": + uploaded_files[data[6]] = "" + + # We found a sync event, add the file to the synced_files. + elif data[5] == "Synchronised file": + synced_files[data[6]] = "" + + # We found a warning, add the warning to the warnings. + elif data[5] == "WARNING": + self.warnings.append(data[6]) + + # We found an error, add the error to the errors. + elif data[5] == "ERROR": + self.errors.append(data[6]) + + + self.patched_files = patched_files.keys() + self.uploaded_files = uploaded_files.keys() + self.synced_files = synced_files.keys() + + # There's no point running the sort functions if we're not going + # to display the resultant lists. + if self.sort_files and self.verbose_report: + self.patched_files.sort() + self.uploaded_files.sort() + + + lfh.close() + + def _parse_stats(self): + if(not self.usage_stats): + return + + # Grab the stats from bbackupquery + sfh = os.popen("bbackupquery usage quit", "r") + raw_stats = sfh.read() + sfh.close() + + # Parse the stats + stats_re = re.compile("commands.[\n ]*\n(.*)\n+", re.S) + stats = stats_re.findall(raw_stats) + + try: + self.stats = stats[0] + except IndexError: + self.stats = "Unable to retrieve usage information." + + def _generate_report(self): + if self.start_datetime == "Unknown": + self.report = "No report data has been found." + return + + total_files = len(self.patched_files) + len(self.uploaded_files) + + report = [] + report.append("--------------------------------------------------") + report.append("Report Title : Box Backup - Backup Statistics") + report.append("Report Period : %s - %s" % (self.start_datetime, + self.end_datetime)) + report.append("--------------------------------------------------") + report.append("") + report.append("This is your box backup report, in summary:") + report.append("") + report.append("%d file(s) have been backed up." % total_files) + report.append("%d file(s) were uploaded." % len(self.uploaded_files)) + report.append("%d file(s) were patched." % len(self.patched_files)) + report.append("%d file(s) were synchronised." % len(self.synced_files)) + + report.append("") + report.append("%d warning(s) occurred." % len(self.warnings)) + report.append("%d error(s) occurred." % len(self.errors)) + report.append("") + report.append("") + + # If we asked for the backup stats and they're available + # show them. + if(self.stats is not None and self.stats != ""): + report.append("Your backup usage information follows:") + report.append("") + report.append(self.stats) + report.append("") + report.append("") + + # List the files if we've been asked for a verbose report. + if(self.verbose_report): + if len(self.uploaded_files) > 0: + report.append("Uploaded Files (%d)" % len(self.uploaded_files)) + report.append("---------------------") + for file in self.uploaded_files: + report.append(file) + report.append("") + report.append("") + + if len(self.patched_files) > 0: + report.append("Patched Files (%d)" % len(self.patched_files)) + report.append("---------------------") + for file in self.patched_files: + report.append(file) + report.append("") + report.append("") + + # Always output the warnings/errors. + if len(self.warnings) > 0: + report.append("Warnings (%d)" % len(self.warnings)) + report.append("---------------------") + for warning in self.warnings: + report.append(warning) + report.append("") + report.append("") + + if len(self.errors) > 0: + report.append("Errors (%d)" % len(self.errors)) + report.append("---------------------") + for error in self.errors: + report.append(error) + report.append("") + report.append("") + + self.report = "\r\n".join(report) + + def _locate_sendmail(self): + for path in SENDMAIL_PATHS: + sendmail = os.path.join(path, SENDMAIL_BIN) + if os.path.isfile(sendmail): + return sendmail + + return None + + def _rotate_log(self): + # If we're not configured to rotate then abort. + if(not self.rotate_log_file): + return + + # So we have these files to possibly account for while we process the + # rotation:- + # self.log_file, self.log_file.0, self.log_file.1.gz, self.log_file.2.gz + # self.log_file.3.gz....self.log_file.(ROTATE_COUNT-1).gz + # + # Algorithm:- + # * Delete last file. + # * Work backwards moving 5->6, 4->5, 3->4, etc... but stop at .0 + # * For .0 move it to .1 then gzip it. + # * Move self.log_file to .0 + # * Done. + + # If it exists, remove the oldest file. + if(os.path.isfile(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1))): + os.unlink(self.log_file + ".%d.gz" % (ROTATE_COUNT - 1)) + + # Copy through the other gzipped log files. + for i in range(ROTATE_COUNT - 1, 1, -1): + src_file = self.log_file + ".%d.gz" % (i - 1) + dst_file = self.log_file + ".%d.gz" % i + + # If the source file exists move/rename it. + if(os.path.isfile(src_file)): + shutil.move(src_file, dst_file) + + # Now we need to handle the .0 -> .1.gz case. + if(os.path.isfile(self.log_file + ".0")): + # Move .0 to .1 + shutil.move(self.log_file + ".0", self.log_file + ".1") + + # gzip the file. + fh = open(self.log_file + ".1", "r") + zfh = gzip.GzipFile(self.log_file + ".1.gz", "w") + zfh.write(fh.read()) + zfh.flush() + zfh.close() + fh.close() + + # If gzip worked remove the original .1 file. + if(os.path.isfile(self.log_file + ".1.gz")): + os.unlink(self.log_file + ".1") + + # Finally move the current logfile to .0 + shutil.move(self.log_file, self.log_file + ".0") + + +def stderr(text): + sys.stderr.write("%s\n" % text) + +def usage(): + stderr("Usage: %s [OPTIONS]\n" % sys.argv[0]) + stderr("Valid Options:-") + stderr(" --logfile=LOGFILE\t\t\tSpecify the logfile to process,\n"+\ + "\t\t\t\t\tdefault: /var/log/syslog\n") + + stderr(" --configfile=CONFIGFILE\t\tSpecify the bbackupd config file,\n "+\ + "\t\t\t\t\tdefault: /etc/box/bbackupd.conf\n") + + stderr(" --email-to=user@example.com\t\tSpecify the e-mail address(es)\n"+\ + "\t\t\t\t\tto send the report to, default is to\n"+\ + "\t\t\t\t\tdisplay the report on the console.\n") + + stderr(" --email-from=user@example.com\t\tSpecify the e-mail address(es)"+\ + "\n\t\t\t\t\tto set the From: address to,\n "+\ + "\t\t\t\t\tdefault: report@boxbackup\n") + + stderr(" --stats\t\t\t\tIncludes the usage stats retrieved from \n"+\ + "\t\t\t\t\t'bbackupquery usage' in the report.\n") + + stderr(" --sort\t\t\t\tSorts the file lists in verbose mode.\n") + + stderr(" --debug\t\t\t\tEnables debug output.\n") + + stderr(" --verbose\t\t\t\tList every file that was backed up to\n"+\ + "\t\t\t\t\tthe server, default is to just display\n"+\ + "\t\t\t\t\tthe summary.\n") + + stderr(" --rotate\t\t\t\tRotates the log files like logrotate\n"+\ + "\t\t\t\t\twould, see the comments for a use-case.\n") + +def main(): + # The defaults + logfile = "/var/log/syslog" + configfile = "/etc/box/bbackupd.conf" + email_to = None + email_from = "report@boxbackup" + rotate = False + verbose = False + stats = False + sort = False + debug = False + # Parse the options + try: + opts, args = getopt.getopt(sys.argv[1:], "dosrvhl:c:t:f:", + ["help", "logfile=", "configfile=","email-to=", + "email-from=","rotate","verbose","stats","sort", + "debug"]) + except getopt.GetoptError: + usage() + return + + for opt, arg in opts: + if(opt in ("--logfile","-l")): + logfile = arg + elif(opt in ("--configfile", "-c")): + configfile = arg + elif(opt in ("--email-to", "-t")): + email_to = arg + elif(opt in ("--email-from", "-f")): + email_from = arg + elif(opt in ("--rotate", "-r")): + rotate = True + elif(opt in ("--verbose", "-v")): + verbose = True + elif(opt in ("--stats", "-s")): + stats = True + elif(opt in ("--sort", "-o")): + sort = True + elif(opt in ("--debug", "-d")): + debug = True + elif(opt in ("--help", "-h")): + usage() + return + + # Run the reporter + bbr = BoxBackupReporter(configfile, logfile, email_to, email_from, + rotate, verbose, stats, sort, debug) + try: + bbr.run() + bbr.deliver() + except BoxBackupReporter.BoxBackupReporterError, error_msg: + print error_msg + +if __name__ == "__main__": + main() diff --git a/contrib/debian/README.txt b/contrib/debian/README.txt new file mode 100644 index 00000000..ebe5fdf7 --- /dev/null +++ b/contrib/debian/README.txt @@ -0,0 +1,9 @@ +These start scripts are for Debian GNU/Linux. If installed manually they should +be placed in /etc/init.d. To create the symbolic links for the appropriate run +levels execute the following commands: + +update-rc.d bbackupd defaults 90 +update-rc.d bbstored defaults 80 + +James Stark + diff --git a/contrib/debian/bbackupd.in b/contrib/debian/bbackupd.in new file mode 100644 index 00000000..c340939a --- /dev/null +++ b/contrib/debian/bbackupd.in @@ -0,0 +1,59 @@ +#! /bin/sh + +# Start and stop the Box Backup client daemon. +# Originally by James Stark, modified by Chris Wilson and James O'Gorman +# For support, visit http://www.boxbackup.org/trac/wiki/MailingLists + +NAME=bbackupd +LONGNAME="Box Backup Client daemon" +BINARY=@sbindir_expanded@/$NAME +CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf +PIDFILE=@localstatedir_expanded@/bbackupd/$NAME.pid + +test -x $BINARY || exit 0 +test -f $CONFIG || exit 0 + +start_stop() { + start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" +} + +start_stop_verbose() { + if start_stop "$@"; then + echo "." + else + echo " failed!" + exit 1 + fi +} + +case $1 in + start) + echo -n "Starting $LONGNAME: $NAME" + start_stop_verbose --start + ;; + + stop) + echo -n "Stopping $LONGNAME: $NAME" + start_stop_verbose --stop + ;; + + reload|force-reload) + echo -n "Reloading $LONGNAME configuration" + start_stop_verbose --stop --signal 1 + ;; + + restart) + echo -n "Restarting $LONGNAME: $NAME" + if start_stop --stop --retry 5 && start_stop --start; then + echo "." + else + echo " failed!" + exit 1 + fi + ;; + + *) + echo "Usage: $0 {start|stop|reload|force-reload|restart}" +esac + +exit 0 diff --git a/contrib/debian/bbstored.in b/contrib/debian/bbstored.in new file mode 100644 index 00000000..c9214537 --- /dev/null +++ b/contrib/debian/bbstored.in @@ -0,0 +1,59 @@ +#! /bin/sh + +# Start and stop the Box Backup server daemon. +# Originally by James Stark, modified by Chris Wilson and James O'Gorman +# For support, visit http://www.boxbackup.org/trac/wiki/MailingLists + +NAME=bbstored +LONGNAME="Box Backup Server daemon" +BINARY=@sbindir_expanded@/$NAME +CONFIG=@sysconfdir_expanded@/boxbackup/$NAME.conf +PIDFILE=@localstatedir_expanded@/run/$NAME.pid + +test -x $BINARY || exit 0 +test -f $CONFIG || exit 0 + +start_stop() { + start-stop-daemon --quiet --exec $BINARY --pidfile $PIDFILE "$@" +} + +start_stop_verbose() { + if start_stop "$@"; then + echo "." + else + echo " failed!" + exit 1 + fi +} + +case $1 in + start) + echo -n "Starting $LONGNAME: $NAME" + start_stop_verbose --start + ;; + + stop) + echo -n "Stopping $LONGNAME: $NAME" + start_stop_verbose --stop + ;; + + reload|force-reload) + echo -n "Reloading $LONGNAME configuration" + start_stop_verbose --stop --signal 1 + ;; + + restart) + echo -n "Restarting $LONGNAME: $NAME" + if start_stop --stop --retry 5 && start_stop --start; then + echo "." + else + echo " failed!" + exit 1 + fi + ;; + + *) + echo "Usage: $0 {start|stop|reload|force-reload|restart}" +esac + +exit 0 diff --git a/contrib/mac_osx/org.boxbackup.bbackupd.plist.in b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in new file mode 100644 index 00000000..3b7b9307 --- /dev/null +++ b/contrib/mac_osx/org.boxbackup.bbackupd.plist.in @@ -0,0 +1,22 @@ + + + + + Label + org.boxbackup.bbackupd + OnDemand + + RunAtLoad + + ProgramArguments + + @prefix@/sbin/bbackupd + -F + @prefix@/etc/boxbackup/bbackupd.conf + + LowPriorityIO + + Nice + 1 + + diff --git a/contrib/mac_osx/org.boxbackup.bbstored.plist.in b/contrib/mac_osx/org.boxbackup.bbstored.plist.in new file mode 100644 index 00000000..531ab240 --- /dev/null +++ b/contrib/mac_osx/org.boxbackup.bbstored.plist.in @@ -0,0 +1,22 @@ + + + + + Label + org.boxbackup.bbstored + OnDemand + + RunAtLoad + + ProgramArguments + + @prefix@/sbin/bbstored + -F + @prefix@/etc/boxbackup/bbackupd.conf + + LowPriorityIO + + Nice + 1 + + diff --git a/contrib/redhat/README.txt b/contrib/redhat/README.txt new file mode 100644 index 00000000..cfc8d968 --- /dev/null +++ b/contrib/redhat/README.txt @@ -0,0 +1,7 @@ +These start scripts are for Fedora Core or RedHat Enterprise Linux. If +installed manually they should be placed in /etc/rc.d/init.d. + +They may also work for Mandrake. + +Martin Ebourne +martin@zepler.org diff --git a/contrib/redhat/bbackupd.in b/contrib/redhat/bbackupd.in new file mode 100644 index 00000000..2f51137a --- /dev/null +++ b/contrib/redhat/bbackupd.in @@ -0,0 +1,83 @@ +#! /bin/bash +# +# bbackupd Start/Stop the box backup client daemon. +# +# chkconfig: 345 93 07 +# description: bbackupd is the client side deamon for Box Backup, \ +# a completely automatic on-line backup system. +# processname: bbackupd +# config: @sysconfdir_expanded@/box +# pidfile: @localstatedir_expanded@/bbackupd.pid + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +# See how we were called. + +prog="bbackupd" + +# Check that configuration exists. +[ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 + +start() { + echo -n $"Starting $prog: " + daemon @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog + return $RETVAL +} + +rhstatus() { + status @sbindir_expanded@/$prog +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog configuration: " + killproc @sbindir_expanded@/$prog -HUP + retval=$? + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + reload + ;; + status) + rhstatus + ;; + condrestart) + [ -f /var/lock/subsys/$prog ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" + exit 1 +esac + +exit $? diff --git a/contrib/redhat/bbstored.in b/contrib/redhat/bbstored.in new file mode 100644 index 00000000..755a8569 --- /dev/null +++ b/contrib/redhat/bbstored.in @@ -0,0 +1,83 @@ +#! /bin/bash +# +# bbstored Start/Stop the box backup server daemon. +# +# chkconfig: 345 93 07 +# description: bbstored is the server side daemon for Box Backup, \ +# a completely automatic on-line backup system. +# processname: bbstored +# config: @sysconfdir_expanded@/box +# pidfile: @localstatedir_expanded@/bbstored.pid + +# Source function library. +. /etc/init.d/functions + +RETVAL=0 + +# See how we were called. + +prog="bbstored" + +# Check that configuration exists. +[ -f @sysconfdir_expanded@/box/$prog.conf ] || exit 0 + +start() { + echo -n $"Starting $prog: " + daemon @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && touch /var/lock/subsys/$prog + return $RETVAL +} + +stop() { + echo -n $"Stopping $prog: " + killproc @sbindir_expanded@/$prog + RETVAL=$? + echo + [ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$prog + return $RETVAL +} + +rhstatus() { + status @sbindir_expanded@/$prog +} + +restart() { + stop + start +} + +reload() { + echo -n $"Reloading $prog configuration: " + killproc @sbindir_expanded@/$prog -HUP + retval=$? + echo + return $RETVAL +} + +case "$1" in + start) + start + ;; + stop) + stop + ;; + restart) + restart + ;; + reload) + reload + ;; + status) + rhstatus + ;; + condrestart) + [ -f /var/lock/subsys/$prog ] && restart || : + ;; + *) + echo $"Usage: $0 {start|stop|status|reload|restart|condrestart}" + exit 1 +esac + +exit $? diff --git a/contrib/rpm/README.txt b/contrib/rpm/README.txt new file mode 100644 index 00000000..290a5252 --- /dev/null +++ b/contrib/rpm/README.txt @@ -0,0 +1,16 @@ +BUILDING AN RPM + +The easy way is to: + +rpmbuild -ta + +where is the archive you downloaded of Box Backup. + +This RPM should work on RedHat Enterprise, Fedora Core, Mandrake, SUSE, and +any similar distributions. It has been developed and tested on Fedora Core. + +Changes for SUSE Linux were provided by Chris Smith +(chris.smith@nothingbutnet.co.nz). + +Martin Ebourne +martin@zepler.org diff --git a/contrib/rpm/boxbackup.spec b/contrib/rpm/boxbackup.spec new file mode 100644 index 00000000..e782a6c3 --- /dev/null +++ b/contrib/rpm/boxbackup.spec @@ -0,0 +1,244 @@ +%define bb_user_id 171 +%define ident %{name}-%{version} + +# In official distribution tarballs, distribution files are copied to +# the base directory (where configure is), so distribution_dir should be empty. +# This is the default, overridden by the following block in non-distribution +# builds. +%define distribution_dir '' + +# BOX_PRIVATE_BEGIN +# In unofficial tarballs, made from svn, distribution files are still in +# distribution/boxbackup, so the following line overrides the default above: +# (this section will be removed automatically from distribution tarballs +# by infrastructure/makedistribution.pl) +%define distribution_dir distribution/boxbackup/ +# BOX_PRIVATE_END + +# Detect distribution. So far we only special-case SUSE. If you need to make +# any distro specific changes to get the package building on your system +# please email them to boxbackup-dev@boxbackup.org +#%define is_fc %(test -e %{_sysconfdir}/fedora-release && echo 1 || echo 0) +#%define is_mdk %(test -e %{_sysconfdir}/mandrake-release && echo 1 || echo 0) +#%define is_rh %(test -e %{_sysconfdir}/redhat-release && echo 1 || echo 0) +%define is_suse %(test -e %{_sysconfdir}/SuSE-release && echo 1 || echo 0) + +%if %{is_suse} +%define init_dir %{_sysconfdir}/init.d +%define distribution suse +%define rc_start rc +%else +%define init_dir %{_sysconfdir}/rc.d/init.d +%define distribution redhat +%define rc_start "service " +%endif + +Summary: An automatic on-line backup system for UNIX. +Name: boxbackup +Version: ###DISTRIBUTION-VERSION-NUMBER### +Release: 1%{?dist} +License: BSD +Group: Applications/Archiving +Packager: boxbackup-dev@boxbackup.org +URL: http://www.boxbackup.org/ +Source0: %{ident}.tgz +Requires: openssl >= 0.9.7a +BuildRoot: %{_tmppath}/%{ident}-%{release}-root +BuildRequires: openssl >= 0.9.7a, openssl-devel + +%description +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +%package client +Summary: An automatic on-line backup system for UNIX. +Group: Applications/Archiving + +%description client +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +This package contains the client. + +%package server +Summary: An automatic on-line backup system for UNIX. +Group: System Environment/Daemons + +%description server +Box Backup is a completely automatic on-line backup system. Backed up files +are stored encrypted on a filesystem on a remote server, which does not need +to be trusted. The backup server runs as a daemon on the client copying only +the changes within files, and old versions and deleted files are retained. It +is designed to be easy and cheap to run a server and (optional) RAID is +implemented in userland for ease of use. + +This package contains the server. + +%prep +%setup -q + +%build +echo -e '%{version}\n%{name}' > VERSION.txt +test -e configure || ./bootstrap +%configure + +make + +%install +rm -rf $RPM_BUILD_ROOT + +mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident} +mkdir -p $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter +mkdir -p $RPM_BUILD_ROOT%{_bindir} +mkdir -p $RPM_BUILD_ROOT%{_sbindir} +mkdir -p $RPM_BUILD_ROOT%{init_dir} +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd +mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored +mkdir -p $RPM_BUILD_ROOT%{_var}/lib/box + +install -m 644 -t $RPM_BUILD_ROOT%{_docdir}/%{ident} \ + BUGS.txt \ + VERSION.txt \ + ExceptionCodes.txt \ + LICENSE-GPL.txt \ + LICENSE-DUAL.txt \ + %{distribution_dir}CONTACT.txt \ + %{distribution_dir}DOCUMENTATION.txt \ + %{distribution_dir}LINUX.txt \ + %{distribution_dir}THANKS.txt + +install -m 644 contrib/bbreporter/LICENSE \ + $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter +install -m 755 contrib/bbreporter/bbreporter.py \ + $RPM_BUILD_ROOT%{_docdir}/%{ident}/bbreporter + +# Client +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbackupd.conf +install -m 755 contrib/%{distribution}/bbackupd $RPM_BUILD_ROOT%{init_dir} +%if %{is_suse} +ln -s ../../%{init_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir}/rcbbackupd +%endif +%define client_dir parcels/%{ident}-backup-client-linux-gnu +install %{client_dir}/bbackupd $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupquery $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupctl $RPM_BUILD_ROOT%{_sbindir} +install %{client_dir}/bbackupd-config $RPM_BUILD_ROOT%{_sbindir} + +# Server +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/bbstored.conf +touch $RPM_BUILD_ROOT%{_sysconfdir}/box/raidfile.conf +install -m 755 contrib/%{distribution}/bbstored $RPM_BUILD_ROOT%{init_dir} +%if %{is_suse} +ln -s ../../%{init_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir}/rcbbstored +%endif +%define server_dir parcels/%{ident}-backup-server-linux-gnu +install %{server_dir}/bbstored $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstoreaccounts $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstored-certs $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/bbstored-config $RPM_BUILD_ROOT%{_sbindir} +install %{server_dir}/raidfile-config $RPM_BUILD_ROOT%{_sbindir} + +%pre server +%{_sbindir}/useradd -c "Box Backup" -u %{bb_user_id} \ + -s /sbin/nologin -r -d / box 2> /dev/null || : + +%post client +/sbin/chkconfig --add bbackupd +if [ ! -f %{_sysconfdir}/box/bbackupd.conf ]; then + echo "You should run the following to configure the client:" + echo "bbackupd-config %{_sysconfdir}/box lazy " \ + "%{_var}/lib/box " + echo "Then follow the instructions. Use this to start the client:" + echo "%{rc_start}bbackupd start" +fi + +%post server +/sbin/chkconfig --add bbstored +if [ ! -f %{_sysconfdir}/box/bbstored.conf ]; then + echo "You should run the following to configure the server:" + echo "raidfile-config %{_sysconfdir}/box 2048 []" + echo "bbstored-config %{_sysconfdir}/box" `hostname` box + echo "Then follow the instructions. Use this to start the server:" + echo "%{rc_start}bbstored start" +fi + +%preun client +if [ $1 = 0 ]; then + %{init_dir}/bbackupd stop > /dev/null 2>&1 + /sbin/chkconfig --del bbackupd +fi + +%preun server +if [ $1 = 0 ]; then + %{init_dir}/bbstored stop > /dev/null 2>&1 + /sbin/chkconfig --del bbstored +fi + + +%clean +rm -rf $RPM_BUILD_ROOT + +%files client +%defattr(-,root,root,-) +%dir %attr(700,root,root) %{_sysconfdir}/box/bbackupd +%dir %attr(755,root,root) %{_var}/lib/box +%doc %{_docdir}/%{ident}/*.txt +%config %{init_dir}/bbackupd +%if %{is_suse} +%{_sbindir}/rcbbackupd +%endif +%config %ghost %{_sysconfdir}/box/bbackupd.conf +%{_sbindir}/bbackupd +%{_sbindir}/bbackupquery +%{_sbindir}/bbackupctl +%{_sbindir}/bbackupd-config + +%files server +%defattr(-,root,root,-) +%dir %attr(700,box,root) %{_sysconfdir}/box/bbstored +%config %{init_dir}/bbstored +%if %{is_suse} +%{_sbindir}/rcbbstored +%endif +%config %ghost %{_sysconfdir}/box/bbstored.conf +%config %ghost %{_sysconfdir}/box/raidfile.conf +%{_sbindir}/bbstored +%{_sbindir}/bbstoreaccounts +%{_sbindir}/bbstored-certs +%{_sbindir}/bbstored-config +%{_sbindir}/raidfile-config +%doc %{_docdir}/%{ident}/bbreporter + +%changelog +* Thu Apr 23 2009 Martin Ebourne +- Use dist tag in version + +* Thu May 29 2008 Martin Ebourne +- Fix paths to bbreporter files + +* Sat Jan 13 2006 Chris Wilson +- Support building from an unofficial tarball (from svn) by changing + %{distribution_dir} at the top. +- Write our RPM version number into VERSION.txt and hence compile it in + +* Wed Dec 28 2005 Martin Ebourne +- Box now uses autoconf so use configure macro + +* Fri Oct 1 2004 Martin Ebourne - 0.08-3 +- Moved most of the exes to /usr/sbin +- SUSE updates from Chris Smith + +* Fri Sep 24 2004 Martin Ebourne - 0.08-2 +- Added support for other distros +- Changes for SUSE provided by Chris Smith + +* Mon Sep 16 2004 Martin Ebourne - 0.07-1 +- Initial build diff --git a/contrib/solaris/bbackupd-manifest.xml.in b/contrib/solaris/bbackupd-manifest.xml.in new file mode 100644 index 00000000..ab30a78e --- /dev/null +++ b/contrib/solaris/bbackupd-manifest.xml.in @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/solaris/bbackupd-smf-method.in b/contrib/solaris/bbackupd-smf-method.in new file mode 100755 index 00000000..0ff610bf --- /dev/null +++ b/contrib/solaris/bbackupd-smf-method.in @@ -0,0 +1,24 @@ + +PIDFILE=@localstatedir_expanded@/bbackupd.pid + +case $1 in + + # SMF arguments (start and restart [really "refresh"]) +'start') + @sbindir_expanded@/bbackupd + ;; + +'restart') + if [ -f "$PIDFILE" ]; then + /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` + fi + ;; + +*) + echo "Usage: $0 { start | restart }" + exit 1 + ;; +esac + +exit $? + diff --git a/contrib/solaris/bbstored-manifest.xml.in b/contrib/solaris/bbstored-manifest.xml.in new file mode 100644 index 00000000..f7133677 --- /dev/null +++ b/contrib/solaris/bbstored-manifest.xml.in @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contrib/solaris/bbstored-smf-method.in b/contrib/solaris/bbstored-smf-method.in new file mode 100755 index 00000000..dbdb3e69 --- /dev/null +++ b/contrib/solaris/bbstored-smf-method.in @@ -0,0 +1,23 @@ +PIDFILE=@localstatedir_expanded@/bbstored.pid + +case $1 in + + # SMF arguments (start and restart [really "refresh"]) +'start') + @sbindir_expanded@/bbstored + ;; + +'restart') + if [ -f "$PIDFILE" ]; then + /usr/bin/kill -HUP `/usr/bin/cat $PIDFILE` + fi + ;; + +*) + echo "Usage: $0 { start | restart }" + exit 1 + ;; +esac + +exit $? + diff --git a/contrib/suse/README.txt b/contrib/suse/README.txt new file mode 100644 index 00000000..0f260b7a --- /dev/null +++ b/contrib/suse/README.txt @@ -0,0 +1,5 @@ +These start scripts are for SUSE Linux. If installed manually they should be +placed in /etc/init.d. + +Copyright (c)2004, Nothing But Net Limited + diff --git a/contrib/suse/bbackupd.in b/contrib/suse/bbackupd.in new file mode 100644 index 00000000..77fd40ba --- /dev/null +++ b/contrib/suse/bbackupd.in @@ -0,0 +1,103 @@ +#!/bin/sh +# +# Copyright (c)2004, Nothing But Net Limited +# +# +###################################################################### +# RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP +# SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: +# +# http://www.boxbackup.org/license.html +###################################################################### +# +# /etc/init.d/bbackupd +# and its symbolic link +# /(usr/)sbin/rcbbackupd +# +### BEGIN INIT INFO +# Provides: bbackupd +# Required-Start: $named $network $local_fs $syslog +# X-UnitedLinux-Should-Start: $time ypbind sendmail +# Required-Stop: $named $network $localfs $syslog +# X-UnitedLinux-Should-Stop: $time ypbind sendmail +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: BoxBackup client side daemon +# Description: Client daemon for the BoxBackup software +# that allows you to communicate with a bbstored server. +### END INIT INFO + +# Check for missing binaries (stale symlinks should not happen) +BBACKUPD_BIN=@sbindir_expanded@/bbackupd +if [ ! -x $BBACKUPD_BIN ] ; then + echo "$BBACKUPD_BIN not installed" + exit 5 +fi + +. /etc/rc.status + +# Reset status of this service +rc_reset + +case "$1" in + start) + echo -n "Starting bbackupd " + startproc $BBACKUPD_BIN + rc_status -v + ;; + + stop) + echo -n "Shutting down bbackupd " + killproc -TERM $BBACKUPD_BIN + rc_status -v + ;; + + try-restart|condrestart) + if test "$1" = "condrestart"; then + echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" + fi + $0 status + if test $? = 0; then + $0 restart + else + rc_reset # Not running is not a failure. + fi + rc_status + ;; + + restart) + $0 stop + $0 start + rc_status + ;; + + force-reload) + echo -n "Reload service bbackupd " + killproc -HUP $BBACKUPD_BIN + rc_status -v + ;; + + reload) + echo -n "Reload service bbackupd " + killproc -HUP $BBACKUPD_BIN + rc_status -v + ;; + + status) + echo -n "Checking for service bbackupd " + checkproc $BBACKUPD_BIN + rc_status -v + ;; + + probe) + test @sysconfdir_expanded@/box/bbackupd.conf \ + -nt @localstatedir_expanded@/bbackupd/bbackupd.pid \ + && echo reload + ;; + + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + +esac +rc_exit diff --git a/contrib/suse/bbstored.in b/contrib/suse/bbstored.in new file mode 100644 index 00000000..e84edfd1 --- /dev/null +++ b/contrib/suse/bbstored.in @@ -0,0 +1,104 @@ +#!/bin/sh +# +# Copyright (c)2004, Nothing But Net Limited +# +# +###################################################################### +# RELEASED AND PROVIDED TO YOU UNDER THE SAME LICENCE AS THE BOXBACKUP +# SUITE OF PROGRAMS. LICENCE MAY BE VIEWED HERE: +# +# http://www.boxbackup.org/license.html +###################################################################### +# +# /etc/init.d/bbstored +# and its symbolic link +# /(usr/)sbin/rcbbstored +# +### BEGIN INIT INFO +# Provides: bbstored +# Required-Start: $named $network $local_fs $syslog +# X-UnitedLinux-Should-Start: $time ypbind sendmail +# Required-Stop: $named $network $localfs $syslog +# X-UnitedLinux-Should-Stop: $time ypbind sendmail +# Default-Start: 3 5 +# Default-Stop: 0 1 2 6 +# Short-Description: BoxBackup server side daemon +# Description: Server daemon for the BoxBackup software, +# to which bbackupd clients connect. +### END INIT INFO +# + +# Check for missing binaries (stale symlinks should not happen) +BBSTORED_BIN=@sbindir_expanded@/bbstored +if [ ! -x $BBSTORED_BIN ] ; then + echo "$BBSTORED_BIN not installed" + exit 5 +fi + +. /etc/rc.status + +# Reset status of this service +rc_reset + +case "$1" in + start) + echo -n "Starting bbstored " + startproc $BBSTORED_BIN + rc_status -v + ;; + + stop) + echo -n "Shutting down bbstored " + killproc -TERM $BBSTORED_BIN + rc_status -v + ;; + + try-restart|condrestart) + if test "$1" = "condrestart"; then + echo "${attn} Use try-restart ${done}(LSB)${attn} rather than condrestart ${warn}(RH)${norm}" + fi + $0 status + if test $? = 0; then + $0 restart + else + rc_reset # Not running is not a failure. + fi + rc_status + ;; + + restart) + $0 stop + $0 start + rc_status + ;; + + force-reload) + echo -n "Reload service bbstored " + killproc -HUP $BBSTORED_BIN + rc_status -v + ;; + + reload) + echo -n "Reload service bbstored " + killproc -HUP $BBSTORED_BIN + rc_status -v + ;; + + status) + echo -n "Checking for service bbstored " + checkproc $BBSTORED_BIN + rc_status -v + ;; + + probe) + test @sysconfdir_expanded@/box/bbstored.conf \ + -nt @localstatedir_expanded@/run/bbstored.pid && echo reload + ;; + + *) + echo "Usage: $0 {start|stop|status|try-restart|restart|force-reload|reload|probe}" + exit 1 + ;; + +esac +rc_exit 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 + + +[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 new file mode 100755 index 00000000..dc68f235 --- /dev/null +++ b/contrib/windows/installer/boxbackup.mpi.in @@ -0,0 +1,3693 @@ +array set info { +AccountNo +{10005005} + +AllowLanguageSelection +{No} + +AppName +{<%BrandName%>} + +ApplicationID +{E10C6FD9-E524-28BD-B0AB3588F16C} + +ApplicationURL +{http://www.boxbackup.org/} + +AutoFileGroups +{No} + +AutoRefreshFiles +{Yes} + +BBVersionNo +{@box_version@} + +BrandName +{Box Backup} + +BuildFailureAction +{Fail (recommended)} + +CancelledInstallAction +{Rollback and Stop} + +CleanupCancelledInstall +{Yes} + +CommandLineFailureAction +{Fail (recommended)} + +Company +{Tebuco, Inc. and Ben Summers and Contributors} + +CompressionLevel +{6} + +CompressionMethod +{zlib} + +ConfigFileName +{<%InstallDir%>\bbackupd.conf} + +ConfigFileTemplate +{<%InstallDir%>\templates\template.conf} + +Copyright +{2003-2011 Tebuco, Inc. and Ben Summers and Contributors} + +CreateDesktopShortcut +{No} + +CreateQuickLaunchShortcut +{No} + +DefaultDirectoryLocation +{} + +DefaultLanguage +{English} + +DefaultToSystemLanguage +{Yes} + +EnableResponseFiles +{Yes} + +EncryptedKeyFilePassword +{Enter_EncryptedKeys_Password_Here} + +Ext +{.exe} + +ExtractSolidArchivesOnStartup +{No} + +Icon +{} + +IgnoreDirectories +{} + +IgnoreFiles +{} + +Image +{@build_dir@/docs/html/images/bblogo.png} + +IncludeDebugging +{Yes} + +InstallDirSuffix +{<%ShortAppName%>} + +InstallPassword +{} + +InstallVersion +{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} + +PackageDescription +{<%BrandName%> Backup Service} + +PackageLicense +{} + +PackageMaintainer +{Tebuco, Inc. and Ben Summers and Contributors} + +PackageName +{<%ShortAppName%>} + +PackagePackager +{Tebuco, Inc. and Ben Summers and Contributors} + +PackageRelease +{<%PatchVersion%>} + +PackageSummary +{} + +PackageVersion +{<%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} + +UpgradeApplicationID +{} + +UserInfoAcctNo +{<%AccountNo%>} + +UserInfoCompany +{} + +UserInfoEmail +{} + +UserInfoName +{} + +UserInfoPhone +{} + +Version +{@box_version@} + +ViewReadme +{No} + +WizardHeight +{365} + +WizardWidth +{500} + +} + +array set ::InstallJammer::InstallCommandLineOptions { +D +{{} Prefix No No {} {set the value of an option in the installer}} + +S +{InstallMode Switch No No Silent {run the installer in silent mode}} + +T +{Testing Switch Yes No {} {run installer without installing any files}} + +Y +{InstallMode Switch No No Default {accept all defaults and run the installer}} + +debug +{Debugging Switch Yes No {} {run installer in debug mode}} + +debugconsole +{ShowConsole Switch Yes No {} {run installer with a debug console open}} + +mode +{InstallMode Choice No No {Console Default Silent Standard} {set the mode to run the installer in}} + +prefix +{InstallDir String No No {} {set the installation directory}} + +test +{Testing Switch Yes No {} {run installer without installing any files}} + +} +array set ::InstallJammer::UninstallCommandLineOptions { +S +{InstallMode Switch No No Silent {run the uninstaller in silent mode}} + +Y +{InstallMode Switch No No Default {accept all defaults and run the uninstaller}} + +debugconsole +{ShowConsole Switch Yes No {} {run uninstaller with a debug console open}} + +mode +{UninstallMode Choice No No {Console Silent Standard} {set the mode to run the uninstaller in}} + +test +{Testing Switch Yes No {} {run uninstaller without uninstalling any files}} + +} +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 +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 +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 +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 +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 +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 +InstallComponent 3CFFF099-6122-46DD-9CE4-F5819434AC53 -setup Install -type action -title {Stop running service} -component ExecuteExternalProgram -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent FB697A88-2842-468E-9776-85E84B009340 -setup Install -type action -title {Remove installed service} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 41CDE776-2667-5CEB-312A-FC4C33A83E7F -setup Install -type action -title {Backup File} -component BackupFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 0D93323D-779D-44A8-1E0614E5285D -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 5CA3EA16-E37C-AABE-E576C4636EB0 -setup Install -type action -title {Execute Action} -component ExecuteAction -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent F5F21749-8B3A-49C6-9138-9C4D6D703D26 -setup Install -type action -title {Unpack Keys} -component ExecuteExternalProgram -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent FDF68FD6-BEA8-4A74-867D-5139F4D9E793 -setup Install -type action -title Wait -component Wait -active No -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent E56ADFF4-C15E-AEDB-A599-C468AF72C4BB -setup Install -type action -title {Copy File NotifySysAdmin} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent D9F88AC1-3D2D-F6DB-871E-3A0E016770B1 -setup Install -type action -title {Copy File config} -component CopyFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +InstallComponent 5F2C1F1C-B9F7-1642-59D9-A18318C1D70B -setup Install -type action -title {Replace Text In File} -component ReplaceTextInFile -active Yes -parent 37E627F2-E04B-AEF2-D566C017A4D6 +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 +InstallComponent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 -setup Install -type pane -title SetBackupLocations -component CustomBlankPane2 -active Yes -parent StandardInstall +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 +InstallComponent 362B6D6A-11BC-83CE-AFF6-410D8FBCF54D -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 2E2963BD-DDBD-738D-A910-B7F3F04946F9 -setup Install -type action -title ShowAddAnotherValue -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 93AA298C-B64E-5683-14D2-7B86F7DEFD2C -setup Install -type action -title BackupLocationName -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 3FDB57ED-598D-8A4E-CEF7-D90833305558 -setup Install -type action -title {Backup Directory} -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A -setup Install -type action -title BackupLocationShortName -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 855DE408-060E-3D35-08B5-1D9AB05C2865 -setup Install -type action -title Exclusions -component AddWidget -active Yes -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +InstallComponent 9892B25C-689B-5B8F-F0C9-B14FF6ACC40C -setup Install -type action -title {Execute Script} -component ExecuteScript -active No -parent 3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7 +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 +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 +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 +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 +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 +InstallComponent 6FEE2889-0338-1D49-60BF-1471F465AB26 -setup Install -type action -title {Write Text To File} -component WriteTextToFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8 -setup Install -type action -title {Copy File} -component CopyFile -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent D23DD94C-E517-7F34-FD59-802CB18AB887 -setup Install -type action -title {Adjust Line Feeds} -component AdjustLineFeeds -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 7D8E1902-2BC4-80D8-2C18771E7C22 -setup Install -type action -title {Installing service} -component ExecuteExternalProgram -active Yes -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 1C14291C-0971-4283-92E9-3808401303F5 -setup Install -type action -title {Starting service} -component ExecuteExternalProgram -active No -parent DA33B826-E633-A845-4646-76DFA78B907B +InstallComponent 6C323815-B9AB-FA94-4F5D152EBC51 -setup Install -type pane -title {Setup Complete} -component SetupComplete -active Yes -parent StandardInstall +InstallComponent 574198A7-7322-2F5E-02EF185D965C -setup Install -type pane -title {Copying Files} -component CopyFiles -active Yes -parent DefaultInstall +InstallComponent 8A761DBD-0640-D98C-9B3AD7672A8F -setup Install -type action -title {Disable Buttons} -component ModifyWidget -active Yes -parent 574198A7-7322-2F5E-02EF185D965C +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 +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 +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 +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 +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 +InstallComponent 2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6 -setup Install -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleInstall +InstallComponent 6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1 -setup Install -type action -title Exit -component Exit -active Yes -parent ConsoleInstall +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 +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 +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 +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 +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 +InstallComponent B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E -setup Install -type action -title {stopservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7 -setup Install -type action -title {Install Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 25AA533E-02FC-47D9-9273-25266B8FA1F9 -setup Install -type action -title {Remove Backup Service} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent CDD84DE3-C970-458F-9162-1A3CE0AA716B -setup Install -type action -title {startservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent B5DFEC63-92A9-4686-909E-0CE78A7069D6 -setup Install -type action -title {restartservice Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent C0452595-F3EB-43AD-BCA2-661437584636 -setup Install -type action -title {editconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 1AF5CD58-65C0-49CB-9A9D-994816CF414E -setup Install -type action -title {QueryUpload Shortcut} -component InstallProgramFolderShortcut -active No -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 1681CF85-A5D2-4D73-A3FC-52B2A6A1847D -setup Install -type action -title {killbackupprocess Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968 -setup Install -type action -title {reloadconfig Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 6F61CDA8-30C9-454F-82A3-9987E1203079 -setup Install -type action -title {sync Shortcut} -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +InstallComponent 9A663209-495B-ED16-09BE-457B61148022 -setup Install -type action -title QueryCurrent -component InstallProgramFolderShortcut -active Yes -parent 198905FB-9FAC-23DE-7422D25B8ECA +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 +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 +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 +InstallComponent E23AC50D-7CFB-800E-A99C6F4068F8 -setup Install -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsInstall +InstallComponent 3B8CDC8E-1239-D2E9-DF4CA6B1756D -setup Uninstall -type pane -title Uninstall -component Uninstall -active Yes -parent StandardUninstall +InstallComponent 19ADBDDB-1690-4A57-913E32A026C4 -setup Uninstall -type action -title {Modify Widget} -component ModifyWidget -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D +InstallComponent 7A983CD8-302C-4942-BE59-525C5B5FA2F2 -setup Uninstall -type action -title {Stop Backup Process} -component ExecuteExternalProgram -active Yes -parent 3B8CDC8E-1239-D2E9-DF4CA6B1756D +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 +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 +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 +InstallComponent 20CBDBEA-2217-457B-8D98-D692C4F591E9 -setup Uninstall -type action -title {Console Message} -component ConsoleMessage -active Yes -parent ConsoleUninstall +InstallComponent 7F85263E-CAE2-46BA-AAC0-6B89D20FD2DE -setup Uninstall -type action -title Exit -component Exit -active Yes -parent ConsoleUninstall +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 +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 +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 +InstallComponent 39D7394E-04E9-CA70-0034DB830BFE -setup Uninstall -type action -title {Uninstall Selected Files} -component UninstallSelectedFiles -active Yes -parent F4024A3E-9A6D-2726-5E0CFFA93054 +InstallComponent 39270FD8-932E-6132-7EF795ED9B93 -setup Uninstall -type actiongroup -title {Finish Actions} -active Yes -parent ActionGroupsUninstall +InstallComponent 905DA2E9-988C-2F27-BB1F5F274AC9 -setup Uninstall -type actiongroup -title {Cancel Actions} -active Yes -parent ActionGroupsUninstall + +array set Properties { +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Active +{No} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Comment +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,Conditions +{0 conditions} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ExecuteAction +{Before Next Pane is Displayed} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,ResultVirtualText +{BackupLocationName} + +0047FF40-0139-2A59-AAC0-A44D46D6F5CC,TclScript +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +0357FAE9-FCFD-26D8-6541D810CD61,CheckCondition +{Before Action is Executed} + +0357FAE9-FCFD-26D8-6541D810CD61,Filename +{<%ProgramReadme%>} + +05060263-E852-87AB-8D0F2954CAA6,Conditions +{0 conditions} + +0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,Prompt +{<%ConsoleSelectDestinationText%>} + +0C12D2D3-AEBC-42FE-A73A-0815EFB10DA5,VirtualText +{InstallDir} + +0D93323D-779D-44A8-1E0614E5285D,Conditions +{0 conditions} + +0D93323D-779D-44A8-1E0614E5285D,State +{disabled} + +0D93323D-779D-44A8-1E0614E5285D,Widget +{Back Button;Next Button} + +0FDBA082-90AB-808C-478A-A13E7C525336,Conditions +{0 conditions} + +0FDBA082-90AB-808C-478A-A13E7C525336,ExecuteAction +{Before Next Pane is Displayed} + +0FDBA082-90AB-808C-478A-A13E7C525336,ResultVirtualText +{BackupLocationNumber} + +0FDBA082-90AB-808C-478A-A13E7C525336,TclScript +{set BackupLocationNumber 1} + +13BD88FE-CD71-5AC7-E99C10B6CB28,CheckCondition +{Before Action is Executed} + +13BD88FE-CD71-5AC7-E99C10B6CB28,Filename +{<%ProgramReadme%>} + +1528F4F0-145C-A48D-A8526DBB6289,CheckCondition +{Before Action is Executed} + +1528F4F0-145C-A48D-A8526DBB6289,Filename +{<%ProgramExecutable%>} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Alias +{Stop Backup Windows Process} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,Conditions +{0 conditions} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,FileName +{<%ShortAppName%>-program-killbackupprocess} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,ShortcutName +{Stop backup process} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,TargetFileName +{<%InstallDir%>/tools/KillBackupProcess.bat} + +1681CF85-A5D2-4D73-A3FC-52B2A6A1847D,WorkingDirectory +{<%InstallDir%>} + +16D53E40-546B-54C3-088B1B5E3BBB,Background +{white} + +16D53E40-546B-54C3-088B1B5E3BBB,Conditions +{2 conditions} + +16D53E40-546B-54C3-088B1B5E3BBB,Text,subst +{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} + +17D8BA8E-5992-AA5C-F5ECB73A3433,Action +{Uninstall Actions} + +17D8BA8E-5992-AA5C-F5ECB73A3433,Conditions +{0 conditions} + +18C00430-D6B1-151F-307762B3A045,CheckCondition +{Before Action is Executed} + +18C00430-D6B1-151F-307762B3A045,Platform +{Windows} + +198905FB-9FAC-23DE-7422D25B8ECA,Alias +{Install Actions} + +198905FB-9FAC-23DE-7422D25B8ECA,Conditions +{0 conditions} + +19ADBDDB-1690-4A57-913E32A026C4,Conditions +{0 conditions} + +19ADBDDB-1690-4A57-913E32A026C4,State +{disabled} + +19ADBDDB-1690-4A57-913E32A026C4,Widget +{NextButton; CancelButton} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Active +{No} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Alias +{Upload File Listing} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Comment +{Upload list of backed up files for Tech Support purposes. May be blocked by firewalls.} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,Conditions +{0 conditions} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,FileName +{<%ShortAppName%>-program-TebucoSafeQuerypload} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,ShortcutName +{Upload Filelisting to TebucoSafe for review} + +1AF5CD58-65C0-49CB-9A9D-994816CF414E,TargetFileName +{<%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} + +1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Conditions +{0 conditions} + +1BEFB82C-C073-73D4-CFCE-F5DE7A674D9E,Message,subst +{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} + +1C14291C-0971-4283-92E9-3808401303F5,Comment +{Don't start it yet, need to install keys by hand.} + +1C14291C-0971-4283-92E9-3808401303F5,Conditions +{0 conditions} + +1C14291C-0971-4283-92E9-3808401303F5,ProgramCommandLine +{net start <%ServiceName%>} + +1C14291C-0971-4283-92E9-3808401303F5,WorkingDirectory +{<%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} + +1F9E8CB8-02C1-0416-1F7445B4147F,Comment +{Ask the user if they want to proceed with the install.} + +1F9E8CB8-02C1-0416-1F7445B4147F,Conditions +{2 conditions} + +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message,subst +{1} + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,CheckCondition +{Before Action is Executed} + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,Operator +{false} + +241BBFCE-4EB1-432F-94DD-69D444DDB6C0,String +{<%Answer%>} + +2583A547-11DE-1C27-B6D04B023CC0,CheckCondition +{Before Action is Executed} + +2583A547-11DE-1C27-B6D04B023CC0,Operator +{false} + +2583A547-11DE-1C27-B6D04B023CC0,String +{<%SilentMode%>} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Alias +{Remove Backup Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Comment +{Remove the Backup Windows Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,Conditions +{0 conditions} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,FileName +{<%ShortAppName%>-program-removeService} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,ShortcutName +{Remove Service} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,TargetFileName +{<%InstallDir%>/tools/RemoveService.bat} + +25AA533E-02FC-47D9-9273-25266B8FA1F9,WorkingDirectory +{<%InstallDir%>} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,Active +{No} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,Conditions +{0 conditions} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,ProgramCommandLine +{notepad <%ConfigFileName%>} + +28E76C8B-2605-4739-9FFE-9C2880C17E59,WorkingDirectory +{<%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} + +2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Conditions +{1 condition} + +2AC89879-6E9D-3D4E-F28E-5985EEBFAAA8,Message,subst +{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} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,Conditions +{0 conditions} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,ExecuteAction +{Before Next Pane is Displayed} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,ResultVirtualText +{AddBackupLocation} + +2BB06B72-DE53-2319-B1B8-351CDCBA2008,TclScript +{set AddBackupLocation no} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message,subst +{1} + +2C456223-3E1E-4D43-B31A-868EAD3241E1,Destination +{<%InstallDir%>} + +2C456223-3E1E-4D43-B31A-868EAD3241E1,Name +{Documents} + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Conditions +{0 conditions} + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text,subst +{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} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Comment +{.conf doesn't exist yet} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Conditions +{0 conditions} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,FileOpenAction +{Append to file} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,Files +{<%ConfigFileTemplate%>} + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite,subst +{1} + +32B08FB1-99DF-234E-8BAF-333E80AAC9F5,Conditions +{0 conditions} + +32B08FB1-99DF-234E-8BAF-333E80AAC9F5,FileName +{<%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%>} + +32DC8FB1-A04B-71AA-EC18496D4BD0,Conditions +{0 conditions} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,CheckCondition +{Before Action is Executed} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Message,subst +{1} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Title,subst +{1} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,TrueValue +{No} + +3379F80B-36D6-73DC-6FC1D6223A26,CheckCondition +{Before Action is Executed} + +3379F80B-36D6-73DC-6FC1D6223A26,Operator +{false} + +3379F80B-36D6-73DC-6FC1D6223A26,String +{<%InstallStopped%>} + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Active +{No} + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,Conditions +{0 conditions} + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,ResultVirtualText +{BackupLocationExclusions} + +362B6D6A-11BC-83CE-AFF6-410D8FBCF54D,TclScript +{set BackupLocationExclusions ""} + +37E627F2-E04B-AEF2-D566C017A4D6,BackButton,subst +{1} + +37E627F2-E04B-AEF2-D566C017A4D6,CancelButton,subst +{1} + +37E627F2-E04B-AEF2-D566C017A4D6,Caption,subst +{1} + +37E627F2-E04B-AEF2-D566C017A4D6,Conditions +{0 conditions} + +37E627F2-E04B-AEF2-D566C017A4D6,FileLabel,subst +{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} + +39270FD8-932E-6132-7EF795ED9B93,Alias +{Finish Actions} + +39270FD8-932E-6132-7EF795ED9B93,Conditions +{0 conditions} + +39B2B666-78D8-75E6-6EA071594D34,Conditions +{1 condition} + +39B2B666-78D8-75E6-6EA071594D34,ShortcutName +{Uninstall <%BrandName%>} + +39B2B666-78D8-75E6-6EA071594D34,TargetFileName +{<%Uninstaller%>} + +39B2B666-78D8-75E6-6EA071594D34,WorkingDirectory +{<%InstallDir%>} + +39D7394E-04E9-CA70-0034DB830BFE,Conditions +{0 conditions} + +3B6E2E7C-1A26-27F1-D578E383B128,Background +{white} + +3B6E2E7C-1A26-27F1-D578E383B128,Conditions +{2 conditions} + +3B6E2E7C-1A26-27F1-D578E383B128,Text,subst +{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} + +3B8CDC8E-1239-D2E9-DF4CA6B1756D,Conditions +{0 conditions} + +3B8CDC8E-1239-D2E9-DF4CA6B1756D,FileValue,subst +{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} + +3C7130B3-3206-403D-B09E-59D4A758FBAD,Action +{Uninstall Actions} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,Conditions +{0 conditions} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,IgnoreErrors +{Yes} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgramCommandLine +{net stop <%ServiceName%>} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,ProgressiveOutputWidget +{Message} + +3CFFF099-6122-46DD-9CE4-F5819434AC53,WorkingDirectory +{<%Temp%>} + +3D0D1898-4C65-3E66-F82F56581E87,CheckCondition +{Before Action is Executed} + +3D0D1898-4C65-3E66-F82F56581E87,Operator +{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} + +3D33AA8C-0037-204B-39A339FD38BD,Conditions +{0 conditions} + +3D33AA8C-0037-204B-39A339FD38BD,Message,subst +{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} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Conditions +{0 conditions} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message,subst +{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} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Conditions +{0 conditions} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,LabelSide +{left} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Text,subst +{1} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Type +{browse entry} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,VirtualText +{BackupLocationPath} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Y +{70} + +3FE82C17-A3E2-4A57-A563-F80818B00B81,Default +{Yes} + +3FE82C17-A3E2-4A57-A563-F80818B00B81,Prompt +{<%InstallStartupText%>} + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,Conditions +{0 conditions} + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,Files +{*/*.conf;*/*.txt;*/*.pem;*/*.raw;*/*.exe;*/*.bat;*/*.dll} + +41CDE776-2667-5CEB-312A-FC4C33A83E7F,RenameFiles +{Yes} + +41D3E165-C263-5F80-0FEEC0AEE47A,BackButton,subst +{1} + +41D3E165-C263-5F80-0FEEC0AEE47A,CancelButton,subst +{1} + +41D3E165-C263-5F80-0FEEC0AEE47A,Caption,subst +{1} + +41D3E165-C263-5F80-0FEEC0AEE47A,Conditions +{1 condition} + +41D3E165-C263-5F80-0FEEC0AEE47A,Message,subst +{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} + +481451CC-F49C-D389-8645076F595B,Name +{Program Files} + +49E59F91-27F7-46D1-A1C1-19865C2392D3,Default +{Yes} + +49E59F91-27F7-46D1-A1C1-19865C2392D3,Prompt +{<%UninstallStartupText%>} + +49E80443-62DB-1C10-392D-1091AEA5ED88,Conditions +{1 condition} + +49E80443-62DB-1C10-392D-1091AEA5ED88,ExecuteAction +{Before Next Pane is Displayed} + +49E80443-62DB-1C10-392D-1091AEA5ED88,Pane +{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} + +4A9C852B-647E-EED5-5482FFBCC2AF,Name +{Default Component} + +4A9C852B-647E-EED5-5482FFBCC2AF,RequiredComponent +{Yes} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,CheckCondition +{Before Action is Executed} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message,subst +{1} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title,subst +{1} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,TrueValue +{No} + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,Conditions +{0 conditions} + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFilePercentage +{Yes} + +4D4A7BF0-7CCE-46E6-BDE5222F82D7,UpdateFileText +{Yes} + +4E643D8A-CA31-018D-57D7053C2CE8,CheckCondition +{Before Action is Executed} + +4E643D8A-CA31-018D-57D7053C2CE8,Filename +{<%ProgramExecutable%>} + +4EE35849-FAD7-170B-0E45-FA30636467B1,CheckCondition +{Before Next Pane is Displayed} + +4EE35849-FAD7-170B-0E45-FA30636467B1,EncryptedPassword +{<%InstallPasswordEncrypted%>} + +4EE35849-FAD7-170B-0E45-FA30636467B1,FailureFocus +{Password Entry} + +4EE35849-FAD7-170B-0E45-FA30636467B1,FailureMessage +{<%PasswordIncorrectText%>} + +4EE35849-FAD7-170B-0E45-FA30636467B1,UnencryptedPassword +{<%InstallPassword%>} + +4EF787E3-0643-DE46-15E64BAF0816,CheckCondition +{Before Action is Executed} + +4EF787E3-0643-DE46-15E64BAF0816,Platform +{Windows} + +52F0A238-57E1-A578-2CE4DA177B32,Conditions +{0 conditions} + +53588803-6B41-D9FC-A385906A5106,Conditions +{0 conditions} + +574198A7-7322-2F5E-02EF185D965C,BackButton,subst +{1} + +574198A7-7322-2F5E-02EF185D965C,CancelButton,subst +{1} + +574198A7-7322-2F5E-02EF185D965C,Caption,subst +{1} + +574198A7-7322-2F5E-02EF185D965C,Conditions +{0 conditions} + +574198A7-7322-2F5E-02EF185D965C,FileLabel,subst +{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} + +58E1119F-639E-17C9-5D3898F385AA,Conditions +{1 condition} + +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} + +592F46AE-8CEE-01F3-0BA7EBDCA4F4,CheckCondition +{Before Action is Executed} + +592F46AE-8CEE-01F3-0BA7EBDCA4F4,Filename +{<%ProgramExecutable%>} + +5CA3EA16-E37C-AABE-E576C4636EB0,Action +{Install Actions} + +5CA3EA16-E37C-AABE-E576C4636EB0,Conditions +{0 conditions} + +5EE78EF7-37CA-D440-3DB5-09136CD566B3,CheckCondition +{Before Action is Executed} + +5EE78EF7-37CA-D440-3DB5-09136CD566B3,String +{<%AddBackupLocation%>} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Conditions +{0 conditions} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,Files +{<%ConfigFileTemplate%>;*/*.bat;<%InstallDir%>/*.vbs} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,LineFeed +{Windows} + +5F2C1F1C-B9F7-1642-59D9-A18318C1D70B,StringMap +{"@@CUSTOMERCOMPANY@@" <%UserInfoCompany%> +"@@BRANDNAME@@" <%BrandName%> +"@@SERVICENAME@@" <%ServiceName%> +"@@CUSTOMERUSERNAME@@" <%UserInfoName%> +"@@CUSTOMERUSERPHONE@@" <%UserInfoPhone%> +"@@CUSTOMERUSEREMAIL@@" <%UserInfoEmail%> +"@@ACCTNO@@" <%UserInfoAcctNo%> +"@@INSTALLDIR@@" <%InstallDir%>} + +614C45B2-7515-780C-E444-7F165CF02DD7,Active +{No} + +614C45B2-7515-780C-E444-7F165CF02DD7,Conditions +{0 conditions} + +614C45B2-7515-780C-E444-7F165CF02DD7,ResultVirtualText +{BackupLocationShortName} + +614C45B2-7515-780C-E444-7F165CF02DD7,TclScript +{set BackupLocationShortName ""} + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Active +{No} + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Comment +{PJ removed. Is this the one at the top leve?} + +6652193C-5D4B-44B6-ABC6-D6E96D89E5DC,Conditions +{0 conditions} + +69FD7409-5E2A-143B-DABD1C3B1E67,Conditions +{2 conditions} + +6B4CB3C2-4799-4C9F-BA8E-1EE47C4606E1,ExitType +{Finish} + +6B966959-05D9-DB32-8D9C4AD2A3DF,CheckCondition +{Before Action is Executed} + +6B966959-05D9-DB32-8D9C4AD2A3DF,Platform +{Windows} + +6C323815-B9AB-FA94-4F5D152EBC51,BackButton,subst +{1} + +6C323815-B9AB-FA94-4F5D152EBC51,CancelButton,subst +{1} + +6C323815-B9AB-FA94-4F5D152EBC51,Caption,subst +{1} + +6C323815-B9AB-FA94-4F5D152EBC51,Conditions +{0 conditions} + +6C323815-B9AB-FA94-4F5D152EBC51,Message,subst +{1} + +6C323815-B9AB-FA94-4F5D152EBC51,NextButton,subst +{1} + +6D9D1ABC-7146-443F-9EE9-205D5CA6C830,CheckCondition +{Before Action is Executed} + +6D9D1ABC-7146-443F-9EE9-205D5CA6C830,String +{<%Property <%CurrentPane%> UserMustAcceptLicense%>} + +6E70FB1F-6A43-6C23-3242E965A0D0,Action +{Install Actions} + +6E70FB1F-6A43-6C23-3242E965A0D0,Conditions +{0 conditions} + +6F61CDA8-30C9-454F-82A3-9987E1203079,Alias +{Start a sync now.} + +6F61CDA8-30C9-454F-82A3-9987E1203079,Conditions +{0 conditions} + +6F61CDA8-30C9-454F-82A3-9987E1203079,FileName +{<%ShortAppName%>-program-sync} + +6F61CDA8-30C9-454F-82A3-9987E1203079,ShortcutName +{Sync now} + +6F61CDA8-30C9-454F-82A3-9987E1203079,TargetFileName +{<%InstallDir%>/tools/Sync.bat} + +6F61CDA8-30C9-454F-82A3-9987E1203079,WorkingDirectory +{<%InstallDir%>} + +6F94698F-0839-3ABF-0CF2DF05A4C8,CheckCondition +{Before Action is Executed} + +6F94698F-0839-3ABF-0CF2DF05A4C8,String +{<%CreateQuickLaunchShortcut%>} + +6FEE2889-0338-1D49-60BF-1471F465AB26,AppendNewline +{No} + +6FEE2889-0338-1D49-60BF-1471F465AB26,Comment +{Closing final BackupLocations bracket} + +6FEE2889-0338-1D49-60BF-1471F465AB26,Conditions +{0 conditions} + +6FEE2889-0338-1D49-60BF-1471F465AB26,FileOpenAction +{Append to file} + +6FEE2889-0338-1D49-60BF-1471F465AB26,Files +{<%ConfigFileTemplate%>} + +6FEE2889-0338-1D49-60BF-1471F465AB26,LineFeed +{Windows} + +6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite,subst +{1} + +738DD098-7E3B-BC89-875CDB93CBE2,CheckCondition +{Before Action is Executed} + +738DD098-7E3B-BC89-875CDB93CBE2,Platform +{Windows} + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Conditions +{0 conditions} + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Destination +{<%ConfigFileName%>} + +73DD4D07-B1DC-BA38-2B12-07EB24A7F0C8,Source +{<%ConfigFileTemplate%>} + +73EA65C1-3BE3-B190-55C3E99F6269,Conditions +{1 condition} + +748D673B-DFE6-5F74-329903ACE4DB,CheckCondition +{Before Action is Executed} + +748D673B-DFE6-5F74-329903ACE4DB,Filename +{<%ProgramExecutable%>} + +793D8178-0F51-7F07-BC5886586D3C,CheckCondition +{Before Action is Executed} + +793D8178-0F51-7F07-BC5886586D3C,Operator +{false} + +793D8178-0F51-7F07-BC5886586D3C,String +{<%InstallStopped%>} + +795EE61F-6C0D-4A8B-93E02AA3894A,CheckCondition +{Before Action is Executed} + +795EE61F-6C0D-4A8B-93E02AA3894A,String +{<%LaunchApplication%>} + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,CheckCondition +{Before Action is Executed} + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,Operator +{false} + +79DAC913-A33D-4ED6-9BAE-B3A2053C0F2C,String +{<%LicenseAccepted%>} + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,Conditions +{0 conditions} + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,ProgramCommandLine +{ServiceControl terminate} + +7A983CD8-302C-4942-BE59-525C5B5FA2F2,WorkingDirectory +{<%InstallDir%>} + +7B770A07-A785-5215-956FA82CF14E,Active +{No} + +7B770A07-A785-5215-956FA82CF14E,Conditions +{3 conditions} + +7B770A07-A785-5215-956FA82CF14E,ShortcutDirectory +{<%QUICK_LAUNCH%>} + +7B770A07-A785-5215-956FA82CF14E,ShortcutName +{<%BrandName%>} + +7B770A07-A785-5215-956FA82CF14E,TargetFileName +{<%ProgramExecutable%>} + +7B770A07-A785-5215-956FA82CF14E,WorkingDirectory +{<%InstallDir%>} + +7D8E1902-2BC4-80D8-2C18771E7C22,Conditions +{0 conditions} + +7D8E1902-2BC4-80D8-2C18771E7C22,ProgramCommandLine +{<%ServiceExeName%> -i -S <%ServiceName%> -c "<%ConfigFileName%>"} + +7D8E1902-2BC4-80D8-2C18771E7C22,ProgressiveOutputWidget +{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} + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Conditions +{0 conditions} + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text,subst +{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} + +848844B5-6103-9343-8B731B0BE4E0,Alias +{Startup Actions} + +848844B5-6103-9343-8B731B0BE4E0,Conditions +{0 conditions} + +84DA7F05-9FB7-CC36-9EC98F8A6826,CheckCondition +{Before Next Pane is Displayed} + +84DA7F05-9FB7-CC36-9EC98F8A6826,FailureMessage +{<%DirectoryPermissionText%>} + +84DA7F05-9FB7-CC36-9EC98F8A6826,Filename +{<%InstallDir%>} + +84DA7F05-9FB7-CC36-9EC98F8A6826,Permission +{can create} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Conditions +{0 conditions} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Height +{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} + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,CheckCondition +{Before Action is Executed} + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,Operator +{false} + +87DE6D78-81E1-495B-A214-B3FF3E7E5614,String +{<%Answer%>} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Active +{No} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Conditions +{1 condition} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,ExecuteAction +{After Pane is Finished} + +88A50FD5-480F-19A5-DA74-C915EB0A9765,Pane +{3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7} + +8A761DBD-0640-D98C-9B3AD7672A8F,Conditions +{0 conditions} + +8A761DBD-0640-D98C-9B3AD7672A8F,State +{disabled} + +8A761DBD-0640-D98C-9B3AD7672A8F,Widget +{Back Button;Next Button} + +8C866252-8760-9B08-FE569C25B60D,CheckCondition +{Before Action is Executed} + +8C866252-8760-9B08-FE569C25B60D,Filename +{<%ProgramExecutable%>} + +8E095096-F018-A880-429D-A2177A9B70EA,Active +{No} + +8E095096-F018-A880-429D-A2177A9B70EA,Conditions +{0 conditions} + +8E095096-F018-A880-429D-A2177A9B70EA,Text,subst +{1} + +8E095096-F018-A880-429D-A2177A9B70EA,X +{50} + +8E095096-F018-A880-429D-A2177A9B70EA,Y +{150} + +8E1A5944-5AF5-5906-16D395E386D8,Conditions +{0 conditions} + +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} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Conditions +{0 conditions} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Message,subst +{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} + +905DA2E9-988C-2F27-BB1F5F274AC9,Alias +{Cancel Actions} + +905DA2E9-988C-2F27-BB1F5F274AC9,Conditions +{0 conditions} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,BackButton,subst +{1} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,CancelButton,subst +{1} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Caption,subst +{1} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Conditions +{0 conditions} + +908CE221-5A3D-0A78-24A1-E7C91EBE38D4,Message,subst +{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} + +937C3FDD-FB28-98BD-3DAB276E59ED,Conditions +{3 conditions} + +937C3FDD-FB28-98BD-3DAB276E59ED,Text,subst +{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} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Comment +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,Conditions +{0 conditions} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,ResultVirtualText +{BackupLocationName} + +93AA298C-B64E-5683-14D2-7B86F7DEFD2C,TclScript +{set BackupLocationName "BackupLocation_${BackupLocationNumber}"} + +96A68CAC-9ED7-806C-086B104720FD,CheckCondition +{Before Action is Executed} + +96A68CAC-9ED7-806C-086B104720FD,String +{<%ErrorsOccurred%>} + +97ACF525-C075-8635-E019202A83D8,Comment +{Ask the user if they want to proceed with the uninstall.} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Active +{No} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,Conditions +{0 conditions} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,ResultVirtualText +{AddBackupLocation} + +9892B25C-689B-5B8F-F0C9-B14FF6ACC40C,TclScript +{set AddBackupLocation no} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Active +{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} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Conditions +{0 conditions} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message,subst +{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} + +9A663209-495B-ED16-09BE-457B61148022,Conditions +{0 conditions} + +9A663209-495B-ED16-09BE-457B61148022,FileName +{<%ShortAppName%>-program-QueryCurrent} + +9A663209-495B-ED16-09BE-457B61148022,ShortcutName +{Query Current Only} + +9A663209-495B-ED16-09BE-457B61148022,TargetFileName +{<%InstallDir%>/tools/QueryOutputCurrent.bat} + +9A663209-495B-ED16-09BE-457B61148022,WorkingDirectory +{<%InstallDir%>} + +9D101299-B80C-441B-8685-6E3AC61808E8,Alias +{Remote Control} + +9D101299-B80C-441B-8685-6E3AC61808E8,Comment +{Get tech support via remote control} + +9D101299-B80C-441B-8685-6E3AC61808E8,Conditions +{0 conditions} + +9D101299-B80C-441B-8685-6E3AC61808E8,FileName +{<%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%>} + +A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Alias +{Startup Actions} + +A1DD1DC2-85D7-9BC6-998AC3D4A3A9,Conditions +{0 conditions} + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,Conditions +{0 conditions} + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,ResultVirtualText +{AddBackupLocation} + +A5B32DA1-B2FE-C1FA-6057-FBC3059EF076,TclScript +{set AddBackupLocation no } + +A6E1B027-A1B4-5848-4F868D028D00,CheckCondition +{Before Action is Executed} + +A6E1B027-A1B4-5848-4F868D028D00,String +{<%ViewReadme%>} + +ADA6EB2F-8820-4366-BBEF-ED1335B7F828,Conditions +{1 condition} + +AE3BD5B4-35DE-4240-B79914D43E56,Active +{No} + +AE3BD5B4-35DE-4240-B79914D43E56,BackButton,subst +{1} + +AE3BD5B4-35DE-4240-B79914D43E56,CancelButton,subst +{1} + +AE3BD5B4-35DE-4240-B79914D43E56,Caption,subst +{1} + +AE3BD5B4-35DE-4240-B79914D43E56,Conditions +{0 conditions} + +AE3BD5B4-35DE-4240-B79914D43E56,Message,subst +{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} + +AIX-ppc,ProgramExecutable +{} + +AIX-ppc,ProgramFolderAllUsers +{No} + +AIX-ppc,ProgramFolderName +{<%AppName%>} + +AIX-ppc,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +AIX-ppc,ProgramName +{} + +AIX-ppc,ProgramReadme +{<%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} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Alias +{Stop Backup Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Comment +{Stop the Backup Windows Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,Conditions +{0 conditions} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,FileName +{<%ShortAppName%>-program-stopservice} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,ShortcutName +{Stop Service} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,TargetFileName +{<%InstallDir%>/tools/StopService.bat} + +B01CBBB2-6A78-CA53-9ED9-C3C4CFC9239E,WorkingDirectory +{<%InstallDir%>} + +B0AA6839-AAB6-A602-C0E4ECA2E4FF,CheckCondition +{Before Action is Executed} + +B0AA6839-AAB6-A602-C0E4ECA2E4FF,Filename +{<%ProgramExecutable%>} + +B39C0455-D1B6-7DDC-E2717F83463E,CheckCondition +{Before Action is Executed} + +B39C0455-D1B6-7DDC-E2717F83463E,Operator +{false} + +B39C0455-D1B6-7DDC-E2717F83463E,String +{<%InstallStopped%>} + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Conditions +{0 conditions} + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,ExecuteAction +{Before Next Pane is Displayed} + +B3B99E2D-C368-A921-B7BC-A71EBDE3AD4D,Password +{<%InstallPassword%>} + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,Conditions +{0 conditions} + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,ProgramCommandLine +{<%ServiceExeName%> -r -S <%ServiceName%>} + +B4D31D1E-ADB1-DE8F-18EB7294DDA8,WorkingDirectory +{<%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} + +B506E7DA-E7C4-4D42-8C03-FD27BA16D078,Conditions +{0 conditions} + +B506E7DA-E7C4-4D42-8C03-FD27BA16D078,DeclineRadiobutton,subst +{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} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Alias +{Restart Backup Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Comment +{Stop and restart the Backup Windows Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,Conditions +{0 conditions} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,FileName +{<%ShortAppName%>-program-restartservice} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,ShortcutName +{Restart Service} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,TargetFileName +{<%InstallDir%>\tools\RestartService.bat} + +B5DFEC63-92A9-4686-909E-0CE78A7069D6,WorkingDirectory +{<%InstallDir%>} + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Conditions +{0 conditions} + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,LabelSide +{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} + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Conditions +{2 conditions} + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,State +{disabled} + +B93D2216-1DDB-484C-A9AC-D6C18ED7DE23,Widget +{NextButton} + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,CheckCondition +{Before Next Action is Executed} + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,FailureMessage +{<%DirectoryPermissionText%>} + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Filename +{<%InstallDir%>} + +BC4EA5FD-50BD-4D6E-953F-5E3EDB957360,Permission +{can create} + +C0452595-F3EB-43AD-BCA2-661437584636,Alias +{Modify Backup Configuration} + +C0452595-F3EB-43AD-BCA2-661437584636,Comment +{Modify your Backup Configuration} + +C0452595-F3EB-43AD-BCA2-661437584636,Conditions +{0 conditions} + +C0452595-F3EB-43AD-BCA2-661437584636,FileName +{<%ShortAppName%>-program-editconfig} + +C0452595-F3EB-43AD-BCA2-661437584636,ShortcutName +{Edit Config File} + +C0452595-F3EB-43AD-BCA2-661437584636,TargetFileName +{<%InstallDir%>/tools/EditConfig.bat} + +C0452595-F3EB-43AD-BCA2-661437584636,WorkingDirectory +{<%InstallDir%>} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,Conditions +{0 conditions} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,FileName +{<%ShortAppName%>-program-QueryAll} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,ShortcutName +{Query All} + +C0AF7C05-A31A-8376-BCB9-BA8B3A666252,TargetFileName +{<%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} + +C105AAAE-7C16-2C9E-769FE4535B60,Conditions +{3 conditions} + +C105AAAE-7C16-2C9E-769FE4535B60,Message,subst +{1} + +C105AAAE-7C16-2C9E-769FE4535B60,TextFile +{<%ProgramReadme%>} + +C105AAAE-7C16-2C9E-769FE4535B60,Title,subst +{1} + +C33D74B2-26FA-16F5-433A10C6A747,Active +{No} + +C33D74B2-26FA-16F5-433A10C6A747,Conditions +{3 conditions} + +C33D74B2-26FA-16F5-433A10C6A747,ProgramCommandLine +{<%ProgramExecutable%>} + +C33D74B2-26FA-16F5-433A10C6A747,WaitForProgram +{No} + +C33D74B2-26FA-16F5-433A10C6A747,WorkingDirectory +{<%InstallDir%>} + +C7762473-273F-E3CA-17E3-65789B14CDB0,Conditions +{0 conditions} + +C7762473-273F-E3CA-17E3-65789B14CDB0,ExecuteAction +{Before Next Pane is Displayed} + +C7762473-273F-E3CA-17E3-65789B14CDB0,FileOpenAction +{Append to file} + +C7762473-273F-E3CA-17E3-65789B14CDB0,Files +{<%ConfigFileTemplate%>} + +C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite,subst +{1} + +CC4337CC-F3B5-757C-DFCF5D1D365A,CheckCondition +{Before Action is Executed} + +CC4337CC-F3B5-757C-DFCF5D1D365A,Operator +{false} + +CC4337CC-F3B5-757C-DFCF5D1D365A,String +{<%SilentMode%>} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Alias +{Start Backup Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Comment +{Start Backup Windows Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,Conditions +{0 conditions} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,FileName +{<%ShortAppName%>-program-startservice} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,ShortcutName +{Start Service} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,TargetFileName +{<%InstallDir%>\tools\StartService.bat} + +CDD84DE3-C970-458F-9162-1A3CE0AA716B,WorkingDirectory +{<%InstallDir%>} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Background +{white} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Conditions +{2 conditions} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text,subst +{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} + +D23DD94C-E517-7F34-FD59-802CB18AB887,Comment +{Need to do before starting anything else.} + +D23DD94C-E517-7F34-FD59-802CB18AB887,Conditions +{0 conditions} + +D23DD94C-E517-7F34-FD59-802CB18AB887,Files +{*/*.conf;*/*.txt;*/*.bat} + +D23DD94C-E517-7F34-FD59-802CB18AB887,LineFeed +{Windows} + +D3D73C76-D9D3-07DA-63D4163A44BE,Conditions +{0 conditions} + +D3D73C76-D9D3-07DA-63D4163A44BE,ExitType +{Finish} + +D4FC6EB5-DDEE-4E4A-B8E1-D4B588A7928B,Action +{Install Actions} + +D55BA4AF-E73B-60D1-E26F79175227,Action +{Uninstall Actions} + +D55BA4AF-E73B-60D1-E26F79175227,Conditions +{0 conditions} + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,Conditions +{0 conditions} + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,ResultVirtualText +{BackupLocationNumber} + +D7FBBEBB-2186-5674-BA87-BB7151859D4E,TclScript +{incr BackupLocationNumber} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Alias +{Reload Configuration File, after editing it.} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,Conditions +{0 conditions} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,FileName +{<%ShortAppName%>-program-reloadconfig} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,ShortcutName +{Reload configuration file} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,TargetFileName +{<%InstallDir%>/tools/ReloadConfig.bat} + +D8B8A9BF-5F2E-4236-A63E-5A8C5FFA8968,WorkingDirectory +{<%InstallDir%>} + +D8F0AA0F-AD79-C566-15CC508F503B,Action +{Install Actions} + +D8F0AA0F-AD79-C566-15CC508F503B,Conditions +{0 conditions} + +D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Conditions +{0 conditions} + +D9F88AC1-3D2D-F6DB-871E-3A0E016770B1,Destination +{<%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} + +DA33B826-E633-A845-4646-76DFA78B907B,Conditions +{0 conditions} + +DA33B826-E633-A845-4646-76DFA78B907B,Message,subst +{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} + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Conditions +{0 conditions} + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Text,subst +{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} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Alias +{Install Backup Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Comment +{Install the Backup Windows Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,Conditions +{0 conditions} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,FileName +{<%ShortAppName%>-program-installService} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,ShortcutName +{Install Service} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,TargetFileName +{<%InstallDir%>/tools/InstallService.bat} + +DE800F1C-CB1A-E1CE-AEB8-B0A6DB4818E7,WorkingDirectory +{<%InstallDir%>} + +DECC120D-6904-7F17-45A49184A5A3,Active +{No} + +DECC120D-6904-7F17-45A49184A5A3,Conditions +{2 conditions} + +DECC120D-6904-7F17-45A49184A5A3,ShortcutName +{<%AppName%>} + +DECC120D-6904-7F17-45A49184A5A3,TargetFileName +{<%ProgramExecutable%>} + +DECC120D-6904-7F17-45A49184A5A3,WorkingDirectory +{<%InstallDir%>} + +DFFF91A9-2CA5-6ABE-8474D814AF88,CheckCondition +{Before Action is Executed} + +DFFF91A9-2CA5-6ABE-8474D814AF88,Operator +{false} + +DFFF91A9-2CA5-6ABE-8474D814AF88,String +{<%SilentMode%>} + +E02368C5-95B5-03A7-3282740037B0,CheckCondition +{Before Action is Executed} + +E02368C5-95B5-03A7-3282740037B0,Operator +{false} + +E02368C5-95B5-03A7-3282740037B0,String +{<%InstallStopped%>} + +E161F216-E597-B340-C1A71C476E2C,CheckCondition +{Before Action is Executed} + +E161F216-E597-B340-C1A71C476E2C,Message,subst +{1} + +E161F216-E597-B340-C1A71C476E2C,Title,subst +{1} + +E23AC50D-7CFB-800E-A99C6F4068F8,Alias +{Cancel Actions} + +E23AC50D-7CFB-800E-A99C6F4068F8,Conditions +{0 conditions} + +E44CFF46-6302-C518-B9C30D2E43F7,CheckCondition +{Before Action is Executed} + +E44CFF46-6302-C518-B9C30D2E43F7,String +{<%CreateDesktopShortcut%>} + +E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,Conditions +{0 conditions} + +E4DEA723-FC78-45D7-BAB1-A3E4C4C96EA1,ProgramCommandLine +{net stop <%ServiceName%>} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Conditions +{0 conditions} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Destination +{<%InstallDir%>\templates\NotifySysAdmin.original.vbs} + +E56ADFF4-C15E-AEDB-A599-C468AF72C4BB,Source +{<%InstallDir%>\templates\NotifySysAdmin.template.vbs} + +EB2B31A1-C111-3582-0C8A5656692A,String +{<%ErrorsOccurred%>} + +EB532611-5F30-3C24-66EB-F3826D9054FD,CheckCondition +{Before Action is Executed} + +EB532611-5F30-3C24-66EB-F3826D9054FD,String +{<%AddBackupLocation%>} + +F4024A3E-9A6D-2726-5E0CFFA93054,Alias +{Uninstall Actions} + +F4024A3E-9A6D-2726-5E0CFFA93054,Conditions +{0 conditions} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,Active +{No} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,Conditions +{0 conditions} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgramCommandLine +{cmd /k tools/7za.exe encrypted_keys.exe -p<%InstallPassword%>} + +F5F21749-8B3A-49C6-9138-9C4D6D703D26,ProgressiveOutputWidget +{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} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Conditions +{0 conditions} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Message,subst +{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} + +F9E38720-6ABA-8B99-2471-496902E4CBC2,Conditions +{0 conditions} + +F9E38720-6ABA-8B99-2471-496902E4CBC2,ResultVirtualText +{BackupLocationPath} + +F9E38720-6ABA-8B99-2471-496902E4CBC2,TclScript +{set BackupLocationPath "" } + +FB697A88-2842-468E-9776-85E84B009340,Active +{No} + +FB697A88-2842-468E-9776-85E84B009340,Conditions +{0 conditions} + +FB697A88-2842-468E-9776-85E84B009340,IgnoreErrors +{Yes} + +FB697A88-2842-468E-9776-85E84B009340,ProgramCommandLine +{"<%InstallDir%>\<%ServiceExeName%> -r -S <%ServiceName%>} + +FB697A88-2842-468E-9776-85E84B009340,WorkingDirectory +{<%Temp%>} + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Active +{No} + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,Conditions +{0 conditions} + +FDF68FD6-BEA8-4A74-867D-5139F4D9E793,WaitTime +{2000} + +FEFD090D-C133-BC95-B3564F693CD3,Alias +{Finish Actions} + +FEFD090D-C133-BC95-B3564F693CD3,Conditions +{0 conditions} + +FreeBSD-4-x86,Active +{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} + +FreeBSD-4-x86,ProgramExecutable +{} + +FreeBSD-4-x86,ProgramFolderAllUsers +{No} + +FreeBSD-4-x86,ProgramFolderName +{<%AppName%>} + +FreeBSD-4-x86,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +FreeBSD-4-x86,ProgramName +{} + +FreeBSD-4-x86,ProgramReadme +{<%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} + +HPUX-hppa,ProgramExecutable +{} + +HPUX-hppa,ProgramFolderAllUsers +{No} + +HPUX-hppa,ProgramFolderName +{<%AppName%>} + +HPUX-hppa,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +HPUX-hppa,ProgramName +{} + +HPUX-hppa,ProgramReadme +{<%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} + +Linux-x86,ProgramName +{} + +Linux-x86,ProgramReadme +{<%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} + +Solaris-sparc,ProgramExecutable +{} + +Solaris-sparc,ProgramFolderAllUsers +{No} + +Solaris-sparc,ProgramFolderName +{<%AppName%>} + +Solaris-sparc,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +Solaris-sparc,ProgramName +{} + +Solaris-sparc,ProgramReadme +{<%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} + +TarArchive,ProgramExecutable +{} + +TarArchive,ProgramFolderAllUsers +{No} + +TarArchive,ProgramFolderName +{<%AppName%>} + +TarArchive,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +TarArchive,ProgramName +{} + +TarArchive,ProgramReadme +{<%InstallDir%>/README.txt} + +TarArchive,PromptForRoot +{Yes} + +TarArchive,RequireRoot +{No} + +TarArchive,RootInstallDir +{/usr/local/<%ShortAppName%>} + +TarArchive,VirtualTextMap +{<%InstallDir%> <%ShortAppName%>} + +Windows,Active +{Yes} + +Windows,BuildSeparateArchives +{No} + +Windows,BuildType +{} + +Windows,Executable +{installer.exe} + +Windows,FileDescription +{<%AppName%> <%Version%> Setup} + +Windows,IncludeTWAPI +{No} + +Windows,InstallDir +{C:\Program Files\<%BrandName%>} + +Windows,InstallMode +{Standard} + +Windows,InstallType +{Typical} + +Windows,LastRequireAdministrator +{Yes} + +Windows,ProgramExecutable +{} + +Windows,ProgramFolderAllUsers +{No} + +Windows,ProgramFolderName +{<%BrandName%>} + +Windows,ProgramLicense +{<%InstallDir%>\LICENSE.txt} + +Windows,ProgramName +{} + +Windows,ProgramReadme +{} + +Windows,RequireAdministrator +{Yes} + +Windows,UseUncompressedBinaries +{No} + +Windows,WindowsIcon +{} + +ZipArchive,Active +{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} + +ZipArchive,ProgramExecutable +{} + +ZipArchive,ProgramFolderAllUsers +{No} + +ZipArchive,ProgramFolderName +{<%AppName%>} + +ZipArchive,ProgramLicense +{<%InstallDir%>/LICENSE.txt} + +ZipArchive,ProgramName +{} + +ZipArchive,ProgramReadme +{<%InstallDir%>/README.txt} + +ZipArchive,PromptForRoot +{Yes} + +ZipArchive,RequireRoot +{No} + +ZipArchive,RootInstallDir +{/usr/local/<%ShortAppName%>} + +ZipArchive,VirtualTextMap +{<%InstallDir%> <%ShortAppName%>} + +} + +::msgcat::mcmset de { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%InstallingApplicationText%>} + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +{<%UninstallingApplicationText%>} + +} +::msgcat::mcmset en { +16D53E40-546B-54C3-088B1B5E3BBB,Text +{<%CreateDesktopShortcutText%>} + +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +2E2963BD-DDBD-738D-A910-B7F3F04946F9,Text +{No:<%BackupLocationNumber%> } + +2EC82FBD-8294-A3E4-7F39-1CBA0582FA64,TextToWrite +BackupLocations\n\{\n + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Message +{<%InstallStartupText%>} + +32F5B0AF-EB83-7A03-D8FAE1ECE473,Title +{<%InstallApplicationText%>} + +36FF8915-8148-0F1F-27D7239CBFA1,Text +{<%ViewReadmeText%>} + +3B6E2E7C-1A26-27F1-D578E383B128,Text +{<%ViewReadmeText%>} + +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.} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Caption +{} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Message +{} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Subtitle +{Add a directory to backup, nickname it, and add some exclusions.} + +3FD9BFF3-2F6E-E4FC-2FAE-98F2017916A7,Title +{Backup Locations} + +3FDB57ED-598D-8A4E-CEF7-D90833305558,Text +{Backup Directory} + +4A9C852B-647E-EED5-5482FFBCC2AF,Description +{<%ProgramFilesDescription%>} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Message +{<%UninstallStartupText%>} + +4ACB0B47-42B3-2B3A-BFE9AA4EC707,Title +{<%UninstallApplicationText%>} + +58E1119F-639E-17C9-5D3898F385AA,Caption +{Setup will install <%ShortAppName%> in the following folder. + +To install to this folder, click Next. To install to a different folder, click Browse and select another folder.} + +58E1119F-639E-17C9-5D3898F385AA,Subtitle +{Where should <%ShortAppName%> be installed?} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Caption +{Backup Exclusions} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Message +{} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Subtitle +{Enter your backup exclusions for this Backup Location <%BackupLocationName_ShortName%> here.} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Text +{} + +59395A3B-6116-F93A-84E1-5E079C2CD44B,Title +{Backup Exclusions} + +640DA2B2-6CF3-0873-D7AE-ABCDDE39EFCF,Text +{Put your custom text here. +<%AddBackupLocation%> +<%BackupLocationNumber%> +<%BackupLocationName%>} + +6C323815-B9AB-FA94-4F5D152EBC51,Caption +{Installation Wizard Complete} + +6C323815-B9AB-FA94-4F5D152EBC51,Message +{The Installation Wizard has successfully installed the <%BrandName%> Backup Service. Click Finish to exit the wizard.} + +6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Caption +{Installing encryption keys...} + +6CFBBE13-6B70-4B7C-B5EF-0677752D95A8,Message +{Click next to install encrypted keys and configuration file (bbackupd.conf)... + +You will be presented with the current configuration file so that you may make any last minute changes...} + +6FEE2889-0338-1D49-60BF-1471F465AB26,TextToWrite +\}\n + +8202CECC-54A0-9B6C-D24D111BA52E,Description +{<%TypicalInstallDescription%>} + +8419AAAD-5860-F73E-8D11-4D1BDA4D7D37,Text +{Add another backup location?} + +855DE408-060E-3D35-08B5-1D9AB05C2865,Text +{Exclusions} + +8E095096-F018-A880-429D-A2177A9B70EA,Text +{AddAnother: <%AddBackupLocation%>} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,Caption +{Please enter your phone number and email address.} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,CompanyLabel +{Email:} + +9013E862-8E81-5290-64F9-D8BCD13EC7E5,UserNameLabel +{Phone:} + +937C3FDD-FB28-98BD-3DAB276E59ED,Text +{<%CreateQuickLaunchShortcutText%>} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Caption +{Click Next to continue...} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Message +{Building configuration file...} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Subtitle +{} + +9A23D3ED-4D9D-9C57-C2A7-71DE0FFF0266,Title +{Continue} + +9BAB328D-414B-D351-CA8D-824DF94B9DCA,Text +{Add another backup location after this one?} + +A18C2977-1409-C1FB-892415711F72,Text +{<%LaunchApplicationText%>} + +AAF2142A-9FC9-4664-DFF2-13B9EB7BA0E1,CompanyLabel +{Company:} + +AE3BD5B4-35DE-4240-B79914D43E56,Caption +{Welcome to the Installation Wizard for <%BrandName%> Backup Service!} + +AE3BD5B4-35DE-4240-B79914D43E56,Message +{Thank you for installing the <%BrandName%>(SM) Backup Service. + +If you need any assistance, please contact us at http://<%BrandName%>.com or support@<%BrandName%>.com. + +This will install <%BrandName%> version <%Version%> on your computer. + +It is recommended that you close all other applications before continuing. + +Click Next to continue or Cancel to exit Setup. +} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%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]} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Caption +{<%BrandName%> will backup the following folder. + +To backup this folder, click Next. To backup a different folder, click Browse and select another folder.} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,DestinationLabel +{Backup This Folder} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Message +{} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Subtitle +{What directory should <%BrandName%> backup?} + +B57F8C91-2439-CFD3-7EB5-57D4EA48D3C6,Title +{Choose Backup Location} + +B927A5AF-4DFE-82A3-DCA8-35FA4D91EC5A,Text +{Short Name} + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Caption +{Please enter the agreed-upon password for your encrypted key file...} + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,CompanyLabel +{Password:} + +B9B85EF1-1D76-4BF5-ABB9-092A8DB35851,Subtitle +{Please enter your encrypted key file password.} + +C105AAAE-7C16-2C9E-769FE4535B60,Caption +{<%ApplicationReadmeText%>} + +C105AAAE-7C16-2C9E-769FE4535B60,Message +{} + +C105AAAE-7C16-2C9E-769FE4535B60,Title +{<%ApplicationReadmeText%>} + +C7762473-273F-E3CA-17E3-65789B14CDB0,TextToWrite +{<%BackupLocationShortName%> +{ +Path = <%BackupLocationPath%> +<%BackupLocationExclusions%> +} +} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,BrowseText +{To continue, click Next. If you would like to select a folder to backup, click Browse.} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Caption +{} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,DestinationLabel +{Backup This Folder} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Subtitle +{What directories should <%BrandName%> backup?} + +CB058DBA-C3B7-2F48-D985-BE2F7107A76D,Title +{Choose Backup Location} + +CFFA27AF-A641-E41C-B4A0E3BB3CBB,Text +{<%LaunchApplicationText%>} + +D4625CA6-9864-D8EF-F252D7B7DC87,Text +{<%CreateDesktopShortcutText%>} + +D47BE952-79F2-844E-D2E5-8F22044E7A9D,Text +{Account Number:} + +DA33B826-E633-A845-4646-76DFA78B907B,Caption +{Click Next to continue...} + +DA33B826-E633-A845-4646-76DFA78B907B,Message +{Completing configuration file...} + +DA33B826-E633-A845-4646-76DFA78B907B,Subtitle +{} + +DA33B826-E633-A845-4646-76DFA78B907B,Title +{Continue} + +DDBBD8A9-13D7-9509-9202-419E989F60A9,Text +{Add another Backup Location?} + +E0CADC4E-08A6-E429-3B49-BB8CFB7B097F,Text +{Simple name for this Backup Location (short, no spaces or special characters)} + +E161F216-E597-B340-C1A71C476E2C,Message +{<%UninstallLeftoverText%>} + +E161F216-E597-B340-C1A71C476E2C,Title +{Uninstall <%BrandName%>} + +EA2C57E8-CCFB-CF3D-92CA-83369EFF1B08,Text +{Add (another) Backup Location after this one?} + +EDE364D6-22C7-5108-D398-26FC24E0A55A,Text +{Enter your Backup Exclusions for this Backup Location...} + +F59AF47D-4136-64F8-82C7-4506BD4327FD,Text +{Add another Backup Location after this one?} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,Caption +{Please enter your Account Number.} + +F8FD4BD6-F1DF-3F8D-B857-98310E4B1143,UserNameLabel +{Account Number (like 10005004):} + +F98784B1-1965-0F42-6BB0542AE1A9,Caption +{Installing and starting the TebucoSafe Backup Service...} + +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 +{<%CreateQuickLaunchShortcutText%>} + +FF4F6EEA-F4CC-428E-AF33-EB0E88E2147E,Text +{ +Copyright (c) 2003 - 2006 + Ben Summers and contributors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All use of this software and associated advertising materials must + display the following acknowledgment: + This product includes software developed by Ben Summers. +4. The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +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. + + + } + +} +::msgcat::mcmset es { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%InstallingApplicationText%>} + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +{<%UninstallingApplicationText%>} + +} +::msgcat::mcmset fr { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%InstallingApplicationText%>} + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +{<%UninstallingApplicationText%>} + +} +::msgcat::mcmset pl { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%InstallingApplicationText%>} + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +{<%UninstallingApplicationText%>} + +} +::msgcat::mcmset pt_br { +20CBDBEA-2217-457B-8D98-D692C4F591E9,Message +{<%UninstallCompleteText%>} + +2BF07B5A-9B06-4C1E-810D-5B5E9303D2C6,Message +{<%InstallationCompleteText%>} + +B002A311-F8E7-41DE-B039-521391924E5B,Message +{<%InstallingApplicationText%>} + +B4ED4636-22D8-41DC-9E3D-BD1E1CAD2174,Message +{<%UninstallingApplicationText%>} + +} + diff --git a/contrib/windows/installer/tools/InstallService.bat b/contrib/windows/installer/tools/InstallService.bat new file mode 100755 index 00000000..80a342eb --- /dev/null +++ b/contrib/windows/installer/tools/InstallService.bat @@ -0,0 +1,3 @@ +service.exe -i -S GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/KillBackupProcess.bat b/contrib/windows/installer/tools/KillBackupProcess.bat new file mode 100755 index 00000000..416d4a79 --- /dev/null +++ b/contrib/windows/installer/tools/KillBackupProcess.bat @@ -0,0 +1,3 @@ +control.exe terminate +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/QueryOutputAll.bat b/contrib/windows/installer/tools/QueryOutputAll.bat new file mode 100755 index 00000000..2ab30a72 --- /dev/null +++ b/contrib/windows/installer/tools/QueryOutputAll.bat @@ -0,0 +1,5 @@ +@ECHO OFF +: o=old, d=deleted, s=size info, t=timestamp, r=recursive +set Queryopts=-odstr +::set Queryopts=-str +query.exe "list %Queryopts%" quit > QueryOutputAllResults.txt diff --git a/contrib/windows/installer/tools/QueryOutputCurrent.bat b/contrib/windows/installer/tools/QueryOutputCurrent.bat new file mode 100755 index 00000000..d59ddbf1 --- /dev/null +++ b/contrib/windows/installer/tools/QueryOutputCurrent.bat @@ -0,0 +1,5 @@ +@ECHO OFF +: o=old, d=deleted, s=size info, t=timestamp, r=recursive +::set Queryopts=-odstr +set Queryopts=-str +query.exe "list %Queryopts%" quit > QueryOutputCurrentResults.txt diff --git a/contrib/windows/installer/tools/ReloadConfig.bat b/contrib/windows/installer/tools/ReloadConfig.bat new file mode 100755 index 00000000..5fd44e83 --- /dev/null +++ b/contrib/windows/installer/tools/ReloadConfig.bat @@ -0,0 +1,3 @@ +control.exe reload +echo off +ping 192.168.254.254 -n 8 -w 1000 > nul diff --git a/contrib/windows/installer/tools/RemoveService.bat b/contrib/windows/installer/tools/RemoveService.bat new file mode 100755 index 00000000..881ec5b1 --- /dev/null +++ b/contrib/windows/installer/tools/RemoveService.bat @@ -0,0 +1,3 @@ +@@SERVICEEXENAME@ -r -S GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/RestartService.bat b/contrib/windows/installer/tools/RestartService.bat new file mode 100755 index 00000000..77092a69 --- /dev/null +++ b/contrib/windows/installer/tools/RestartService.bat @@ -0,0 +1,5 @@ +net stop GigaLock +ping 192.168.254.254 -n 2 -w 1000 > nul +net start GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/ShowUsage.bat b/contrib/windows/installer/tools/ShowUsage.bat new file mode 100755 index 00000000..e6f69e9f --- /dev/null +++ b/contrib/windows/installer/tools/ShowUsage.bat @@ -0,0 +1,3 @@ +query.exe usage quit +ping 192.168.254.254 -n 10 -w 1000 > nul + diff --git a/contrib/windows/installer/tools/StartService.bat b/contrib/windows/installer/tools/StartService.bat new file mode 100755 index 00000000..1771238f --- /dev/null +++ b/contrib/windows/installer/tools/StartService.bat @@ -0,0 +1,3 @@ +net start GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/StopService.bat b/contrib/windows/installer/tools/StopService.bat new file mode 100755 index 00000000..8e70d68d --- /dev/null +++ b/contrib/windows/installer/tools/StopService.bat @@ -0,0 +1,3 @@ +net stop GigaLock +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/contrib/windows/installer/tools/Sync.bat b/contrib/windows/installer/tools/Sync.bat new file mode 100755 index 00000000..30a04bec --- /dev/null +++ b/contrib/windows/installer/tools/Sync.bat @@ -0,0 +1,3 @@ +control.exe sync +echo off +ping 192.168.254.254 -n 5 -w 1000 > nul diff --git a/distribution/COMMON-MANIFEST.txt b/distribution/COMMON-MANIFEST.txt new file mode 100644 index 00000000..6753c886 --- /dev/null +++ b/distribution/COMMON-MANIFEST.txt @@ -0,0 +1,47 @@ +LICENSE-DUAL.txt + +LICENSE DUAL + +RUN ./bootstrap +RUN cd docs; make + +lib/common +lib/crypto +lib/server +lib/compress +lib/win32 +test/common +test/common/testfiles +test/basicserver +test/basicserver/testfiles +test/crypto +test/compress +test/win32 +docs/api-notes +docs/api-notes/common +docs/api-notes/common/lib_common +docs/api-notes/common/lib_common.txt +docs/api-notes/common/lib_compress +docs/api-notes/common/lib_crypto +docs/api-notes/common/lib_server +MKDIR infrastructure +infrastructure/buildenv-testmain-template.cpp +infrastructure/makebuildenv.pl.in +infrastructure/makedistribution.pl.in +infrastructure/makeparcels.pl.in +infrastructure/parcelpath.pl +infrastructure/printversion.pl +infrastructure/BoxPlatform.pm.in +infrastructure/mingw +infrastructure/msvc +bootstrap +configure.ac +configure +parcels.txt +runtest.pl.in +COPYING.txt + +LICENSE none +config.sub +config.guess +infrastructure/m4 diff --git a/distribution/boxbackup/CONTACT.txt b/distribution/boxbackup/CONTACT.txt new file mode 100644 index 00000000..9f12e435 --- /dev/null +++ b/distribution/boxbackup/CONTACT.txt @@ -0,0 +1,6 @@ + +http://www.boxbackup.org/ + +Ben Summers & contributors +boxbackup@boxbackup.org + diff --git a/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt new file mode 100644 index 00000000..31e16d34 --- /dev/null +++ b/distribution/boxbackup/DISTRIBUTION-MANIFEST.txt @@ -0,0 +1,92 @@ +LICENSE-GPL.txt +qdbm + +LICENSE DUAL +lib/intercept +lib/raidfile +lib/httpserver +test/raidfile +test/raidfile/testfiles +test/httpserver + +LICENSE GPL +lib/backupclient +lib/backupstore +bin/bbstored +bin/bbstoreaccounts +bin/bbackupd +bin/bbackupd/win32 +bin/bbackupquery +bin/bbackupctl +bin/bbackupobjdump +bin/s3simulator +test/backupstore +test/backupstore/testfiles +test/backupstorefix +test/backupstorefix/testfiles +test/backupstorepatch +test/bbackupd +test/bbackupd/testfiles +test/backupdiff +docs/Makefile +docs/tools + +LICENSE DUAL +docs/api-notes +docs/api-notes/raidfile +docs/api-notes/raidfile/lib_raidfile.txt + +LICENSE GPL +docs/images +docs/htmlguide +docs/htmlguide/adminguide +docs/htmlguide/images +docs/htmlguide/instguide +docs/htmlguide/man-html +docs/man +MKDIR docs/docbook +docs/docbook/ExceptionCodes.xml +docs/docbook/adminguide.xml +docs/docbook/bb-book.xsl +docs/docbook/bb-man.xsl +docs/docbook/bb-nochunk-book.xsl +docs/docbook/bbackupctl.xml +docs/docbook/bbackupd-config.xml +docs/docbook/bbackupd.conf.xml +docs/docbook/bbackupd.xml +docs/docbook/bbackupquery.xml +docs/docbook/bbstoreaccounts.xml +docs/docbook/bbstored-certs.xml +docs/docbook/bbstored-config.xml +docs/docbook/bbstored.conf.xml +docs/docbook/bbstored.xml +docs/docbook/instguide.xml +docs/docbook/raidfile-config.xml +docs/docbook/raidfile.conf.xml +docs/docbook/html +docs/docbook/html/images +docs/xsl-generic +docs/xsl-generic/manpages +docs/xsl-generic/lib +docs/xsl-generic/common +docs/xsl-generic/html +docs/xsl-generic/highlighting +BUGS.txt +contrib + +contrib/bbadmin +contrib/bbreporter +contrib/debian +contrib/mac_osx +contrib/redhat +contrib/rpm +REPLACE-VERSION-IN contrib/rpm/boxbackup.spec +contrib/solaris +contrib/suse +contrib/windows +contrib/windows/installer +contrib/windows/installer/tools + +infrastructure/msvc +infrastructure/msvc/2003 +infrastructure/msvc/2005 diff --git a/distribution/boxbackup/DOCUMENTATION.txt b/distribution/boxbackup/DOCUMENTATION.txt new file mode 100644 index 00000000..f45f61d5 --- /dev/null +++ b/distribution/boxbackup/DOCUMENTATION.txt @@ -0,0 +1,6 @@ + +For compilation and installation instructions, see the web site at + + http://www.boxbackup.org/ + + diff --git a/distribution/boxbackup/LINUX.txt b/distribution/boxbackup/LINUX.txt new file mode 100644 index 00000000..d7481811 --- /dev/null +++ b/distribution/boxbackup/LINUX.txt @@ -0,0 +1,29 @@ + +For instructions on building an RPM of Box Backup, see the contrib/rpm +directory. This is primarily for RedHat style systems, but notes are provided +on what needs to be modified for SUSE. + + +Requirements: + + OpenSSL 0.9.7 + +Require zlib and openssl headers for compilation -- may not be included when +installing the packages. (libssl-dev + libz-dev packages under debian) + +Bekerley DB v1 or v4 support is required. The configure script should find an +appropriate db package -- and if not, use an in-memory version of the code. +However, the in-memory version is not desirable as it will lose information +over restarts of the daemon. + +Ideally, use libeditline as a readline replacement. If not available then use +GNU readline (libreadline4-dev, probably) and pass --enable-gnu-readline to +./configure. + + + +(OpenSSL 0.9.7 is required as it implements new interfaces which make encryption +using the same key faster by avoiding key setup each time around. Configure it with +./config shared , then copy the libs and headers into the required places.) + + diff --git a/distribution/boxbackup/NETBSD.txt b/distribution/boxbackup/NETBSD.txt new file mode 100644 index 00000000..03000791 --- /dev/null +++ b/distribution/boxbackup/NETBSD.txt @@ -0,0 +1,8 @@ + +Install perl + +Install OpenSSL 0.9.7 or later. + +Not a completely working port -- symlinks don't get backed up or restored properly. +(run test/bbackupd) + diff --git a/distribution/boxbackup/THANKS.txt b/distribution/boxbackup/THANKS.txt new file mode 100644 index 00000000..09ad60e2 --- /dev/null +++ b/distribution/boxbackup/THANKS.txt @@ -0,0 +1,69 @@ + +The following developers contributed code to version 0.10: + +Nick Knight + - ported Box Backup to Windows (properly, not using Cygwin) + +Gary Niemcewicz + - added client/server (SSL) keepalives to keep the connection to the + server alive during a long diff, and saving the daemon's state across restarts + +Martin Ebourne + - ported to Solaris; wrote extended attribute support (xattr); + converted to use autoconf for automatic compiler configuration. + +Chris Wilson + - updated Nick's and Gary's work to fit in with the new trunk, + fixed some issues pointed out by Ben and Martin, made it compile + on Windows with the free MinGW compiler. + +Jonathan Morton + - vastly improved the performance and efficiency of the file-diffing code, and + obtained a free G5 PowerMac from IBM as his reward. + + +---- + +Many individuals have helped with the development of Box Backup by testing, reporting experiences, and making suggestions. In particular, thanks are due to + +Charles Lecklider + - Helped with the finer details of Win32 programming + +Pascal Lalonde + - Comprehensive and accurate bug reports, and constructive feedback + +Paul Arch + - Cygwin client port + +Ben Lovett + - Help with odd architectures, suggesting small changes + +Martin Ebourne + - RPM specification for RedHat based Linux distributions + - Patch to fix problems on 64 bit architectures + - Patch to fix compilation after RedHat Fedora's latest changes + +Per Thomsen + - Cygwin Windows service install scripts and build notes + - Answering queries on the boxbackup mailing list + +Tim Fletcher +David Harris +Richard Eigenmann + - Testing many attempts at clean compiles on various Linux distributions + +Eduardo Alvarenga + - Valuable feedback and persuasion to include new features + +Joe Gillespie + - Web site design + +Jrme Schell + - Fixes to build+config problems on Linux + +John Pybus + - Ideas and feature requests + - Useful little patches to code + +Stefan Norlin + - Help with testing and fixes on lots of different Solaris platforms diff --git a/distribution/boxbackup/VERSION.txt b/distribution/boxbackup/VERSION.txt new file mode 100644 index 00000000..89770ba5 --- /dev/null +++ b/distribution/boxbackup/VERSION.txt @@ -0,0 +1,2 @@ +0.13 +boxbackup diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..c4a63671 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,183 @@ +# Process DocBook to HTML + +# This makefile is a bit obfuscated so that it works correctly on both +# BSD and GNU make. Some parts apply to one version of make and not the +# other; these are marked by comments. + +# The "all" target shouldn't be up here, but the trickery below defines +# what looks like a rule to GNU make, and so we need to define the actual +# default target before it. + +all: docs + +DBPROC_COMMAND = xsltproc +MKDIR_COMMAND = mkdir +CP_COMMAND = cp +PERL_COMMAND = perl +RM_COMMAND = rm -f +TAR_COMMAND = tar +GZIP_COMMAND = gzip -f +GENERATE_SCRIPT = tools/generate_except_xml.pl + +DBPROC = $(DBPROC_COMMAND) +MKDIR = $(MKDIR_COMMAND) +CP = $(CP_COMMAND) +GENERATE = $(PERL_COMMAND) $(GENERATE_SCRIPT) +RM_QUIET = $(RM_COMMAND) +TAR = $(TAR_COMMAND) +GZIP = $(GZIP_COMMAND) +PROGRESS = @ true + +# use a GNU make "define" command, that looks like a harmless dummy rule +# to BSD make, to hide parts of the Makefile from GNU make. +define IGNORED_BY_GNU_MAKE: +.if 0 +endef + + # seen by GNU make, not by BSD make + ifeq ($(V),) + DBPROC = @ echo " [XLSTPROC]" $^ && $(DBPROC_COMMAND) 2>/dev/null + GENERATE = @ echo " [GENERATE]" $@ && $(PERL_COMMAND) $(GENERATE_SCRIPT) + TAR = @ echo " [TAR] " $@ && $(TAR_COMMAND) + GZIP = @ echo " [GZIP] " $< && $(GZIP_COMMAND) + RM_QUIET = @ $(RM_COMMAND) + PROGRESS = @ echo + endif + +define IGNORED_BY_GNU_MAKE: +.endif + +.ifndef V + # seen by BSD make, not by GNU make + DBPROC = @ echo " [XSLTPROC]" $(.ALLSRC) && $(DBPROC_COMMAND) 2>/dev/null + GENERATE = @ echo " [GENERATE]" $(.TARGET) && $(PERL_COMMAND) $(GENERATE_SCRIPT) + TAR = @ echo " [TAR] " $(.TARGET) && $(TAR_COMMAND) + GZIP = @ echo " [GZIP] " $(.TARGET:.gz=) && $(GZIP_COMMAND) + RM_QUIET = @ $(RM_COMMAND) + PROGRESS = @ echo +.endif + +# neither .endif nor endef can be followed by a colon; each creates +# warnings or errors in one or other version of make. we need some +# magic to make them both work. Luckily, .endfor ignores the colon. + +.for DUMMY in $(NO_SUCH_VARIABLE) +endef +.endfor : + +PROGRESS_RM = $(PROGRESS) " [RM] " + +DOCBOOK_DIR = docbook +HTML_DIR = htmlguide +MAN_DIR = man + +BOOKXSL = $(DOCBOOK_DIR)/bb-book.xsl +NOCHUNKBOOKXSL = $(DOCBOOK_DIR)/bb-nochunk-book.xsl +MANXSL = $(DOCBOOK_DIR)/bb-man.xsl + +VPATH = $(DOCBOOK_DIR) +.SUFFIXES: .html .xml .gz .1 .5 .8 + +docs: instguide adminguide manpages + @mkdir -p $(HTML_DIR)/images + @cp $(DOCBOOK_DIR)/html/images/*.png $(HTML_DIR)/images/. + @cp $(DOCBOOK_DIR)/html/*.css $(HTML_DIR)/. + @cp $(DOCBOOK_DIR)/html/*.ico $(HTML_DIR)/. + +adminguide: $(DOCBOOK_DIR)/ExceptionCodes.xml $(HTML_DIR)/adminguide/index.html + +# $^ gives all sources on GNU make, and nothing on BSD make +# $> gives all sources on BSD make, and nothing on GNU make +$(HTML_DIR)/adminguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/adminguide.xml + $(DBPROC) -o $(HTML_DIR)/adminguide/ $^ $> + +instguide: $(HTML_DIR)/instguide/index.html + +$(HTML_DIR)/instguide/index.html: $(BOOKXSL) $(DOCBOOK_DIR)/instguide.xml + $(DBPROC) -o $(HTML_DIR)/instguide/ $^ $> + +# On BSD make, $> contains all sources and $^ is empty +# On GNU make, $^ contains all sources and $> is empty +$(DOCBOOK_DIR)/ExceptionCodes.xml: ../ExceptionCodes.txt + $(GENERATE) $> $^ $@ + +manpages: man-dirs man-nroff man-html + +man-dirs: man/.there $(HTML_DIR)/man-html/.there + +$(HTML_DIR)/man-html/.there: + mkdir -p $(HTML_DIR)/man-html + touch $(HTML_DIR)/man-html/.there + +man/.there: + mkdir -p man + touch man/.there + +NROFF_PAGES = bbackupd.8 bbackupd-config.8 bbackupctl.8 bbackupquery.8 \ + bbstored.8 bbstored-config.8 bbstoreaccounts.8 bbstored-certs.8 \ + raidfile-config.8 \ + bbackupd.conf.5 bbstored.conf.5 raidfile.conf.5 + +NROFF_FILES = $(NROFF_PAGES:%=$(MAN_DIR)/%.gz) + +man-nroff: $(NROFF_FILES) + +HTML_FILES_1 = $(NROFF_PAGES:%.5=%.html) +HTML_FILES_2 = $(HTML_FILES_1:%.8=%.html) +HTML_FILES = $(HTML_FILES_2:%=$(HTML_DIR)/man-html/%) + +man-html: $(HTML_FILES) + +# $^ gives all sources on GNU make, and nothing on BSD make + +# GNU make +$(HTML_DIR)/man-html/%.html: $(NOCHUNKBOOKXSL) $(DOCBOOK_DIR)/%.xml + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.8: $(MANXSL) $(DOCBOOK_DIR)/%.xml + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.8.gz: $(MAN_DIR)/%.8 + $(GZIP) $< + +# GNU make +$(MAN_DIR)/%.5: $(MANXSL) $(DOCBOOK_DIR)/%.xml $(MANXSL) + $(DBPROC) -o $@ $^ + +# GNU make +$(MAN_DIR)/%.5.gz: $(MAN_DIR)/%.5 + $(GZIP) $< + +# BSD make: the final colon (:) is required to make the .for and .endfor +# lines valid in GNU make. It creates (different) dummy rules in GNU and +# BSD make. Both dummy rules are harmless. + +.for MAN_PAGE in $(NROFF_PAGES) : +$(MAN_DIR)/$(MAN_PAGE).gz: $(MANXSL) $(DOCBOOK_DIR)/$(MAN_PAGE:R).xml + $(DBPROC) -o $(.TARGET:.gz=) $(.ALLSRC) + $(GZIP) $(.TARGET:.gz=) + +$(HTML_DIR)/man-html/$(MAN_PAGE:R).html: $(NOCHUNKBOOKXSL) \ +$(DOCBOOK_DIR)/$(MAN_PAGE:R).xml + $(DBPROC) -o $(.TARGET) $(.ALLSRC) +.endfor : + +dockit: clean docs documentation-kit-0.10.tar.gz + +documentation-kit-0.10.tar.gz: + $(TAR) zcf documentation-kit-0.10.tar.gz $(HTML_DIR)/ + +clean: + $(PROGRESS_RM) "$(HTML_DIR)/man-html/*.html" + $(RM_QUIET) $(HTML_FILES) + + $(PROGRESS_RM) "$(MAN_DIR)/*.[58].gz" + $(RM_QUIET) $(NROFF_FILES) + + $(PROGRESS_RM) "$(DOCBOOK_DIR)/ExceptionCodes.xml" + $(RM_QUIET) $(DOCBOOK_DIR)/ExceptionCodes.xml + + $(PROGRESS_RM) "documentation-kit-0.10.tar.gz" + $(RM_QUIET) documentation-kit-0.10.tar.gz diff --git a/docs/api-notes/INDEX.txt b/docs/api-notes/INDEX.txt new file mode 100644 index 00000000..f333ac3b --- /dev/null +++ b/docs/api-notes/INDEX.txt @@ -0,0 +1,61 @@ +TITLE Programmers Notes for Box Backup + +This directory contains the programmers notes for the Box Backup system. They will be of interest only if you want to review or modify the code. + +These notes are intended to be run through a script to produce HTML at some later stage, hence the marks such as 'TITLE'. + + +SUBTITLE Organisation + +The project is split up into several modules. The modules within 'lib' are building blocks for creation of the actual executable programs within 'bin'. 'test' contains unit tests for lib and bin modules. + +The file modules.txt lists the modules which are to be built, and their dependencies. It also allows for platform differences. + + +SUBTITLE Documentation Organisation + +In this directory, the files correspond to modules or areas of interest. Sub directories of the same name contain files documenting specific classes within that module. + + +SUBTITLE Suggested reading order + +* common/lib_common.txt +* common/lib_server.txt +* bin_bbackupd.txt +* backup_encryption.txt +* bin_bbstored.txt +* raidfile/lib_raidfile.txt + +and refer to other sections as required. + + +SUBTITLE Building + +The makefiles are generated by makebuildenv.pl. (The top level makefile is generated by makeparcels.pl, but this is for the end user to use, not a programmer.) + +To build a module, cd to it and type make. If the -DRELEASE option is specified (RELEASE=1 with GNU make) the release version will be built. The object files and exes are placed in a directory structure under 'release' or 'debug'. + +It is intended that a test will be written for everything, so in general make commands will be issued only within the test/* modules. Once it has been built, cd to debug/test/ and run the test with ./t . + + +SUBTITLE Programming style + +The code is written to be easy to write. Ease of programming is the primary concern, as this should lead to fewer bugs. Efficiency improvements can be made later when the system as a whole works. + +Much use is made of the STL. + +There is no common base class. + +All errors are reported using exceptions. + +Some of the boring code is generated by perl scripts from description files. + +There are a lot of modules and classes which can easily be used to build other projects in the future -- there is a lot of "framework" code. + + +SUBTITLE Lots more documentation + +The files are extensively commented. Consider this notes as an overview, and then read the source files for detailed and definitive information. + +Each function and class has a very brief decsription of it's purpose in a standard header, and extensive efforts have been maed to comment the code itself. + diff --git a/docs/api-notes/Win32_Clients.txt b/docs/api-notes/Win32_Clients.txt new file mode 100644 index 00000000..fad6c4af --- /dev/null +++ b/docs/api-notes/Win32_Clients.txt @@ -0,0 +1,13 @@ +The basic client tools now run on Win32 natively. +The port was done by nick@omniis.com. + +* bbackupd +* bbackupquery +* bbackupctl + +Have been ported. bbackupd runs as a NT style service. + +Known limitations: + +* File attributes and permissions are not backed up. + diff --git a/docs/api-notes/backup_encryption.txt b/docs/api-notes/backup_encryption.txt new file mode 100644 index 00000000..36580581 --- /dev/null +++ b/docs/api-notes/backup_encryption.txt @@ -0,0 +1,109 @@ +TITLE Encryption in the backup system + +This document explains how everything is encrypted in the backup system, and points to the various functions which need reviewing to ensure they do actually follow this scheme. + + +SUBTITLE Security objectives + +The crpyto system is designed to keep the following things secret from an attacker who has full access to the server. + +* The names of the files and directories +* The contents of files and directories +* The exact size of files + +Things which are not secret are + +* Directory heirarchy and number of files in each directory +* How the files change over time +* Approximate size of files + + +SUBTITLE Keys + +There are four separate keys used: + +* Filename +* File attributes +* File block index +* File data + +and an additional secret for file attribute hashes. + +The Cipher is Blowfish in CBC mode in most cases, except for the file data. All keys are maximum length 448 bit keys, since the key size only affects the setup time and this is done very infrequently. + +The file data is encrypted with AES in CBC mode, with a 256 bit key (max length). Blowfish is used elsewhere because the larger block size of AES, while more secure, would be terribly space inefficient. Note that Blowfish may also be used when older versions of OpenSSL are in use, and for backwards compatibility with older versions. + +The keys are generated using "openssl rand", and a 1k file of key material is stored in /etc/box/bbackupd. The configuration scripts make this readable only by root. + +Code for review: BackupClientCryptoKeys_Setup() +in lib/backupclient/BackupClientCryptoKeys.cpp + + +SUBTITLE Filenames + +Filenames need to be secret from the attacker, but they need to be compared on the server so it can determine whether or not is it a new version of an old file. + +So, the same Initialisation Vector is used for every single filename, so the same filename encrypted twice will have the same binary representation. + +Filenames use standard PKCS padding implemented by OpenSSL. They are proceeded by two bytes of header which describe the length, and the encoding. + +Code for review: BackupStoreFilenameClear::EncryptClear() +in lib/backupclient/BackupStoreFilenameClear.cpp + + +SUBTITLE File attributes + +These are kept secret as well, since they reveal information. Especially as they contain the target name of symbolic links. + +To encrypt, a random Initialisation Vector is choosen. This is stored first, followed by the attribute data encrypted with PKCS padding. + +Code for review: BackupClientFileAttributes::EncryptAttr() +in lib/backupclient/BackupClientFileAttributes.cpp + + +SUBTITLE File attribute hashes + +To detect and update file attributes efficiently, the file status change time is not used, as this would give suprious results and result in unnecessary updates to the server. Instead, a hash of user id, group id, and mode is used. + +To avoid revealing details about attributes + +1) The filename is added to the hash, so that an attacker cannot determine whether or not two files have identical attributes + +2) A secret is added to the hash, so that an attacker cannot compare attributes between accounts. + +The hash used is the first 64 bits of an MD5 hash. + + +SUBTITLE File block index + +Files are encoded in blocks, so that the rsync algorithm can be used on them. The data is compressed first before encryption. These small blocks don't give the best possible compression, but there is no alternative because the server can't see their contents. + +The file contains a number of blocks, which contain among other things + +* Size of the block when it's not compressed +* MD5 checksum of the block +* RollingChecksum of the block + +We don't want the attacker to know the size, so the first is bad. (Because of compression and padding, there's uncertainty on the size.) + +When the block is only a few bytes long, the latter two reveal it's contents with only a moderate amount of work. So these need to be encrypted. + +In the header of the index, a 64 bit number is chosen. The sensitive parts of the block are then encrypted, without padding, with an Initialisation Vector of this 64 bit number + the block index. + +If a block from an previous file is included in a new version of a file, the same checksum data will be encrypted again, but with a different IV. An eavesdropper will be able to easily find out which data has been re-encrypted, but the plaintext is not revealed. + +Code for review: BackupStoreFileEncodeStream::Read() (IV base choosen about half-way through) +BackupStoreFileEncodeStream::EncodeCurrentBlock() (encrypt index entry) +in lib/backupclient/BackupStoreFileEncodeStream.cpp + + +SUBTITLE File data + +As above, the first is split into chunks and compressed. + +Then, a random initialisation vector is chosen, stored first, followed by the compressed file data encrypted using PKCS padding. + +Code for review: BackupStoreFileEncodeStream::EncodeCurrentBlock() +in lib/backupclient/BackupStoreFileEncodeStream.cpp + + diff --git a/docs/api-notes/bin_bbackupd.txt b/docs/api-notes/bin_bbackupd.txt new file mode 100644 index 00000000..67ad9267 --- /dev/null +++ b/docs/api-notes/bin_bbackupd.txt @@ -0,0 +1,88 @@ +TITLE bin/bbackupd + +The backup client daemon. + +This aims to maintain as little information as possible to record which files have been uploaded to the server, while minimising the amount of queries which have to be made to the server. + + +SUBTITLE Scanning + +The daemon is given a length of time, t, which files over this age should be uploaded to the server. This is to stop recently updated files being uploaded immediately to avoid uploading something repeatedly (on the assumption that if a file has been written, it is likely to be modified again shortly). + +It will scan the files at a configured interval, and connect to the server if it needs to upload files or make queries about files and directories. + +The scan interval is actually varied slightly between each run by adding a random number up to a 64th of the configured time. This is to reduce cyclic patterns of load on the backup servers -- otherwise if all the boxes are turned on at about 9am, every morning at 9am there will be a huge spike in load on the server. + +Each scan chooses a time interval, which ends at the current time - t. This will be from 0 to current time - t on the first run, then the next run takes the start time as the end time of the previous run. The scan is only performed if the difference between the start and end times is greater or equal to t. + +For each configured location, the client scans the directories on disc recursively. + +For each directory + +* If the directory has never been scanned before (in this invocation of the daemon) or the modified time on the directory is not that recorded, the listing on the server is downloaded. + +* For each file, if it's modified time is within the time period, it is uploaded. If the directory has been downloaded, it is compared against that, and only uploaded if it's changed. + +* Find all the new files, and upload them if they lie within the time interval. + +* Recurse to sub directories, creating them on the server if necessary. + +Hence, the first time it runs, it will download and compare the entries on the disc to those on the server, but in future runs it will use the file and directory modification times to work out if there is anything which needs uploading. + +If there aren't any changes, it won't even need to connect to the server. + +There are some extra details which allow this to work reliably, but they are documented in the source. + + +SUBTITLE File attributes + +The backup client will update the file attributes on files as soon as it notices they are changed. It records most of the details from stat(), but only a few can be restored. Attributes will only be considered changed if the user id, group id or mode is changed. Detection is by a 64 bit hash, so detection is strictly speaking probablistic. + + +SUBTITLE Encryption + +All the user data is encrypted. There is a separate file, backup_encryption.txt which describes this, and where in the code to look to verify it works as described. + + +SUBTITLE Tracking files and directories + +Renaming files is a difficult problem under this minimal data scanning scheme, because you don't really know whether a file has been renamed, or another file deleted and new one created. + +The solution is to keep (on disc) a map of inode numbers to server object IDs for all directories and files over a certain user configurable threshold. Then, when a new file is discovered, it is first checked to see if it's in this map. If so, a rename is considered, which will take place if the local object corresponding to the name of the tracked object doesn't exist any more. + +Because of the renaming requirement, deletions of objects from the server are recorded and delayed until the end of the scan. + + +SUBTITLE Running out of space + +If the store server indicates on login to the backup client, it will scan, but not upload anything nor adjust it's internal stored details of the local objects. However, deletions and renames happen. + +This is to allow deletions to still work and reduce the amount of storage space used on the server, in the hope that in the future there will be enough space. + +Just not doing anything would mean that one big file created and then deleted at the wrong time would stall the whole backup process. + + +SUBTITLE BackupDaemon + +This is the daemon class for the backup daemon. It handles setting up of all the objects, and implements calulcation of the time intervals for the scanning. + + +SUBTITLE BackupClientContext + +State information for the scans, including maintaining a connection to the store server if required. + + +SUBTITLE BackupClientDirectoryRecord + +A record of state of a directory on the local filesystem. Containing the recursive scanning function, which is long and entertaining, but very necessary. It contains lots of comments which explain the exact details of what's going on. + + +SUBTITLE BackupClientInodeToIDMap + +A implementation of a map of inode number to object ID on the server. If Berkeley DB is available on the platform, it is stored on disc, otherwise there is an in memory version which isn't so good. + + + + + + diff --git a/docs/api-notes/bin_bbstored.txt b/docs/api-notes/bin_bbstored.txt new file mode 100644 index 00000000..c9c4e229 --- /dev/null +++ b/docs/api-notes/bin_bbstored.txt @@ -0,0 +1,54 @@ +TITLE bin/bbstored + +The backup store daemon. + +Maintains a store of encrypted files, and every so often goes through deleting unnecessary data. + +Uses an implementation of Protocol to communicate with the backup client daemon. See bin/bbstored/backupprotocol.txt for details. + + +SUBTITLE Data storage + +The data is arranged as a set of objects within a RaidFile disc set. Each object has a 64 bit object ID, which is turned into a filename in a mildly complex manner which ensure that directories don't have too many objects in them, but there is a minimal number of nested directories. See StoreStructure::MakeObjectFilename in lib/backupstore/StoreStructure.cpp for more details. + +An object can be a directory or a file. Directories contain files and other directories. + +Files in directories are supersceded by new versions when uploaded, but the old versions are flagged as such. A new version has a different object ID to the old version. + +Every so often, a housekeeping process works out what can be deleted, and deletes unnecessary files to take them below the storage limits set in the store info file. + + +SUBTITLE Note about file storage and downloading + +There's one slight entertainment to file storage, in that the format of the file streamed depends on whether it's being downloaded or uploaded. + +The problem is that it contains an index of all the blocks. For efficiency in managing these blocks, they all need to be in the same place. + +Files are encoded and decoded as they are streamed to and from the server. With encoding, the index is only completely known at the end of the process, so it's sent last, and lives in the filesystem last. + +When it's downloaded, it can't be decoded without knowing the index. So the index is sent first, followed by the data. + + +SUBTITLE BackupContext + +The context of the current connection, and the object which modifies the store. + +Maintains a cache of directories, to avoid reading them continuously, and keeps a track of a BackupStoreInfo object which is written back periodiocally. + + +SUBTITLE BackupStoreDaemon + +A ServerTLS daemon which forks off a separate housekeeping process as it starts up. + +Handling connections is delegated to a Protocol implementation. + + +SUBTITLE BackupCommands.cpp + +Implementation of all the commands. Work which requires writing is handled in the context, read only commands mainly in this file. + + +SUBTITLE HousekeepStoreAccount + +A class which performs housekeeping on a single account. + diff --git a/docs/api-notes/common/lib_common.txt b/docs/api-notes/common/lib_common.txt new file mode 100644 index 00000000..11d7b02d --- /dev/null +++ b/docs/api-notes/common/lib_common.txt @@ -0,0 +1,52 @@ +TITLE lib/common + +This module is the basic building block of the project. It is included implicitly as a dependency of all other modules. + +It provides basic services such as file and stream functionality, platform information, and various bits of utility code which doesn't fit naturally anywhere else but is useful to many other applications. + + +SUBTITLE Box.h + +The main include file for the project. It should be the first file included in every cpp file, followed by any system headers, then other project headers. This order has been chosen so that eventually it can be used as a target for pre-compiled headers. (This will be important for the Windows port) + + +SUBTITLE BoxPlatform.h + +This contains various bits of information on platform differences. The build scripts include a compile command line definition like PLATFORM_OPENBSD, which is then used to select the required options. + +Some of the stuff is not particularly pleasant, but it aims to produce a consistent compilation environment, and to have a abstracted way of setting other defines to tell the other files what things are available on this platform. + +GNU configure is not used, as it would be just another dependency. Although something does have to be done about compilation on Linux, which is just too different on each distribution to be caught by a common configuration here. + + +SUBTITLE Streams + +Much use is made of streams -- files, connections, buffers, all sorts of things are implemented using streams. + +The abstract base class IOStream defines the interface, see the class file for more details. + +Streams are lib/common's basic contribution to the project. A standard interface for handling data, and a number of utility classes which make performing common stream functions easy. + + +SUBTITLE Serialisation + +Note there is no "serialisable" class. Instead, objects which need to be serialised into Streams simply have two functions, ReadFromStream and WriteToStream. These perform as expected. + +As it turns out, there's no real need for a specific serialisation base class. If you do need to be able to serialise arbitary objects, there are two approaches. + +1) If you're serialising an arbitary object, you're going to know roughly what type it is. So it's likely to be a subclass of a known base class... in which case, you simply use virtual functions. + +2) If it really is an arbitary type, then you just write your function which accepts any type of object to serialise as a template. + + +SUBTITLE BoxException + +The exception base class for the project. All exceptions thrown by this code are dervied from BoxException. In general, each module which has unique errors has it's own exception class. + +CommonException errors are thrown throughout the project. + + +SUBTITLE Other bits + +There are some other extra useful bits which are documented only in the source files. + diff --git a/docs/api-notes/common/lib_common/BoxTime.txt b/docs/api-notes/common/lib_common/BoxTime.txt new file mode 100644 index 00000000..18bef5a5 --- /dev/null +++ b/docs/api-notes/common/lib_common/BoxTime.txt @@ -0,0 +1,7 @@ +TITLE BoxTime + +Not strictly a class, but time is presented in the project as 64 bit integers as microseconds since the UNIX Epoch. + +There are a number of utility functions in the BoxTime*.h files, which are useful. + +To get BoxTime values from a struct stat, use the FileModificationTime.h functions. \ No newline at end of file diff --git a/docs/api-notes/common/lib_common/CollectInBufferStream.txt b/docs/api-notes/common/lib_common/CollectInBufferStream.txt new file mode 100644 index 00000000..5f9556a3 --- /dev/null +++ b/docs/api-notes/common/lib_common/CollectInBufferStream.txt @@ -0,0 +1,26 @@ +CLASS CollectInBufferStream + +This class is essentially a buffer. Data is written to it, and stored in memory. Then when all data is written which will be written, it can be read out again. + +Useful for building streams in memory. + + +FUNCTION CollectInBufferStream::SetForReading() + +After you've written all the data, call this to set it to read mode. + + +FUNCTION CollectInBufferStream::Reset() + +Reset the stream to the state where is has no data, and is about to have data written. + + +FUNCTION CollectInBufferStream::GetSize() + +Get the size of the buffered data -- the entire data. Use the interface function BytesLeftToRead() in most cases. + + +FUNCTION CollectInBufferStream::GetBuffer() + +Get the buffer, so you can do bad things with it if you're feeling naughty. + diff --git a/docs/api-notes/common/lib_common/Configuration.txt b/docs/api-notes/common/lib_common/Configuration.txt new file mode 100644 index 00000000..ef5f38f6 --- /dev/null +++ b/docs/api-notes/common/lib_common/Configuration.txt @@ -0,0 +1,102 @@ +CLASS Configuration + +Implements the reading of multi-level configuration files. This is intended to be a generic way of representing configuration implementation. + +Basic validation is performed on files as they are read, specified by a data structure. + +test/common has some examples of usage. + + +SUBTITLE Configuration file format + +The format is simple, a list of Key = Value pairs. For example + +Key1 = Value1 +Key2 = Value2 + +Optionally, multiple values for the same key are allowed. + +Lists of configurations can be nested to any level. These are called Sub-configurations: + +SubConfig +{ + SubKey1 = ValueX + SubKey2 = ValueY +} + + +SUBTITLE Verification + +The verification structure specifies what keys are required, what are optional, and what sub-configurations are expected. Default values can be specified. + +See Configuration.h for the structures. + +RaidFileController::Initialise has a good example of a simple verification layout. + +Wildcards can be used for SubConfigurations, by specifying a name of "*". This allows you to use multiple sections to configure any number of items. + +Each item has a number of flags, which are combined to say whether an item is required, should be an integer or boolean, and rather importantly, whether it's the last item in the list. + +Verification is limited, so you may wish to do more verification the file. There are unimplemented hooks for custom verification functions to be included in the verification definitions. Should be done at some point. + +Boolean keys have possible values "true", "yes", "false", "no" (case insensitive). + + +FUNCTION Configuration::LoadAndVerify() + +Loads the configuration from disc, and verifies it. If there are problems with the verification, it returns some text which can be used to tell the user the problems with the file. These are fairly basic error messages, but do say what should be done to get it to parse properly. + +This returns a Configuration object, which can then be queries for Keys and Values. Sub-configurations are implemented through an interface which returns a reference to another Configuration object. + + + +FUNCTION Configuration::KeyExists() + +Does a specified key exist? + +Use this for optional key values only -- specify them in the verification structure if they are required. + + +FUNCTION Configuration::GetKeyValue() + +Get the value of a key as a string. + +If ConfigTest_MultiValueAllowed is set in the relevant entry in the verify structure, this string may contain multiple values, separated by a single byte with value 0x01 (use Configuration::MultiValueSeparator in code). Use SplitString() defined in Utils.h to split it into components. + +This representation was chosen as multi-values are probably rare, and unlikely to justify writing a nicer (but more memory intensive and complex) solution. + + +FUNCTION Configuration::GetKeyValueInt() + +Get the value of a key as an integer. Make sure the AsInt property is requried in the verification structure. + + +FUNCTION Configuration::GetKeyValueBool() + +Get the value of a key as a boolean value. Make sure the AsBool property is requried in the verification structure. + +Default to "false", should this verification not be performed and an unknown value is specified. + + +FUNCTION Configuration::GetKeyNames() + +Return a list of all the keys in this configuration. + + +FUNCTION Configuration::SubConfigurationExists() + +Does a specified sub-configuration exist? + + +FUNCTION Configuration::GetSubConfiguration() + +Return another Configuration object representing the sub section. + + +FUNCTION Configuration::GetSubConfigurationNames() + +Get a list of all the sub configurations. + +As there isn't a particularly neat way that configurations can be iterated over, mSubConfigurations is public. (BAD!) + + diff --git a/docs/api-notes/common/lib_common/Conversion.txt b/docs/api-notes/common/lib_common/Conversion.txt new file mode 100644 index 00000000..62d1967a --- /dev/null +++ b/docs/api-notes/common/lib_common/Conversion.txt @@ -0,0 +1,14 @@ +TITLE Generic type conversion + +Conversion.h provides generic type conversion. Within the BoxConvert namespace, it defines the templated function + + ToType Convert(FromType From) + +which converts the data type as expected. In general, from and to types have to be specified explicitly. + +Templates rather than overloaded functions are used, firstly to be absolutely explicit about the conversion required, and secondly because overloaded functions can't have differing return types for the same argument type. + +The function is specialised for various types, and the generic version uses C++ type conversion. + +Exceptions may be thrown if the conversion is not possible. These are all of the ConversionException type. + diff --git a/docs/api-notes/common/lib_common/ExcludeList.txt b/docs/api-notes/common/lib_common/ExcludeList.txt new file mode 100644 index 00000000..8a5bf36c --- /dev/null +++ b/docs/api-notes/common/lib_common/ExcludeList.txt @@ -0,0 +1,21 @@ +TITLE ExcludeList + +A class implementing a list of strings which are to be excluded in some way. Entries can be added as strings to be matched exactly, or as regular expressions. + +Multiple entries can be added in a single function call in a manner designed to work with the multi-value entries store in a Configuation object. + + +FUNCTION ExcludeList::AddDefiniteEntries() + +Definite entries are exact strings to match. + + +FUNCTION ExcludeList::AddRegexEntries() + +Regular expressions as defined by POSIX, and supported by the host platform. Will throw an exception if regular expressions are not supported by the platform. + + +FUNCTION ExcludeList::IsExcluded() + +Test a string for being excluded by definite or regex entries. + diff --git a/docs/api-notes/common/lib_common/FdGetLine.txt b/docs/api-notes/common/lib_common/FdGetLine.txt new file mode 100644 index 00000000..d92ff94d --- /dev/null +++ b/docs/api-notes/common/lib_common/FdGetLine.txt @@ -0,0 +1,11 @@ +CLASS FdGetLine + +See IOStreamGetLine, difference is that it works on basic UNIX file descriptors rather than full blown streams. Follows basic interface, except... + + +FUNCTION FdGetLine::GetLine + +Returns a string containing the optionally preprocessed line. + +Do not call if IsEOF if true. + diff --git a/docs/api-notes/common/lib_common/Guards.txt b/docs/api-notes/common/lib_common/Guards.txt new file mode 100644 index 00000000..6174fea6 --- /dev/null +++ b/docs/api-notes/common/lib_common/Guards.txt @@ -0,0 +1,5 @@ +TITLE Guards.h + +This file contains a couple of classes for using file and memory. When the class goes out of scope, the underlying object is closed or freed, respectively. + + diff --git a/docs/api-notes/common/lib_common/IOStream.txt b/docs/api-notes/common/lib_common/IOStream.txt new file mode 100644 index 00000000..09460656 --- /dev/null +++ b/docs/api-notes/common/lib_common/IOStream.txt @@ -0,0 +1,89 @@ +CLASS IOStream + +The base class for streams of data. See IOStream.h for specific details on functions, but the interface is described here. + +Most streams only implement one direction, but some both. + + +SUBTITLE Reading + + +FUNCTION IOStream::Read() + +Reads data from the stream. Returns 0 if there is no data available within the timeout requested. + +Unlike the UNIX API, a return of 0 does not mean that there is no more data to read. Use IOStream::StreamDataLeft() to find this out. + + +FUNCTION IOStream::ReadFullBuffer() + +If you want to read a specific sized block of data from a stream, it is annoying ot use the Read function, because it might return less, so you have to loop. + +This implements that loop. Note that the timeout is the timeout for each individual read -- it might take a lot longer to complete. + +You must check the return value, which is whether or not it managed to get all that data. + + +FUNCTION IOStream::BytesLeftToRead() + +How many bytes are left to read in the stream treated as a whole. May return IOStream::SizeOfStreamUnknown if it is something like a socket which doesn't know how much data is in the stream. + + +FUNCTION IOStream::StreamDataLeft() + +Returns true if more data can be read. Or more specifically, if more data might be able to be read some time in the future. + + +SUBTITLE Writing + + +FUNCTION IOStream::Write() + +Write data to a stream. Writes the entire block, or exceptions. + + +FUNCTION IOStream::WriteAllBuffered() + +Writes any buffered data to the underlying object. + +Call at the end of a series of writes to make sure the data is actually written. (Buffering is not yet implemented anywhere, but it should be soon.) + + +FUNCTION IOStream::StreamClosed() + +Is the stream closed, and writing no longer possible? + + +FUNCTION IOStream::CopyStreamTo() + +A utility function to copy the contents of one stream to another, until the reading stream has no data left. + + +SUBTITLE Other interfaces + +These are slightly nasty interfaces, because they have to match the UNIX API to some extent. + + +FUNCTION IOStream::Close() + +Close the stream -- intended to indicate that nothing more will be written. + +However, also closes reading in most cases. Stream dependent. Probably best just to delete the object and let it sort itself out. + + +FUNCTION IOStream::Seek() + +Seeks within the stream -- mainly for using files within a stream interface, although some of the utility stream classes support it too. + +This is actually a bad interface, because it does not specify whether it applies to reading or writing. This matches the interface provided by files with a single file pointer, but does not map well onto other stream types. + +Should it be changed? Perhaps. But then it means that files would either have to have an inconsitent interface, or the class keep track of two separate file pointers. Which isn't nice. + +So use carefully, and remember whether you're using a file stream, a reading stream, or a writing stream. + + +FUNCTION IOStream::GetPosition() + +Get the current file pointer. Subject to same problems as Seek with regard to semantics. + + diff --git a/docs/api-notes/common/lib_common/IOStreamGetLine.txt b/docs/api-notes/common/lib_common/IOStreamGetLine.txt new file mode 100644 index 00000000..04c56b57 --- /dev/null +++ b/docs/api-notes/common/lib_common/IOStreamGetLine.txt @@ -0,0 +1,29 @@ +CLASS IOStreamGetLine + +This class provides a convenient way to read text from a file, line by line. It also can preprocess the line to remove leading and trailing whitespace and comments. Comments are started by the character # and run to the end of the line. + +Create an instance by passing a reference to a stream into the constructor. + +Note the class does internal buffering, so you can only detach it later if the stream supports seeking backwards. + + +FUNCTION IOStreamGetLine::GetLine() + +Returns true if a line could be retreieved without a read timing out. + + +FUNCTION IOStreamGetLine::IsEOF() + +Whether the end of the stream has been reached. Do not call GetLine if this is true. + + +FUNCTION IOStreamGetLine::GetLineNumber() + +Returns the line number. + + +FUNCTION IOStreamGetLine::DetachFile() + +Detaches the stream from the GetLine class. Will seek backwards to "replace" data it's buffered back in the stream. + + diff --git a/docs/api-notes/common/lib_common/MainHelper.txt b/docs/api-notes/common/lib_common/MainHelper.txt new file mode 100644 index 00000000..eb5b07f0 --- /dev/null +++ b/docs/api-notes/common/lib_common/MainHelper.txt @@ -0,0 +1,4 @@ +TITLE MainHelper.h + +This header contains a couple of macros which add exception handling and reporting to main() functions for executable programs. + diff --git a/docs/api-notes/common/lib_common/WaitForEvent.txt b/docs/api-notes/common/lib_common/WaitForEvent.txt new file mode 100644 index 00000000..0bc55726 --- /dev/null +++ b/docs/api-notes/common/lib_common/WaitForEvent.txt @@ -0,0 +1,16 @@ +CLASS WaitForEvent + +This class implements a way to efficiently wait on one or more system objects, for example sockets and file descriptors. Where kqueue() is available, this is used, otherwise poll(). The poll() implementation is less comprehensive and rather more limited. + +To add a compatible object, call Add(). To remove it, call Remove(). To wait for an object to become ready, call Wait(), which returns a pointer to the first ready object, or 0 for a timeout. + +The timeout is set either in the constructor, or using the SetTimout() method. It is specified in milliseconds. + +The kqueue based implementation will automatically remove objects when they are closed (actually, the OS does this), but the poll implementation requires that Remove() be called. + +The flags passed to Add() or Remove() are passed to the object, and are ignored by this class. + +For an object to be able to be added to the list, it should implement FillInKEvent() and FillInPoll() for kqueue and poll implementations respectively. Use the PLATFORM_KQUEUE_NOT_SUPPORTED define to test which is necessary for the platform. + +It does not have to be derived off any particular class, as it uses templates to be compatible with any class. + diff --git a/docs/api-notes/common/lib_common/xStream.txt b/docs/api-notes/common/lib_common/xStream.txt new file mode 100644 index 00000000..91e9c0ea --- /dev/null +++ b/docs/api-notes/common/lib_common/xStream.txt @@ -0,0 +1,40 @@ +TITLE Various stream types + +Some useful streams are implemented in lib/common. + + +SUBTITLE CollectInBufferStream + +Described in it's own page. + + +SUBTITLE FileStream + +Implements a stream from a file, allowing both reading and writing. + + +SUBTITLE MemBlockStream + +Turns a memory block into a readable stream. + +Can also be constructed using StreamableMemBlock, CollectInBufferStream and MemBlockStream as sources of the buffer. + + +SUBTITLE PartialReadStream + +Create a readable stream which will read a set number of bytes from another stream, and then declare itself as closed. + +Useful for extracting a small chunk of another stream to present to a function which expects to consume all of a stream. + + +SUBTITLE ReadGatherStream + +Create a readable stream out of blocks from many streams -- so various sections of any number of streams are composed into a single stream. + +To use, register each stream with AddComponent, then use the returned 'handle' with AddBlock to add blocks to the composed stream. + +Optionally, the object will take ownership of the streams added, and delete them when itself is deleted. + +See the comments in the function blocks in the cpp file for more info. + + diff --git a/docs/api-notes/common/lib_compress.txt b/docs/api-notes/common/lib_compress.txt new file mode 100644 index 00000000..f3e26f20 --- /dev/null +++ b/docs/api-notes/common/lib_compress.txt @@ -0,0 +1,8 @@ +TITLE lib/compress + +This is a horrid C++ interface to zlib. It is written this way to avoid having to buffer any data, and so be unnecessarily inefficient. But this does end up just being a wrapper to the zlib interface. + +See test/compress for an example of how to use it. But it should be very familiar. + +For an easier interface, use CompressStream. + diff --git a/docs/api-notes/common/lib_compress/CompressStream.txt b/docs/api-notes/common/lib_compress/CompressStream.txt new file mode 100644 index 00000000..eca43eb6 --- /dev/null +++ b/docs/api-notes/common/lib_compress/CompressStream.txt @@ -0,0 +1,27 @@ +TITLE CompressStream + +This implements a compressing stream class, which compresses and decompresses data sent to an underlying stream object. Data is likely to be buffered. + + +WARNING: Use WriteAllBuffered() (after objects which need to be sent in their entirity) and Close() (after you have finished writing to the stream) otherwise the compression buffering will lose the last bit of data for you. + + +The class works as a filter to an existing stream. Ownership can optionally be taken so that the source stream is deleted when this object is deleted. + +It can operate in one or two directions at any time. Data written to the stream is compressed, data read from the stream is decompressed. If a direction is not being (de)compressed, then it can act as a pass-through without touching the data. + +The constructor specifies the actions to be taken: + +CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, + bool PassThroughWhenNotCompressed = false); + +pStream - stream to filter +TakeOwnership - if true, delete the stream when this object is deleted. +DecompressRead - reads pass through a decompress filter +CompressWrite - writes pass through a compression filter +PassThroughWhenNotCompressed - if not filtering a direction, pass through the data unaltered, otherwise exception if an attempt is made on that direction. + + +Note that the buffering and compression will result in different behaviour than the underlying stream: data may not be written in one go, and data being read may come out in different sized chunks. + diff --git a/docs/api-notes/common/lib_crypto.txt b/docs/api-notes/common/lib_crypto.txt new file mode 100644 index 00000000..9ddafe69 --- /dev/null +++ b/docs/api-notes/common/lib_crypto.txt @@ -0,0 +1,28 @@ +TITLE lib/crypto + +Provides cryptographic primatives using the OpenSSL implementation. + + +SUBTITLE CipherContexts and CipherDescriptions + +A CipherContext is the interface to encryption and decryption, providing a generic interface. + +It is constructed with a decrypt or encrypt flag, and a CipherDescription. The CipherDescription specifies which cipher is to be used, the key, and all other cipher specific paramteres. + +The CipherContext has support for padding and initialisation vectors. + + +SUBTITLE Random numbers + +An interface to the OpenSSL psuedo random number generater is provided. + + +SUBTITLE Digests + +An interface to the MD5 digest is provided. + + +SUBTITLE RollingChecksum + +The rsync rolling checksum is implemented. The interface is a little tricky to be efficient as the caller must track the position and provide relevant bytes to advance the checksum. + diff --git a/docs/api-notes/common/lib_crypto/CipherContext.txt b/docs/api-notes/common/lib_crypto/CipherContext.txt new file mode 100644 index 00000000..30fb4608 --- /dev/null +++ b/docs/api-notes/common/lib_crypto/CipherContext.txt @@ -0,0 +1,28 @@ +CLASS CipherContext + +Encryption and decryption using OpenSSL EVP interface. + +See the cpp file for more documentation in the function headers, and test/crypto for examples. + +General notes below. + + +SUBTITLE Construction + +Construct with the encryption direction, and a CipherDescription of the cipher and key required. + + +SUBTITLE Encrypting or decrypting + +Begin() and Transform() allow piece by piece transformation of a block. + +TransformBlock() transforms an entire block. + + +SUBTITLE Buffering + +All transforms expect to have enough space in the buffers for their entire output. Because of block boundaries and padding, it is necessary that the output buffer should be bigger than the input buffer. The amount of space depends on the cipher. + +InSizeForOutBufferSize() and MaxOutSizeForInBufferSize() perform these space calculations, returning the maximuim in size for a specified out size, and the reverse, respectively. + + diff --git a/docs/api-notes/common/lib_crypto/RollingChecksum.txt b/docs/api-notes/common/lib_crypto/RollingChecksum.txt new file mode 100644 index 00000000..d871b3f2 --- /dev/null +++ b/docs/api-notes/common/lib_crypto/RollingChecksum.txt @@ -0,0 +1,36 @@ +CLASS RollingChecksum + +Implementing the rsync rolling checksum algorithm. Read it's description first: + +http://samba.anu.edu.au/rsync/tech_report/node3.html + + +SUBTITLE Construction and initial checksum calculation + +The constructor takes a pointer to a block of data and a size, and calculates the checksum of this block. It can now be "rolled forward" to find the checksum of the block of the same size, one byte forward, with minimal calculation. + + +FUNCTION RollingChecksum::GetChecksum() + +Returns the checksum for the current block. + + +FUNCTION RollingChecksum::RollForward() + +This function takes the byte at the start of the current block, and the last byte of the block it's rolling forward to, and moves the checksum on. + +If the block is + + char *pBlock = ; + +with size s, then it should be called with + + RollForward(pBlock[0], pBlock[s]) + +and now GetChecksum will return the checksum of the block (pBlock+1) of size s. + + +FUNCTION RollingChecksum::RollForwardSeveral() + +Similar to RollForward(), but is more efficient for skipping several bytes at once. Takes pointers to the data buffer rather than the actual data values. + diff --git a/docs/api-notes/common/lib_server.txt b/docs/api-notes/common/lib_server.txt new file mode 100644 index 00000000..392f331d --- /dev/null +++ b/docs/api-notes/common/lib_server.txt @@ -0,0 +1,9 @@ +TITLE lib/server + +This provides various classes for implementing client/server applications (and UNIX daemons). + +The stars of the show are ServerTLS and Protocol, which allow client server applications which communicate using TLS (SSL successor) to be built with surprisingly few lines of code. + +All details in the respective class files. + +Look at test/basicserver for examples. diff --git a/docs/api-notes/common/lib_server/Daemon.txt b/docs/api-notes/common/lib_server/Daemon.txt new file mode 100644 index 00000000..6956ec2b --- /dev/null +++ b/docs/api-notes/common/lib_server/Daemon.txt @@ -0,0 +1,96 @@ +CLASS Daemon + +Implement a UNIX daemon which + +* Daemonises (detach from console, etc) +* Sets up signal handlers, and does useful things with them +* Reads configuration files +* Writes PID file +* Opens syslog + +all the usual UNIX basic daemon behaviour. + +The daemon exe takes optional arguments: The first is a configuration file filename which overrides the default. If the second argument is 'SINGLEPROCESS' the daemon will not daemonise. + +The configuration file must have a section like this + +Server +{ + PidFile = /var/run/daemon.pid + User = username +} + +Use DAEMON_VERIFY_SERVER_KEYS (defined in Daemon.h) to include the necessary keys in your configuration file's verify structure. + +The "User" line is optional, and if it is present the Daemon will change user to this username just before it daemonises. Note that unless the directory the PID file is written to has write permission for this user, the PID file will not be deleted on exit. + + +To implement a daemon, derive a class from Daemon, and override Run(). + +Then in your main file, do something like + +int main(int argc, const char *argv[]) +{ + MAINHELPER_START + + BackupDaemon daemon; + return daemon.Main(BOX_FILE_BBACKUPD_DEFAULT_CONFIG, argc, argv); + + MAINHELPER_END +} + +and that's it. Obviously it's worth doing a few more things as well, but that's it. + + +FUNCTION Daemon::Run() + +Override with the main daemon code. It should behave like this + +void SomethingDaemon::Run() +{ + // Read configuration file + + // Do lots of work until StopRun() returns true + while(!StopRun()) + { + ::sleep(10); + } + + // Clean up nicely +} + +which allows the base class to implement the standard start, terminate and -HUP behaviours correctly. + + +FUNCTION Daemon::DaemonName() + +Returns the name of the daemon, for use in syslog. + + +FUNCTION Daemon::DaemonBanner() + +Returns the banner to be displayed on startup, or 0 for no banner. + + +FUNCTION Daemon::GetConfigVerify() + +Returns a configuration verify structure for verifying the config file. Note that this configuration structure should include a sub-configuration called "Server, and have entries defined by DAEMON_VERIFY_SERVER_KEYS. See one of the bin/bbackupd for an example. + + +FUNCTION Daemon::StopRun() + +Returns true when the daemon needs to be terminated or restarted. Use IsReloadConfigWanted() and IsTerminateWanted() to find out which one, if you need to know. + + +FUNCTION Daemon::SetupInInitialProcess() + +Override to perform additional functions in the initial process, before forking and detachment happens. + + +FUNCTION Daemon::EnterChild() + +Called when a child is entered. If you override it, remember to call the base class. + + + + diff --git a/docs/api-notes/common/lib_server/Protocol.txt b/docs/api-notes/common/lib_server/Protocol.txt new file mode 100644 index 00000000..09d3c1f1 --- /dev/null +++ b/docs/api-notes/common/lib_server/Protocol.txt @@ -0,0 +1,120 @@ +CLASS Protocol + +Protocol + +* serialises and deserialises data objects +* sends arbitary streams + +through a bi-directional IOStream object, usually a socket. + +These data objects are auto-generated by a perl script, along with the logic to implement a simple protocol where one end is a client, and the other a server. The client sends a command object (and optional streams) and the server returns a reply object (and optional streams). + +The perl script uses a description file which specifies the data inside these objects (which can be extended to include any type which implements the standard serialisation functions), the required responses to the commands, and whether streams are involved. + +It then implements a server object, which given a stream and a user defined context object, waits for commands, processes them, and sends the results back. All you need to do is implement a DoCommand() function for each command object you define. + +On the client side, a Query() function is implemented which takes objects as parameters, and returns the right type of reply object (or throws an exception if it doesn't get it.) Short cut Query() functions are also implemented which don't require objects to be created manually. + +Thus, implementing a server is as simple as deriving a daemon off ServerStream or ServerTLS, and implementing the Connection() function as + + void TestProtocolServer::Connection(SocketStream &rStream) + { + TestProtocolServer server(rStream); + TestContext context; + server.DoServer(context); + } + +and that's it. TestContext is a user defined class which keeps track of all the state of the connection, and is passed to all the DoCommand() functions, which look like this: + + std::auto_ptr + TestProtocolServerSimple::DoCommand(TestProtocolServer &rProtocol, + TestContext &rContext) + { + return std::auto_ptr + (new TestProtocolServerSimpleReply(mValue+1)); + } + +(taken from test/basicserver) + +The client code looks like this + + SocketStream conn; + conn.Open(Socket::TypeUNIX, "testfiles/srv4.sock"); + + TestProtocolClient protocol(conn); + + // Query + { + std::auto_ptr + reply(protocol.QuerySimple(41)); + TEST_THAT(reply->GetValuePlusOne() == 42); + } + + +Finally, debug logging can be generated which allows a list of all commands and their parameters to be logged to syslog or a file. + + +SUBTITLE Protocol Description File + +This file is passed to the lib/server/makeprotocol.pl script, which generates a h and cpp file to implement the protocol. + +It is in two sections, separated by a 'BEGIN_OBJECTS' on a line of it's own. + +In the top half, the following statements must be made. + +Name + The name of the protocol, used in naming classes. + +IdentString + The idenfitifaction string sent over the IOStream to confirm it it + is talking to another Protocol object speaking the same Protocol. + +ServerContextClass + The user defined context class used for the server, and the header + file it is defined in. + +Additionally, the following optional commands can be made. + +ClientType +ServerType (similarly) + Extends the types used in the objects below. Server and client + can use different types for the same object type. + +ImplementLog (Client|Server) (syslog|file) + Implement command logging for client or server into syslog or a file. + +LogTypeToText (Client|Server) + + For extended types, optionally define how to convert them into printf + elements and the code to run to get the argument. Within the evaluate + parameter, VAR is replaced by the name of the variable to display. + If this is not specified for a given type, OPAQUE is output instead. + + +In the object section, an object is defined by a line + + + +followed by lines beginning with whitespace defining the data transmitted in the object. The type may be list, which specifies a list (implemented as a std::vector) of entries of that type. + +The attributes specify exactly how that object is used in the defined protocol. + +Reply + The object is a reply object, sent from the server to the client. + +Command(Reply-Type) + The object is a command, send from the client to the server, and the server + will send back an object of type Reply-Type. + +IsError(Type-Field,SubType-Field) + The command is an error object, and the two files specify the data member + which describes the error type and sub type. + +EndsConversation + When this command is received, the connection is to be terminated. + (ie a logout command) + +StreamWithCommand + When this command is sent as a command, a stream follows it. + + diff --git a/docs/api-notes/common/lib_server/ServerStream.txt b/docs/api-notes/common/lib_server/ServerStream.txt new file mode 100644 index 00000000..6c5932a0 --- /dev/null +++ b/docs/api-notes/common/lib_server/ServerStream.txt @@ -0,0 +1,29 @@ +CLASS ServerStream + +ServerStream implementes a Daemon which accepts stream connections over sockets, and forks into a child process to handle them. + +To implement a daemon, derive from + + ServerStream + +The type SocketStream specifies that incoming connections should be treated as normal sockets, and SERVER_LISTEN_PORT is the port to listen to (if it's a inet socket, and if not overridden in the config file). The actual addresses (or names) to bind to are specified in the configuration file by the user. + +Make sure SERVERSTREAM_VERIFY_SERVER_KEYS(0) is included in the configuration verification structure in the "Server" sub configuration. 0 could be replaced with a default address, for example "unix:/var/run/server.sock" to specific a default UNIX socket in the filesystem. + +See test/basicserver for a simple example. + +The ListenAddresses key in the Server subconfiguration is a comma separated list of addresses, specified as family:name. Internet sockets are family 'inet', for example 'inet:localhost' (or 'inet:localhost:1080' to specify a port number as well), and unix domain sockets are 'unix', example above. + + +Override Connection to handle the connection. + +Remember to override Daemon functions like the server name, and start it up, just like a generic Daemon. + + +FUNCTION ServerStream::Connection + +This function takes a connected stream as it's argument. It should then proceed to do whatever it needs to do to talk to the client. + +Using IOStreamGetLine or a Protocol class to communicate may be quick ways of implementing this functionality. + + diff --git a/docs/api-notes/common/lib_server/ServerTLS.txt b/docs/api-notes/common/lib_server/ServerTLS.txt new file mode 100644 index 00000000..dbde500f --- /dev/null +++ b/docs/api-notes/common/lib_server/ServerTLS.txt @@ -0,0 +1,6 @@ +CLASS ServerTLS + +Implements a server which uses TLS (SSL) to encrypt and authenticate connections. + +Very similar to ServerStream, except it reads the certificate files for the TLSContext out of the Server sub-configuration to set up a TLSContext ("CertificateFile", "PrivateKeyFile" and "TrustedCAsFile"). Otherwise works exactly the same. + diff --git a/docs/api-notes/common/lib_server/SocketStream.txt b/docs/api-notes/common/lib_server/SocketStream.txt new file mode 100644 index 00000000..82813279 --- /dev/null +++ b/docs/api-notes/common/lib_server/SocketStream.txt @@ -0,0 +1,8 @@ +CLASS SocketStream + +A implementation of IOStream which wraps a socket connection. + +It can either be created by attaching to an existing object, or use the Open() function to open a connection to a named host on a specific port (or a local UNIX socket in the filesystem). + +Follows stream interface. + diff --git a/docs/api-notes/common/lib_server/SocketStreamTLS.txt b/docs/api-notes/common/lib_server/SocketStreamTLS.txt new file mode 100644 index 00000000..ebb3f233 --- /dev/null +++ b/docs/api-notes/common/lib_server/SocketStreamTLS.txt @@ -0,0 +1,11 @@ +CLASS SocketStreamTLS + +An implementation of IOStream which wraps a TLS (SSL) connection over a socket. + +The Open function takes a TLSContext reference which specifies the parameters for the connection. + + +FUNCTION GetPeerCommonName() + +Returns the common name of the certificate presented by the remote end. + diff --git a/docs/api-notes/common/lib_server/TLSContext.txt b/docs/api-notes/common/lib_server/TLSContext.txt new file mode 100644 index 00000000..ff50d3e6 --- /dev/null +++ b/docs/api-notes/common/lib_server/TLSContext.txt @@ -0,0 +1,16 @@ +CLASS TLSContext + +A wrapper over the OpenSSL context object. + +Note: you need to call SSLLib::Initialise at the beginning of your program to use these functions. + + +SUBTITLE Construction + +The constuctor takes the following parameters + +* Boolean for whether this is acting as a server or a client +* The .pem file containing the certificate which will be used +* The .pem file containing the private key for this certificate +* The .pem file containing the certificates which will certify the other end of the connection. + diff --git a/docs/api-notes/common/memory_leaks.txt b/docs/api-notes/common/memory_leaks.txt new file mode 100644 index 00000000..9a9764ea --- /dev/null +++ b/docs/api-notes/common/memory_leaks.txt @@ -0,0 +1,44 @@ +TITLE Memory leak detection + +The build system contains a primative memory leak detection system in debug builds. + +It works by using #defines to replace calls to malloc, free, realloc, new and delete with debug versions which record their use. When a process ends, it dumps a list of all the blocks or objects which were allocated by not freed, and the file and line of the source where they are originally allocated. + +It's not perfect, but should catch most things and work on most platforms. + +If it doesn't work on your platform, define PLATFORM_DISABLE_MEM_LEAK_TESTING in BoxPlatform.h within the relevant section. + + +SUBTITLE Requirements in source + +It does require some extra lines in the source file. The last included file in each .cpp file must be + + #include "MemLeakFindOn.h" + +and if a .h file uses any of these functions, the contents of the .h file should be bounded with + + #include "MemLeakFindOn.h" + + // ... some code, but absolutely no #includes + + #include "MemLeakFindOff.h" + +The cleanupforcvs.pl script checks for correct usage. + + +SUBTITLE Use in tests + +Tests will automatically dump memory leaks and regard them as a failure for anything executing in the main test process. + +To test for memory leaks in programs run from the test, or daemons, use something like + + TestRemoteProcessMemLeaks("bbackupd.memleaks"); + +If memory leak detection is being used, it will check the specified file for memory leak reports (deleting it afterwards). Any leak is an error. + +The Daemon class automactically arranges for the memory leak reports to be written. Other exes should set this up themselves, preferably using the define in MainHelper.h, as the first thing in their main() function. + + MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, name) + +File is the filename to write the report to, conventionally called ".memleaks", and name is the name of the exe. + diff --git a/docs/api-notes/encrypt_rsync.txt b/docs/api-notes/encrypt_rsync.txt new file mode 100644 index 00000000..a5db2df2 --- /dev/null +++ b/docs/api-notes/encrypt_rsync.txt @@ -0,0 +1,66 @@ +TITLE Encrypted rsync algorithm + +The backup system uses a modified version of the rsync algorithm. A description of the plain algorithm can be found here: + + http://samba.anu.edu.au/rsync/tech_report/ + +The algorithm is modified to allow the server side to be encrypted, yet still benefit from the reduced bandwidth usage. For a single file transfer, the result will be only slightly less efficient than plain rsync. For a backup of a large directory, the overall bandwidth may be less due to the way the backup client daemon detects changes. + +This document assumes you have read the rsync document. + +The code is in lib/backupclient/BackupStoreFile*.*. + + +SUBTITLE Blocks + +Each file is broken up into small blocks. These are individually compressed and encrypted, and have an entry in an index which contains, encrypted, it's weak and strong checksums and decoded plaintext size. This is all done on the client. + +Why not just encrypt the file, and use the standard rsync algorithm? + +1) Compression cannot be used, since encryption turns the file into essentially random data. This is not very compressible. + +2) Any modification to the file will result in all data after that in the file having different ciphertext (in any cipher mode we might want to use). Therefore the rsync algorithm will only be able to detect "same" blocks up until the first modification. This significantly reduces the effectiveness of the process. + +Note that blocks are not all the same size. The last block in the file is unlikely to be a full block, and if data is inserted which is not a integral multiple of the block size, odd sized blocks need to be created. This is because the server cannot reassemble the blocks, because the contents are opaque to the server. + + +SUBTITLE Modifed algorithm + +To produce a list of the changes to send the new version, the client requests the block index of the file. This is the same step as requesting the weak and strong checksums from the remote side with rsync. + +The client then decrypts the index, and builds a list of the 8 most used block sizes above a certain threshold size. + +The new version of the file is then scanned in exactly the same way as rsync for these 8 block sizes. If a block is found, then it is added to a list of found blocks, sorted by position in the file. If a block has already been found at that position, then the old entry is only replaced by the new entry if the new entry is a "better" (bigger) match. + +The block size covering the biggest file area is searched first, so that most of the file can be skipped over after the first pass without expensive checksumming. + +A "recipe" is then built from the found list, by trivially discarding overlapping blocks. Each entry consists of a number of bytes of "new" data, a block start number, and a number of blocks from the old file. The data is stored like this as a memory optimisation, assuming that files mostly stay the same rather than having all their blocks reordered. + +The file is then encoded, with new data being sent as blocks of data, and references to blocks in the old file. The new index is built completely, as the checksums and size need to be rencrypted to match their position in the index. + + +SUBTITLE Combination on server + +The "diff" which is sent from the client is assembled into a full file on the server, simply by adding in blocks from the old file where they are specified in the block index. + + +SUBTITLE Storage on server + +Given that the server will in general store several versions of a file, combining old and new files to form a new file is not terribly efficient on storage space. Particularly for large multi-Gb database files. + +An alternative scheme is outlined below, however, it is significantly more complex to implement, and so is not implemented in this version. + +1) In the block index of the files, store the file ID of the file which each block is source from. This allows a single file to reference blocks from many files. + +2) When the file is downloaded, the server combines the blocks from all the files into a new file as it is streamed to the client. (This is not particuarly complicated to do.) + +This all sounds fine, until housekeeping is considered. Old versions need to be deleted, without losing any blocks necessary for future versions. + +Instead of just deleting a file, the server works out which blocks are still required, and rebuilds the file omitting those blocks which aren't required. + +This complicates working out how much space a file will release when it is "deleted", and indeed, adds a whole new level of complexity to the housekeeping process. (And the tests!) + +The directory structure will need an additional flag, "Partial file", which specifies that the entry cannot be built as previous blocks are no longer available. Entries with this flag should never be sent to the client. + + + diff --git a/docs/api-notes/lib_backupclient.txt b/docs/api-notes/lib_backupclient.txt new file mode 100644 index 00000000..3e4a079b --- /dev/null +++ b/docs/api-notes/lib_backupclient.txt @@ -0,0 +1,46 @@ +TITLE lib/backupclient + +Classes used on the store and on the server. + +See documentation in the files for more details. + + +SUBTITLE BackupStoreDirectory + +The directory listing class, containing a number of entries, representing files. + + +SUBTITLE BackupStoreFile + +Handles compressing and encrypting files, and decoding files downloaded from the server. + + +SUBTITLE BackupStoreFilename + +An encrypted filename. + + +SUBTITLE BackupStoreFilenameClear + +Derived from BackupStoreFilename, but with the ability to encrypt and decrypt filenames. Client side only. + + +SUBTITLE BackupClientFileAttributes + +Only used on the client -- the server treats attributes as blocks of opaque data. + +This reads attributes from files on discs, stores them, encrypts them, and applies them to new files. + +Also has a static function to generate filename attribute hashes given a struct stat and the filename. + + +SUBTITLE BackupClientRestore + +Routines to restore files from the server onto the client filesystem. + + +SUBTITLE BackupClientCryptoKeys + +This reads the key material from disc, and sets up the crypto for storing files, attributes and directories. + + diff --git a/docs/api-notes/lib_backupstore.txt b/docs/api-notes/lib_backupstore.txt new file mode 100644 index 00000000..8f24eb7b --- /dev/null +++ b/docs/api-notes/lib_backupstore.txt @@ -0,0 +1,30 @@ +TITLE lib/backupstore + +Classes which are shared amongst the server side store utilities, bbstored and bbstoreaccounts. Note also depends on lib/backupclient, as a lot of code is shared between the client and server. + + +SUBTITLE BackupStoreAccountDatabase + +A simple implementation of an account database. This will be replaced with a more suitable implementation. + + +SUBTITLE BackupStoreAccounts + +An interface to the account database, and knowledge of how to initialise an account on disc. + + +SUBTITLE BackupStoreConfigVerify + +The same configuration file is used by all the utilities. This is the Configuration verification structure for this file. + + +SUBTITLE BackupStoreInfo + +The "header" information about an account, specifying current disc usage, space limits, etc. + + +SUBTITLE StoreStructure + +Functions specifying how the files are laid out on disc in the store. + + diff --git a/docs/api-notes/raidfile/RaidFileRead.txt b/docs/api-notes/raidfile/RaidFileRead.txt new file mode 100644 index 00000000..0a5efbf7 --- /dev/null +++ b/docs/api-notes/raidfile/RaidFileRead.txt @@ -0,0 +1,14 @@ +CLASS RaidFileRead + +Read a raid file. + +IOStream interface, plus a few extras, including reading directories and checking that files exist. + + +FUNCTION RaidFileRead::Open + +Open a given raid file -- returns a pointer to a new RaidFileRead object. + +Note that one of two types could be returned, depending on the representation of the file. + + diff --git a/docs/api-notes/raidfile/RaidFileWrite.txt b/docs/api-notes/raidfile/RaidFileWrite.txt new file mode 100644 index 00000000..89500c37 --- /dev/null +++ b/docs/api-notes/raidfile/RaidFileWrite.txt @@ -0,0 +1,36 @@ +CLASS RaidFileWrite + +Interface to writing raidfiles. + +See IOStream interface. + +Some other useful functions are available, see h and cpp files. + + +FUNCTION RaidFileWrite::RaidFileWrite() + +The constructor takes the disc set number and filename of the file you're interested. + + +FUNCTION RaidFileWrite::Open() + +Open() opens the file for writing, and takes an "allow overwrite" flag. + + +FUNCTION RaidFileWrite::Commit() + +Commmit the file, and make it visible to RaidFileRead. If ConvertToRaidNow == true, it will be converted to raid file representation immediately. + +Setting it to false is not a good idea. Later on, it will tell a daemon to convert it in the background, but for now it simply won't be converted. + + +FUNCTION RaidFileWrite::Discard() + +Abort the creation/update. Equivalent to just deleting the object without calling Commit(). + + +FUNCTION RaidFileWrite::Delete() + +Delete a file -- don't need to Open() it first. + + diff --git a/docs/api-notes/raidfile/lib_raidfile.txt b/docs/api-notes/raidfile/lib_raidfile.txt new file mode 100644 index 00000000..0c4244ce --- /dev/null +++ b/docs/api-notes/raidfile/lib_raidfile.txt @@ -0,0 +1,73 @@ +TITLE lib/raidfile + +Implements RAID type abilities in files in userland, along with simple transaction like semantics for writing and reading files. + +IOStream interfaces are used to read and write files. + +Eventually a raid file daemon will be written to "raidify" files in the background. This will allow fast streaming of data to a single disc, followed by splitting into separate raid files later. + +There is an option to disable raidification for use on systems with only one hard disc, or a hardware RAID array. + + +SUBTITLE Controller + +The raid disc sets are managed by RaidFileController. This reads the configuration file, /etc/box/raidfile.conf, and then stores the sets of discs for use by the other classes. + +In the code, files are referenced using an integer set number, and a filename within that set. For example, (0, "dir/subdir/filename.ext"). The RAID file controller turns this into actual physcial filenames transparently. + +The files are devided into blocks, which should match the fragment/block size of the underlying filesystem for maximum space efficiency. + +If the directories specified in the configuration files are all equal, then userland RAID is disabled. The files will not be processed into the redundant parts. + + +SUBTITLE Writing + +Files are written (and modified) using the RaidFileWrite class. This behaves as a seekable IOStream for writing. + +When all data has been written, the file is committed. This makes it available to be read. Processing to split it into the three files can be delayed. + +If the data is not commited, the temporary write file will be deleted. + +RaidFileWrite will optionally refuse to overwrite another file. If it is being overwritten, a read to that file will get the old file until it has been committed. + + +SUBTITLE Reading + +File are opened and read with RaidFileRead::Open. This returns an IOStream (with some extras) which reads a file. + +If the file has been turned into the RAID representation, then if an EIO error occurs on any operation, it will switch transparently into recovery mode where the file will be regenerated from the remaining two files using the parity file. (and log an error to syslog) + + +SUBTITLE Layout on disc + +The directory structure is replicated onto each of the three directories within the disc set. + +Given a filename x, within the disc set, one of the three discs is chosen as the 0th disc for this file. This is done by hashing the filename. This ensures that an equal load is placed on each disc -- otherwise one disc would only be accessed when a RAID file was being written. + +When the file is being written, the actual physical file is "x.rfwX". This just a striaght normal file -- nothing is done to the data. + +When it is commited, it is renamed to "x.rfw". + +Then, when it is turned into the raid representation, it is split across three discs, each file named "x.rf". + +When trying to open a file, the first attempt is for "x.rfw". If this exists, it is used as is. + +If this fails, two of the "x.rf" files are attempted to be opened. If this succeeds, the file is opened in RAID mode. + +This procedure guarenettes that a file will be opened either as a normal file, or as the correct three RAID file elements, even if a new version is being commited at the same time, or another process is working to turn it into a raid file. + + +SUBTITLE Raid file representation + +The file is split into three files, stripe 1, stripe 2 and parity. + +Considering the file as a set of blocks of the size specified in the configuration, odd numbered blocks go in stripe 1, even blocks in stripe 2, and a XOR of the corresponding blocks in parity. + +Thus only two files are needed to recreate the original. + +The complexity comes because the size of the file is not stored inside the files at any point. This means if stripe 2 is missing, it is ambiguous as to how big the file is. + +Size is stored as a 64 bit integer. This is appended to the parity file, unless it can be stored by XORing it into the last 8 bytes of the parity file. + +This scheme is complex to implement (see the RaidFileRead.cpp!) but minimises the disc spaced wasted. + diff --git a/docs/api-notes/win32_build_on_cygwin_using_mingw.txt b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt new file mode 100644 index 00000000..5b413cd3 --- /dev/null +++ b/docs/api-notes/win32_build_on_cygwin_using_mingw.txt @@ -0,0 +1,157 @@ +How to build Box Backup on Win32 using Cygwin and MinGW +By Chris Wilson, 2009-03-31 + +(To read this document online with better formatting, browse to: +[http://www.boxbackup.org/trac/wiki/CompileWithMinGW]) + +== MinGW C++ == + +Start by installing Cygwin on your Windows machine from [http://www.cygwin.org/cygwin/]. + +Make sure to select the following packages during installation: + + * Archive/unzip + * Devel/autoconf + * Devel/automake + * Devel/make + * Devel/mingw64-x86_64-gcc + * Devel/mingw64-x86_64-gcc-g++ + * Devel/mingw64-x86_64-zlib + * Libs/libxml2 + * Libs/libxslt (for xsltproc) + * 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: + + * 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: + + C:\Cygwin\Home\YourUsername + +Make sure you know the full path to this directory. + +If your user name has spaces in it, which is quite common on Windows, +please rename your home directory to one without any spaces, and change +your user's home directory in /etc/passwd to match. + +== OpenSSL == + +Download OpenSSL from [http://www.openssl.org/source/openssl-1.0.1f.tar.gz] + +Open a Cygwin shell, go to the base directory, and unpack OpenSSL: + + tar xzvf openssl-1.0.1f.tar.gz + +Configure OpenSSL for MinGW compilation, and build and install it: + + cd openssl-1.0.1f + ./Configure --prefix=/usr/x86_64-w64-mingw32 mingw64 --cross-compile-prefix=x86_64-w64-mingw32- + make + 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.34.tar.bz2?download]. + +Open a Cygwin shell, go to the base directory, and unpack, build and +install PCRE: + + tar xjvf pcre-8.34.tar.bz2 + cd pcre-8.34 + ./configure --prefix=/usr/x86_64-w64-mingw32 --disable-shared --host=x86_64-w64-mingw32 + 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/x86_64-w64-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/x86_64-w64-mingw32 \ + --host=x86_64-w64-mingw32 \ + LIBS="-lpdcurses" --with-curses --disable-shared \ + CFLAGS="-DSIGHUP=0 -DSIGQUIT=0 -DS_ISUID=0 -DS_ISGID=0 -DS_IXGRP=0 -DS_IXOTH=0 -DS_IWOTH=0" + 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: + + find -type f -not \( -wholename .*svn*. \) -exec dos2unix {} \; + +== Compile Box Backup == + +Enter the source directory and configure like this: + + cd trunk + ./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'': + + * boxbackup\release\bin\bbackupd\bbackupd.exe + * boxbackup\release\bin\bbackupquery\bbackupquery.exe + * boxbackup\release\bin\bbackupctl\bbackupctl.exe + * openssl\out32dll\libeay32.dll + * 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. + +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/api-notes/win32_build_on_linux_using_mingw.txt b/docs/api-notes/win32_build_on_linux_using_mingw.txt new file mode 100644 index 00000000..2c3e4fe9 --- /dev/null +++ b/docs/api-notes/win32_build_on_linux_using_mingw.txt @@ -0,0 +1,108 @@ +How to build Box Backup for Windows (Native) on Linux using MinGW +By Chris Wilson, 2005-12-07 + +Install the MinGW cross-compiler for Windows: + +- Debian and Ubuntu users can "apt-get install mingw32" +- Fedora and SuSE users can download RPM packages from + [http://mirzam.it.vu.nl/mingw/] + +You will need to know the prefix used by the cross-compiler executables. +It will usually be something like "ix86-mingw32*-". All the binaries in the +cross-compiler package will start with this prefix. The documentation below +assumes that it is "i386-mingw32-". Adjust to taste. + +You will also need to install Wine and the Linux kernel "binary formats" +(binfmt) support, so that you can run Windows executables on Linux, +otherwise the configure scripts will not work properly with a cross-compiler. +On Ubuntu, run: + + apt-get install wine binfmt-support + /etc/init.d/binfmt-support start + +Start by downloading Zlib from [http://www.zlib.net/], unpack and enter +source directory: + + export CC=i386-mingw32-gcc + export AR="i386-mingw32-ar rc" + export RANLIB="i386-mingw32-ranlib" + ./configure + make + make install prefix=/usr/local/i386-mingw32 + +Download OpenSSL 0.9.8b from +[http://www.openssl.org/source/openssl-0.9.8b.tar.gz] + +Unpack and configure: + + tar xzvf openssl-0.9.8b.tar.gz + cd openssl-0.9.8b + ./Configure --prefix=/usr/local/i386-mingw32 mingw + make makefile.one + wget http://www.boxbackup.org/svn/box/chris/win32/support/openssl-0.9.8b-mingw-cross.patch + patch -p1 < openssl-0.9.8b-mingw-cross.patch + make -f makefile.one + make -f makefile.one install + +Download PCRE from +[http://prdownloads.sourceforge.net/pcre/pcre-6.3.tar.bz2?download] + +Unpack: + + tar xjvf pcre-6.3.tar.bz2 + cd pcre-6.3 + +Configure and make: + + export AR=i386-mingw32-ar + ./configure --host=i386-mingw32 --prefix=/usr/local/i386-mingw32/ + make winshared + +If you get this error: + + ./dftables.exe pcre_chartables.c + /bin/bash: ./dftables.exe: cannot execute binary file + make: *** [pcre_chartables.c] Error 126 + +then run: + + wine ./dftables.exe pcre_chartables.c + make winshared + +to complete the build. Finally: + + cp .libs/libpcre.a /usr/local/i386-pc-mingw32/lib + cp .libs/libpcreposix.a /usr/local/i386-pc-mingw32/lib + cp pcreposix.h /usr/local/i386-pc-mingw32/include + +You will need to find a copy of mingwm10.dll that matches your cross-compiler. +Most MinGW distributions should come with it. On Debian and Ubuntu, for some +bizarre reason, you'll find it compressed as +/usr/share/doc/mingw32-runtime/mingwm10.dll.gz, in which case you'll +have to un-gzip it with "gzip -d". Copy it to a known location, e.g. +/usr/local/i386-mingw32/bin. + +Download and extract Box Backup, and change into the base directory, +e.g. boxbackup-0.11rc2. Change the path to mingwm10.dll in parcels.txt to +match where you found or installed it. + +Now configure Box with: + + ./configure --host=i386-mingw32 \ + CXXFLAGS="-mthreads -I/usr/local/i386-mingw32/include" \ + LDFLAGS=" -mthreads -L/usr/local/i386-mingw32/lib" \ + LIBS="-lcrypto -lws2_32 -lgdi32" + make + +or, if that fails, try this: + + export CXX="i386-mingw32-g++" + export AR=i386-mingw32-ar + export RANLIB=i386-mingw32-ranlib + export CFLAGS="-mthreads" + export CXXFLAGS="-mthreads" + export LDFLAGS="-mthreads" + export LIBS="-lcrypto -lws2_32 -lgdi32" + (if you don't have a "configure" file, run "./bootstrap") + ./configure --target=i386-mingw32 + make CXX="$CXX" AR="$AR" RANLIB="$RANLIB" WINDRES="i386-mingw32-windres" diff --git a/docs/api-notes/windows_porting.txt b/docs/api-notes/windows_porting.txt new file mode 100644 index 00000000..ada3b857 --- /dev/null +++ b/docs/api-notes/windows_porting.txt @@ -0,0 +1,100 @@ +TITLE Notes on porting the backup client to Windows + +It should be relatively easy to port the backup client (bbackupd) to Windows. However, the server relies on unlink() behaviour of the UNIX filesystem which is different on the Windows platform, so it will not be so easy to port. + +An installation of perl is required to build the system. The ActiveState port is the easiest to install. + + +SUBTITLE Build environment + +The build environment generation script, makebuildenv.pl, uses perl to scan all the files and generate makefiles. It's rather orientated towards UNIX systems with gcc. + +Probably the easiest way to handle this is to detect the Windows platform, set up a few variables appropriately (in BoxPlatform.pm) and then post-process the generated Makefiles to mould them into something more handy for the MS Windows compiler and toolchain. + +The script itself is a bit messy. It was fine at first, but then the multi-platform thing got in the way. I do intend to rewrite it at some point in the future. + +Make sure your new version defines PLATFORM_WIN32 on the compile line. + +All files #include "Box.h" as the first include file. Use this for pre-compiled headers. Edit BoxPlatform.h to include the Windows headers required, and include a PLATFORM_WIN32 section. The easiest start will be to leave this blank, apart from typedefs for the basic types and any "not supported" #defines you can find. + +Boring bits of the code, such as exceptions and protocol definitions, are autogenerated using perl scripts. This code should be portable without modification. + +I have tried to avoid the things I know won't work with the MS compiler, so hopefully the code will be fairly clean. However, it might be a little easier to use the MinGW compiler [ http://www.mingw.org/ ] just to be consistent with the UNIX version. But I hope this won't be necessary. + +You'll need the latest version of OpenSSL. This was slightly difficult to get to compile last time I tried -- especially if you're determined to use the optimised assembler version. The main difficulty was getting a version which would link properly with the options used in my project, the default libraries selected got in the way. + + +SUBTITLE Porting as UNIX emulation + +Since the daemon uses so few UNIX system calls and with a limited set of options, it seems to make sense to port it by writing emulations of these functions. It's probably nicest to create a lib/win32 directory, and populate this with header files corresponding to the UNIX header files used. These just contain inline functions which map the UNIX calls to Win32 calls. + +File/socket handles may have to be translated. -1 is used as a failure return value and by the code internally to mark an invalid socket handle. (0 is a valid socket handle) + +Of course, some bits of code aren't relevant, so will just be #ifdefed out, or replaced. But this should be minimal. (Only perhaps the small bit relating to filesystem structure -- there aren't really mount points as such.) + + +SUBTITLE File tracking + +The daemon uses the inode number of a file to keep track of files and directories, so when they're renamed they can be moved efficiently on the store. Some unique (per filesystem) number will have to be found and used instead. + +It uses the Berkeley DB to store these on disc. It's likely another storage system will need to be used. (It just has to map the file's unique number into to a 8 byte struct.) + +There is a in-memory implementation for platforms which don't support Berkeley DB, but this isn't so good when the daemon has to be restarted as all the tracking is lost. But it's an easy start. + + +SUBTITLE Expected filesystem behaviour + +File and directories have (at least) two modification times, for contents and attributes. + +For files, the contents modification time must change when the contents change, and the attributes time when the attributes change (and may change when the contents change too.) + +For directories, the contents modification time must change when files or directories are deleted or added. If it changes any more frequently than this, then the client will be slightly less efficient -- it will download the store's directory listing whenever this time changes. The attributes modification time is less important, as the actual attributes are compared and only uploaded if different. + + +SUBTITLE Attributes + +Attributes means file modification times, flags, and filesystem permissions. + +The BackupClientFileAttribute class will need to be extended. Allocate another "attribute type" for the Win32 attributes, and then serialise it in a compatible way -- put your new attribute type in the header, and then a serialised network byte order structure in the rest. The different size of block is handled for you, and the server never looks inside. + +Add code so that under UNIX, Win32 attributes are ignored, and UNIX attributes under Win32. + +It's probably not necessary to worry too much about these for the first version. Not many people seem to use these attributes anyway. + + +SUBTITLE Times + +The system uses it's own 64 bit time type -- see BoxTime.h. Everything is translated to this from the various different system time types, and calculated and stored internally in this form. + + +SUBTITLE Daemon as a Service + +The client is derived from the Daemon class, which implements a daemon. The interface is simple, and it shouldn't be hard to write a compatible class which implements a Windows Service instead. + +Or cheat and run it as a Win32 application. + +Note that the daemon expects to be able to read every file it wants, and will abort a scan and upload run if it gets an error. The daemon must therefore be run with sufficient privileges. It runs as root under UNIX. + + +SUBTITLE Command Socket + +The backup daemon accepts commands from bbackupctl through a UNIX domain socket. When a connection is made, the user ID of the connecting process is checked to see if it's the same user ID as the daemon is running under. + +This may not have any exact analogue under Win32, so another communications scheme may have to be devised. + +This is only actually necessary if the client is to be run in snapshot mode. It can be safely left unimplemented if snapshot mode is not required, or the prompts for it to sync with the server are implemented some other way. + + +SUBTITLE NTFS streams + +If you want to back up NTFS streams, then a generic solution should probably be defined, so that the Mac OS X resource forks can be backed up with the same mechanism. + + +SUBTITLE Source code + +I work on a slightly different version of the source files. A make distribution script adds the license header and removes private sections of code. This means submitted diffs need a slight bit of translation. + + + + + diff --git a/docs/docbook/adminguide.xml b/docs/docbook/adminguide.xml new file mode 100644 index 00000000..edb0a58c --- /dev/null +++ b/docs/docbook/adminguide.xml @@ -0,0 +1,1981 @@ + + +]> + + Box Backup administrator's guide + + + License + + Copyright © 2003 - 2007, Ben Summers and contributors. All rights + reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + + + All use of this software and associated advertising materials + must display the following acknowledgement: This product includes + software developed by Ben Summers and contributors. + + + + The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + + + [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. + + + + Configuration + +
    + System configuration + +
    + Server + + After you've downloaded and compiled the programs you need to + install the programs on your server. As root do the following: + + make install-backup-server + + This assumes that you are installing on the same server that you + compiled the software on. If not, copy the + boxbackup-x.xx-backup-server-OSNAME.tgz file to the server you want to + run on, and install there. For example (on Mac OS X): + + tar zxvf boxbackup-0.10-server-darwin8.5.0.tgz +cd boxbackup-0.10-server-darwin8.5.0 +./install-backup-server + + Then create the user for the backup daemon on the server: + + useradd _bbstored + + Box Backup has a built-in software RAID facility (redundant + array of inexpensive disks) for the backup store. This allows you to + spread the store data over three disks, and recover from the loss of + any one disk without losing data. However, this is now deprecated, and + you are recommended to use the software or hardware RAID facilities of + your operating system instead. Use the following command if you want + to create a simple server without Box Backup RAID: + + mkdir /tmp/boxbackupRepository # Create the directory +chown _bbstored /tmp/boxbackupRepository/ # Change the owner to the new boxbackup daemon user + +/usr/local/sbin/raidfile-config /etc/box/ 1024 /tmp/boxbackupRepository + +#substitute 1024 with the desired blocksize +#substitute /tmp/boxbackupRepository with a directory that exists where you want the backup store located +#/usr/local/sbin/raidfile-config --help shows you the options + + Then create the configuration file /etc/box/bbstored.conf The + hostname is tricky as it is used for two things: The name of the + server in the certificate and the address the server is listening on. + Since you might be using NAT, might move the server around or the + domain name might change, choose a name that describes the server. + When the network address of the server changes, you need to update the + ListenAddresses directive in the + /etc/box/bbstored.conf file. + + /usr/local/sbin/bbstored-config /etc/box hostname _bbstored + + This last step outputs 5 instructions that you must execute to + the letter. A lot of questions are raised on the mailing list because + these steps have not been followed properly. + + TODO: Expand on this. Explain the 5 steps in detail. + + If you want to run the server as a non-root user, look here. +
    + +
    + Certificate Management + + There are two steps involved to create an account. You need to + create the account on the server, and sign a certificate to give the + client permission to connect to the server. + + Running a Certification Authority for TLS (SSL) connections is + not trivial. However, a script to does most of the work in a way which + should be good enough for most deployments. + + + The certificate authority directory is intended to be stored + on another server. It should not be kept on the backup server, in + order to limit the impact of a server compromise. The instructions + and the script assume that it will be kept elsewhere, so will ask + you to copy files to and from the CA. + + + + SSL certificates contain validity dates, including a "valid + from" time. If the clock on the machine which signs the certificates + is not syncronised to the clocks of the machines using these + certificates, you will probably get strange errors until the start + time is reached on all machines. If you get strange errors when + attempting to use new certificates, check the clocks on all machines + (client, store and CA). You will probably just need to wait a while + until the certificates become valid, rather than having to + regenerate them. + + +
    + Set up a Certificate Authority + + It is recommended that you keep your Certificate Authority on + a separate machine than either the client or the server, preferably + without direct network access. The contents of this directory + control who can access your backup store server. + + To setup the basic key structure, do the following: + + /usr/local/sbin/bbstored-certs ca init + + (See OpenSSL notes if you + get an OpenSSL error) + + This creates the directory called ca in + the current directory, and initialises it with basic keys. +
    + +
    + Sign a server certificate + + When you use the bbstored-config script to + set up a config file for a server, it will generate a certificate + request (CSR) for you. Transfer it to the machine with your CA, then + do: + + /usr/local/sbin/bbstored-certs ca sign-server hostname-csr.pem + + This signs the certificate for the server. Follow the + instructions in the output on which files to install on the server. + The CSR file is now no longer needed. Make sure you run this command + from the directory above the directory 'ca'. + + TODO: Explain instructions in output. +
    + +
    + Set up an account + + Choose an account number for the user. This must be unique on + the server, and is presented as a 31 bit number in hex greater than + 0, for example, 1 or 75AB23C. Then on the backup store server, + create the account with: + + /usr/local/sbin/bbstoreaccounts create 75AB23C 0 4096M 4505M + + This looks complicated. The numbers are, in order: + + + + The account number allocated (hex) + + + + The RAID disc set (0 if you use raidfile-config and don't + add a new set) + + + + Soft limit (size) + + + + Hard limit (size) + + + + The sizes are are specified in Mb, Gb, or blocks, depending on + the suffix. 1M specifies 1 Mb, 1G specifies 1 Gb, and 1B specifies 1 + block, the size of which depends on how you have configured the + raidfile system with raidfile-config. + + In this example, I have allocated 4Gb (assuming you use 2048 + byte blocks as per my example) as the soft limit, and 4Gb + 10% as + the hard limit. + + NOTE The sizes specified here are pre-RAID. So if you are + using userland RAID, you are actually allocating two-thirds of this + amount. This means that, when you take compression into account, + that if you allocate 2Gb on the server, it'll probably hold about + 2Gb of backed up files (depending on the compressability of those + files). + + The backup client will (voluntarily) try not to upload more + data than is allowed by the soft limit. The store server will refuse + to accept a file if it would take it over the hard limit, and when + doing housekeeping for this account, try and delete old versions and + deleted files to reduce the space taken to below the soft + limit. + + This command will create some files on disc in the raid file + directories (if you run as root, the utility will change to the user + specified in the bbstored.conf file to write them) and update the + accounts file. A server restart is not required. + + NOTE If you get a message saying 'Exception: RaidFile (2/8)', + the directories you specified in the raidfile.conf are not writable + by the _bbstored user -- fix it, and try again. + + Finally, tell the user their account number, and the hostname + of your server. They will use this to set up the backup client, and + send you a CSR. This has the account number embedded in it, and you + should be sure that it has the right account number in it. + + Sign this CSR with this command: + + /usr/local/sbin/bbstored-certs ca sign 75AB23C-csr.pem + + Don't forget to check that the embedded account number is + correct! Then send the two files back to the user, as instructed by + the script. + + Please read the Troubleshooting page if you have + problems. + + TODO: Link to troubleshooting... +
    +
    + +
    + Log Files + + You may wish to see what's going on with the server. Edit + /etc/syslog.conf, and add: + + local6.info /var/log/box +local5.info /var/log/raidfile + + Note: Separators must be tabs, + otherwise these entries will be ignored. + + touch /var/log/box +touch /var/log/raidfile + + Set up log rotation for these new log files. For example, if you + have /etc/newsyslog.conf, add the following lines + to it: + + /var/log/box 644 7 2000 * Z +/var/log/raidfile 644 7 2000 * Z + + If you have /etc/logrotate.d, create a new + file in there (for example + /etc/logrotate.d/boxbackup) containing the + following: + + /var/log/box /var/log/raidfile { + weekly + create + compress + rotate 52 +} + + Then restart syslogd, for example: + + /etc/init.d/syslogd restart +
    + +
    + Configuring a client + + Before you can do any configuration, you need to know the + hostname of the server you will be using, and your account number on + that server. + + Later in the process, you will need to send a certificate + request to the administrator of that server for it to be + signed. + + Installation is covered in the compiling and installing section. + You only need the backup-client parcel. + + It is important that you read all the output of the config + scripts. See the end of this page for an example. + + The backup client has to be run as root, because it needs to + read all your files to back them up, although it is possible to back + up a single user's files by running it as that user. (Tip: specify a + directory other than /etc/box, and then give the + alternate config file as the first argument to + bbackupd). However, it will fall over if you don't + give yourself read access to one of your files. + +
    + Basic configuration + + Run the bbackupd-config script to generate + the configuration files and generate a private key and certificate + request. + + /usr/local/sbin/bbackupd-config /etc/box lazy 999 hostname /var/bbackupd /home + + (See OpenSSL notes if you + get an OpenSSL error) + + The items in bold need to be changed. In order, they are the + account number, the hostname of the server you're using, and + finally, the directories you want backed up. You can include as many + you want here. + + However, the directories you specify must not contain other + mounted file systems within them at any depth. Specify them + separately, one per mount point. No checks are currently made to + catch bad configuration of this nature! + + You may also want to consider changing the mode from lazy to + snapshot, depending on what your system is used for: + + + + Lazy Mode + + + This mode regularly scans the files, with only a rough + schedule. It uploads files as and when they are changed, if + the latest version is more than a set age. This is good for + backing up user's documents stored on a server, and spreads + the load out over the day. + + + + + Snapshot Mode + + + This mode emulates the traditional backup behaviour of + taking a snapshot of the filesystem. The backup daemon does + absolutely nothing until it is instructed to make a backup + using the bbackupctl utility (probably as a cron job), at + which point it uploads all files which have been changed since + the last time it uploaded. + + + + + When you run the config script, it will tell you what you need + to do next. Don't forget to read all the output. An example is shown + at the end of this page, but the instructions for your installation + may be different. +
    + +
    + Certificates + + After you have sent your certificate request off to the server + administrator and received your certificate and CA root back, + install them where instructed by the bbackupd-config script during + basic bbackupd configuration. + + You can then run the daemon (as root) by running + /usr/local/sbin/bbackupd, and of course, adding it + to your system's startup scripts. The first time it's run it will + upload everything. Interrupting it and restarting it will only + upload files which were not uploaded before - it's very + tolerant. + + If you run in snapshot mode, you will need to add a cron job + to schedule backups. The config script will tell you the exact + command to use for your system. + + Please read the Troubleshooting page if you have + problems. + + Remember to make a traditional backup of the keys file, as + instructed. You cannot restore files without it. + + It is recommended that you backup up all of /etc/box as it + will make things easier if you need to restore files. But only the + keys are absolutely essential. + + If you want to see what it's doing in more detail (probably a + good idea), follow the instructions in the server setup to create + new log files with syslog. +
    + +
    + Adding and removing backed up locations + + By editing the /etc/box/bbackupd.conf file, you can add and + remove directories to back up - see comments in this file for help. + Send bbackupd a HUP signal after you modify it. + + When you remove a location, it will not be marked as deleted + immediately. Instead, bbackupd waits about two days before doing so, + just in case you change your mind. After this, it will be eventually + removed from the store by the housekeeping process. Run as + root. + + The backup client is designed to be run as root. It is + possible to run without root, but this is not recommended. Clock + synchronisation for file servers. + + If you are using the backup client to backup a filesystem + served from a fileserver, you should ideally ensure that the + fileserver clocks are synchronised with the fileserver. + + bbackupd will cope perfectly well if the clocks are not + synchronised. Errors up to about half an hour cause no problems. + Larger discrepancies cause a loss of efficiency and the potential to + back up a file during a write process. + + There is a configuration parameter MaxFileTimeInFuture, which + specifies how far in the future a file must be for it to be uploaded + as soon as it is seen. You should not need to adjust this (default + is 2 days). Instead, get those clocks synchronised. Excluding files + and directories from the backup. + + Within the bbackupd.conf file, there is a section named + BackupLocations which specifies which locations on disc should be + backed up. It has subsections, each of which 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. + + 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. +
    + +
    + Example configuration output + + This is an example of output from the bbstored-config + script. + + + Follow the instructions output by your script, not the ones + here -- they may be different for your system. + + + /usr/local/sbin/bbackupd-config /etc/box lazy 51 server.example.com /var/bbackupd /home /etc/samba + +Setup bbackupd config utility. + +Configuration: + Writing configuration file: /etc/box/bbackupd.conf + Account: 51 + Server hostname: server.example.com + Directories to back up: + /home + /etc/samba + +Note: If other file systems are mounted inside these directories, then problems may occur +with files on the store server being renamed incorrectly. This will cause efficiency +problems, but not affect the integrity of the backups. + +WARNING: Directories not checked against mountpoints. Check mounted filesystems manually. + +Creating /etc/box... +Creating /etc/box/bbackupd +Generating private key... + [OpenSSL output omitted] + +Generating keys for file backup +Writing notify script /etc/box/bbackupd/NotifyStoreFull.sh +Writing configuration file /etc/box/bbackupd.conf + +=================================================================== + +bbackupd basic configuration complete. + +What you need to do now... + +1) Make a backup of /etc/box/bbackupd/51-FileEncKeys.raw + This should be a secure offsite backup. + Without it, you cannot restore backups. Everything else can + be replaced. But this cannot. + KEEP IT IN A SAFE PLACE, OTHERWISE YOUR BACKUPS ARE USELESS. + +2) Send /etc/box/bbackupd/51-csr.pem + to the administrator of the backup server, and ask for it to + be signed. + +3) The administrator will send you two files. Install them as + /etc/box/bbackupd/51-cert.pem + /etc/box/bbackupd/serverCA.pem + after checking their authenticity. + +4) You may wish to read the configuration file + /etc/box/bbackupd.conf + and adjust as appropraite. + + There are some notes in it on excluding files you do not + wish to be backed up. + +5) Review the script + /etc/box/bbackupd/NotifyStoreFull.sh + and check that it will email the right person when the store + becomes full. This is important -- when the store is full, no + more files will be backed up. You want to know about this. + +6) Start the backup daemon with the command + /usr/local/sbin/bbackupd + in /etc/rc.local, or your local equivalent. + Note that bbackupd must run as root. + +=================================================================== + + Remember to make a secure, offsite backup of your backup keys, + as described in Basic + configuration above. If you do not, and that key is lost, you + have no backups. +
    +
    + +
    + Configuration Options + + Box Backup has many options in its configuration file. We will + try to list them all here. + + First of all, here is an example configuration file, for + reference: + + + Example Configuration File + + StoreHostname = localhost +AccountNumber = 0x2 + +KeysFile = /etc/box/2-FileEncKeys.raw +CertificateFile = /etc/box/2-cert.pem +PrivateKeyFile = /etc/box/2-key.pem +TrustedCAsFile = /etc/box/serverCA.pem +DataDirectory = /var/run/boxbackup +NotifyScript = /etc/box/NotifySysadmin.sh +CommandSocket = /var/run/box/bbackupd.sock + +UpdateStoreInterval = 86400 +MinimumFileAge = 3600 +MaxUploadWait = 7200 +FileTrackingSizeThreshold = 65536 +DiffingUploadSizeThreshold = 65536 +MaximumDiffingTime = 20 +ExtendedLogging = no +LogAllFileAccess = yes + +Server +{ + PidFile = /var/run/bbackupd.pid +} +BackupLocations +{ + etc + { + Path = /etc + } + home + { + Path = /home + ExcludeDir = /home/shared + ExcludeDir = /home/chris/.ccache + ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache + } +} + + + As you can see from the example above, the configuration file + has a number of subsections, enclosed in curly braces {}. Some options + appear outside of any subsection, and we will refer to these as root options. The available options in + each section are described below. + + Every option has the form name = value. Names are + not case-sensitive, but values are. Depending on the option, the value + may be: + + + + a path (to a file or directory); + + + + a number (usually in seconds or bytes); + + + + a boolean (the word Yes or No); + + + + a hostname (or IP address). + + + + Paths are specified in native format, i.e. a full Windows path + with drive letter on Windows clients, or a full Unix path on Unix + clients. + + + Example: + + StoreObjectInfoFile = + /var/state/boxbackup/bbackupd.dat + + StoreObjectInfoFile = C:\Program Files\Box + Backup\data\bbackupd.dat + The use of relative paths (which do not start with a + forward slash on Unix, or a drive specification on Windows) is + possible but not recommended, since they are interpreted relative to + the current working directory when bbackupd was started, which is + liable to change unexpectedly over time. + + Numbers which start with "0x" are interpreted as hexadecimal. + Numbers which do not start with "0x" are interpreted as + decimal. + +
    + Root Options + + These options appear outside of any subsection. By convention + they are at the beginning of the configuration file. + + Some options are required, and some are optional. + + + + StoreHostname (required) + + + The Internet host name (DNS name) or IP address of the + server. This is only used to connect to the server. + + + + + AccountNumber (required) + + + The number of the client's account on the server. This + must be provided by the server operator, and must match the + account number in the client's certificate, otherwise the + client will not be able to log into the server. + + The account number may be specified in hexadecimal + (starting with 0x, as in the example above) or in decimal, but + since the server operator works in hexadecimal, that format is + highly recommended and is the default. + + + + + KeysFile (required) + + + The path to the file containing the encryption key used + for data encryption of client file data and filenames. This is + the most important file to keep safe, since without it your + backups cannot be decrypted and are useless. Likewise, if an + attacker gets access to this key and to your encrypted + backups, he can decrypt them and read all your data. + + Do not change the encryption key without deleting all + files from the account on the server first. None of your old + files on the store will be readable if you do so, and if you + change it back, none of the files uploaded with the new key + will be readable. + + + + + CertificateFile (required) + + + The path to the OpenSSL client certificate in PEM + format. This is supplied by the server operator in response to + the certificate request which you send to them. Together with + the PrivateKeyFile, this provides access to the store server + and the encrypted data stored there. + + It is not critical to protect this file or to back it up + safely, since it can be regenerated by creating a new + certificate request, and asking the server operator to sign + it. You may wish to back it up, together with the + PrivateKeyFile, to avoid this inconvenience if you lose all + your data and need quick access to your backups. + + If you do back them up, you should keep them in a + separate location to the KeysFile, since any person holding + the KeysFile and the PrivateKeyFile can gain access to your + encrypted data and decrypt it. + + + + + PrivateKeyFile (required) + + + The path to the OpenSSL private key in PEM format. This + is generated at the same time as the certificate request, but + there is no need to send it to the server operator, and you + should not do so, in case the communication is intercepted by + an attacker. Together with the CertificateFile, this provides + access to the store server and the encrypted data stored + there. + + See the notes under CertificateFile for information + about backing up this file. + + + + + TrustedCAsFile (required) + + + The path to the OpenSSL certificate of the Client + Certificate Authority (CCA), in PEM format. This is supplied + by the server operator along with your account details, or + along with your signed client certificate. This is used to + verify that the server which you are connecting to is + authorised by the person who signed your certificate. It + protects you against DNS and ARP poisoning and IP spoofing + attacks. + + + + + DataDirectory (required) + + + The path to a directory where bbackupd will keep local + state information. This consists of timestamp files which + identify the last backup start and end times, used by + bbackupquery to determine whether files + have changed, and optionally a database of inode numbers, + which are used to check for files being renamed. The database + is only saved if Box Backup is built with Berkeley Database + (BDB) support. + + + + + NotifyScript (optional) + + + The path to the script or command to run when the Box + Backup client detects an error during the backup process. This + is normally used to notify the client system administrator by + e-mail when a backup fails for any reason. + + The script or command is called with one of the + following additional arguments to identify the cause of the + problem: + + + + store-full + + + The backup store is full. No new files are being + uploaded. If some files are marked as deleted, they + should be removed in due course by the server's + housekeeping process. Otherwise, you need to remove some + files from your backup set, or ask the store operator + for more space. + + + + + read-error + + + One or more files which were supposed to be backed + up could not be read. This could be due to: + + running the server as a non-root user; + + + + backing up a mounted filesystem such as + NFS; + + + + access control lists being applied to some + files; + + + + SELinux being enabled; + + + + trying to back up open files under + Windows; + + + + strange directory permissions such as 0000 or + 0400. + + Check the client logs, e.g. + /var/log/bbackupd on Unix, or the Windows Event Viewer + in Control Panel > Administrative Tools, for more + information about which files are not being backed up + and why. + + + + + backup-error + + + There was a communications error with the server, + or an unexpected exception was encountered during a + backup run. Check the client logs, e.g. + /var/log/box on Unix, or the + Windows Event Viewer in Control Panel > + Administrative Tools, for more information about the + problem. + + You may wish to check your Internet access to the + server, check that the server is running, and ask your + server operator to check your account on the + server. + + + + + + + + CommandSocket (optional) + + + The path to the Unix socket which + bbackupd creates when running, and which + bbackupctl uses to communicate with it, for + example to force a sync or a configuration reload. If this + option is omitted, no socket will be created, and + bbackupctl will not function. + + Unix sockets appear within the filesystem on Unix, as a + special type of file, and must be created in a directory which + exists and to which bbackupd has write access, and bbackupctl + has read access. + + On Windows, the path is ignored, and a named + pipe is created instead. This does not currently + have any security attached, so it can be accessed by any user. + Unlike a Unix socket it can also be accessed remotely. Please + use this option with extreme caution on Windows, and only on + fully trusted networks. + + + + + AutomaticBackup (optional) + + + Enable or disable the client from connecting + automatically to the store every + UpdateStoreInterval seconds. When + enabled (set to Yes), the client is in + Lazy Mode. When disabled (set to + No), it is in Snapshot + Mode. This setting is optional, and the default + value is Yes. + + + + + UpdateStoreInterval (required) + + + The approximate time between successive connections to + the server, in seconds, when the client is in Lazy + Mode. The actual time is randomised slightly to + prevent "rush hour" traffic jams on the server, where many + clients try to connect at the same time. + + This value is ignored if the client is in + Snapshot Mode. However, it is still + required. It can be set to zero in this case. + + You will probably need to experiment with the value of + this option. A good value to start with is probably 86400 + seconds, which is one day. + + + + + MinimumFileAge (required) + + + The number of seconds since a file was last modified + before it will be backed up. The reason for this is to avoid + repeatedly backing up files which are repeatedly changing. A + good value is about 3600 seconds (one hour). If set to zero, + files which have changed will always be backed up on the next + backup run. + + The MaxUploadWait option + overrides this option in some circumstances. + + + + + MaxUploadWait (required) + + + The number of seconds since a file was last uploaded + before it will be uploaded again, even if it keeps changing. + The reason for this is to ensure that files which are + continuously modified are eventually uploaded anyway. This + should be no less than the value of + MinimumFileAge. A good value is about + 14400 seconds (4 hours). + + + + + MaxFileTimeInFuture (optional) + + + The maximum time that a file's timestamp can be in the + future, before it will be backed up anyway. Due to clock + synchronisation problems, it is inevitable that you will + occasionally see files timestamped in the future. Normally, + for files which are dated only slightly in the future, you + will want to wait until after the file's date before backing + it up. However, for files whose dates are very wrong (more + than a few hours) you will normally prefer to back them up + immediately. + + A good value is about 7200 seconds (2 hours) to cope + with potential problems when moving in and out of daylight + saving time, if applicable in your timezone. The default + value, if this setting is not provided, is 172800 seconds (2 + days). + + + + + FileTrackingSizeThreshold (required) + + + The minimum size of files which will be tracked by inode + number to detect renames. It is not worth detecting renames of + small files, since they are quick to upload again in full, and + keeping their inode numbers in memory increases the client's + memory usage and slows down searches. Larger files should be + tracked to avoid wasting space on the store and long + uploads. + + A good value is about 65536 bytes (64 kilobytes). + + + + + DiffingUploadSizeThreshold (required) + + + The minimum size of files which will be compared to the + old file on the server, and for which only changes will be + uploaded. It is not worth comparing small files, since they + are quick to upload again in full, and sending the entire file + reduces the risk of data loss if the store is accidentally + corrupted. Larger files should have only their differences + uploaded to avoid wasting space on the store and long + uploads. + + A good value is about 65536 bytes (64 kilobytes). + + + + + MaximumDiffingTime (optional) + + + The maximum time for which the client will attempt to + find differences between the current version and the old + version in the store, before giving up and uploading the + entire file again. Very large files (several gigabytes) may + take a very long time to scan for changes, but would also take + a very long time to upload again and use a lot of space on the + store, so it is normally worth omitting this value. + + Use this option only if, for some bizarre reason, you + prefer to upload really large files in full rather than spend + a long time scanning them for changes. + + + + + KeepAliveTime (optional) + + + The interval (in seconds) between sending Keep-Alive + messages to the server while performing long operations such + as finding differences in large files, or scanning large + directories. + + These messages ensure that the SSL connection is not + closed by the server, or an intervening firewall, due to lack + of activity. + + The server will normally wait up to 15 minutes (900 + seconds) before disconnecting the client, so the value should + be given and should be less than 900. Some firewalls may time + out inactive connections after 10 or 5 minutes. + + A good value is 300 seconds (5 minutes). You may need to + reduce this if you frequently see TLSReadFailed or + TLSWriteFailed errors on the client. + + + + + StoreObjectInfoFile (optional) + + + Enables the use of a state file, which stores the + client's internal state when the client is not running. This + is useful on clients machines which are frequently shut down, + for example desktop and laptop computers, because it removes + the need for the client to recontact the store and rescan all + directories on the first backup run, which may take some time. + This feature is somewhat experimental and not well tested. + + + This is option is disabled by default, in which case the + state is stored in memory only. The value is the path to the + state file. + + + + + ExtendedLogging (optional) + + + Enables the connection debugging mode of the client, + which writes all commands sent to or received from the server + to the system logs. This generates a lot + of output, so it should only be used when instructed, or when + you suspect a connection problem or client-server protocol + error (and you know how to interpret the output). + + This is a boolean value, which may be set to + Yes or No. The default is of + course No. + + + + + ExtendedLogFile (optional, new in 0.11) + + + Enables the same debugging output as + ExtendedLogging, but written to a file + instead of the system logs. This is useful if you need + extended logging, but do not have access to the system logs, + for example if you are not the administrator of the + computer. + + The value is the path to the file where these logs will + be written. If omitted, extended logs will not be written to a + file. This is entirely independent of the + ExtendedLogging option. It does not + make much sense to use both at the same time. + + + + + LogAllFileAccess (optional, new in 0.11) + + + Enables logging of all local file and directory access, + file uploads (full and differential), and excluded files. This + may be useful if the client is failing to upload a particular + file, or crashing while trying to upload it. The logs will be + sent to the system log or Windows Event Viewer. + + This generates a lot + of output, so it should only be used when instructed, or when + you suspect that bbackupd is skipping some files and want to + know why. Because it is verbose, the messages are hidden by + default even if the option is enabled. To see them, you must + run bbackupd with at least one -v option. + + This is a boolean value, which may be set to + Yes or No. The default is of + course No. + + + + + SyncAllowScript (optional) + + + The path to the script or command to run when the client + is about to start an automatic backup run, and wishes to know + whether it is safe to do so. This is useful for clients which + do not always have access to the server, for example laptops + and computers on dial-up Internet connections. + + The script should either output the word + now if the backup should proceed, or else a + number, in seconds, which indicates how long the client should + wait before trying to connect again. Any other output will + result in an error on the client, and the backup will not + run. + + This value is optional, and by default no such script is + used. + + + +
    + +
    + Server Section + + These options appear within the Server subsection, which is at + the root level. + + + + PidFile + + + This option enables the client to write its processs + identifier (PID) to the specified file after starting. The + file will be deleted when the client daemon exits for any + reason. This is disabled by default, but is recommended + whenever you run the client daemon as a daemon (in the + background), which is usually the case. This file can be used + by scripts to determine whether the daemon is still running, + and to send it messages to reload its configuration or to + terminate. + + + Example Server Section + + Server +{ + PidFile = /var/state/boxbackup/bbackupd.pid +} + + + + +
    + +
    + Backup Locations Section + + This section serves only as a container for all defined backup + locations. + + + Example Backup Locations Section + + BackupLocations +{ + etc + { + Path = /etc + } + home + { + Path = /home + ExcludeDir = /home/shared + ExcludeDir = /home/chris/.ccache + ExcludeDir = /home/chris/.mozilla/firefox/vvvkq3vp.default/Cache + } +} + + + Each subsection is a backup location. The name of the + subsection is the name that will be used on the server. The root + directory of the account on the server contains one subdirectory per + location. The name should be simple, not containing any spaces or + special characters. + + If you do not define any locations, the client will not back + up any files! + + It is currently not recommended to back up the root directory + of the filesystem on Unix. Box Backup is designed to back up + important data and configuration files, not full systems. + Nevertheless, nothing prevents you from doing so if you + desire. + + On Windows, it is currently not possible to back up files + which are open (currently in use), such as open documents in + Microsoft Office, and system files such as the registry and the + paging file. You will get an error for each open file which the + client attempts to back up. Once the file has been closed, it will + be backed up normally. System files will always be open, and should + be excluded from your backups. +
    +
    +
    +
    + + + Administration + + This chapter deals with the dauily running and management of the Box + Backup system. It explains most day-to-day tasks. + +
    + Regular Maintenance + + The steps involved in maintaining and keeping the backup sets + healthy are outlined in this section. + +
    + Controlling a backup client + + The bbackupctl program sends control commands to the bbackupd + daemon. It must be run as the same user as the daemon, and there is no + exception for root. + + The command line syntax is: + + /usr/local/sbin/bbackupctl [-q] [-c config-file] command + + The -q option reduces the amount of output the program emits, + and -c allows an alternative configuration file to be + specified. + + Valid commands are: + + + + terminate + + Stop the bbackupd daemon now (equivalent to kill) + + + + reload + + Reload the configuration file (equivalent to kill + -HUP) + + + + sync + + Connect to the server and synchronise files now + + + + bbackupctl communicates with + the server via a UNIX domain socket, specified in bbackupd.conf with + the CommandSocket directive. This does not need to be specified, and + bbackupd will run without the command + socket, but in this case bbackupctl will not be able to communicate + with the daemon. + + Some platforms cannot check the user id of the connecting + process, so this command socket becomes a denial of service security + risk. bbackupd will warn you when it + starts up if this is the case on your platform, and you should + consider removing the CommandSocket directive on these + platforms. +
    + +
    + Using bbackupctl to perform snapshots + + bbackupctl's main purpose is to + implement snapshot based backups, emulating the behaviour of + traditional backup software. + + Use bbackupd-config to write a configuration file in snapshot + mode, and then run the following command as a cron job. + + /usr/local/sbin/bbackupctl -q sync + + This will cause the backup daemon to upload all changed files + immediately. bbackupctl will exit + almost immediately, and will not output anything unless there is an + error. +
    + +
    + Checking storage space used on the server + +
    + From the client machine + + bbackupquery can tell you how much space is used on the server + for this account. Either use the usage command in interactive mode, + or type: + + /usr/local/sbin/bbackupquery -q usage quit + + to show the space used as a single command. +
    + +
    + On the server + + bbstoreaccounts allows you to query the space used, and change + the limits. To display the space used on the server for an account, + use: + + /usr/local/sbin/bbstoreaccounts info 75AB23C + + To adjust the soft and hard limits on an account, use: + + /usr/local/sbin/bbstoreaccounts setlimit 75AB23C new-soft-limit new-hard-limit + + You do not need to restart the server. +
    +
    + +
    + Verify and restore files + + Backups are no use unless you can restore them. The bbackupquery + utility does this and more. + + You don't provide any login information to it, as it just picks + up the data it needs from /etc/box/bbackupd.conf. You should run it as + root so it can find everything it needs. + + Full documentation can be found in the bbackupquery manual page. It follows + the model of a command line sftp client quite closely. + + TODO: Link to bbackupquery man-page here. + + On systems where GNU readline is available (by default) it uses + that for command line history and editing. Otherwise it falls back to + very basic UNIX text entry. + + TODO: Did the readline dependency change to editline? + +
    + Using bbackupquery + + bbackupquery is the tool you use to verify, restore and + investigate your backup files with. When invoked, it simply logs + into the server using the certificates you have listed in + bbackupd.conf. + + After you run bbackupquery, you will see a prompt, allowing + you to execute commands. The list (or ls) command lets you view + files in the store. It works much like unix ls, but with different + options. An example: + + [pthomsen@host bbackupquery]$ bbackupquery +Box Backup Query Tool v0.10, (c) Ben Summers and contributors 2003-2006 +Using configuration file /etc/box/bbackupd.conf +Connecting to store... +Handshake with store... +Login to store... +Login complete. + +Type "help" for a list of commands. + +query > ls +00000002 -d---- mp3 +00000003 -d---- video +00000004 -d---- home-pthomsen +00000005 -d---- root +query > + + The ls commands shows the directories that are backed up. Now + we'll take a closer look at the home-pthomsen directory: + + query > cd home-pthomsen +query > ls +00002809 f----- sample.tiff +0000280a f----- s3.tiff +0000280b f----- s4.tiff +0000280d f----- s2.tiff +0000280e f----- foo.pdf +0000286c f----- core.28720 +0000339a -d---- .emacs.d +0000339d -d---- bbackup-contrib +00003437 f----- calnut.compare.txt +0000345d f----- DSCN1783.jpg +0000345e f----- DSCN1782.jpg +query > + + The ls command takes the following options; + + + + -r -- recursively list + all files + + + + -d -- list deleted + files/directories + + + + -o -- list old versions + of files/directories + + + + -I -- don't display + object ID + + + + -F -- don't display + flags + + + + -t -- show file + modification time (and attr mod time if has the object has + attributes, ~ separated) + + + + -s -- show file size in + blocks used on server (only very approximate indication of size + locally) + + + + The flags displayed from the ls command are as follows: + + + f = file + + d = directory + + X = deleted + + o = old version + + R = remove from server as soon as marked deleted or + old + + a = has attributes stored in directory record which + override attributes in backup file + +
    + +
    + Verify backups + + As with any backup system, you should frequently check that + your backups are working properly by comparing them. Box Backup + makes this very easy and completely automatic. All you have to do is + schedule the bbackupquery compare command to run + regularly, and check its output. You can run the command manually as + follows: + + /usr/local/sbin/bbackupquery "compare -a" quit + + This command will report all the differences found between the + store and the files on disc. It will download everything, so may + take a while. You should expect to see some differences on a typical + compare, because files which have recently changed are unlikely to + have been uploaded yet. It will also tell you how many files have + been modified since the last backup run, since these will normally + have changed, and such failures are expected. + + You are strongly recommended to add this command as a + cron job, at least once a month, and to check the + output for anything suspicious, particularly a large number of + compare failures, failures on files that have not been modified, or + any error (anything except a compare mismatch) that occurs during + the compare operation. + + Consider keeping a record of these messages and comparing them + with a future verification. + + If you would like to do a "quick" check which just downloads + file checksums and compares against that, then run: + + /usr/local/sbin/bbackupquery "compare -aq" quit + + However, this does not check that the file attributes are + correct, and since the checksums are generated on the client they + may not reflect the data on the server if there is a problem -- the + server cannot check the encrypted contents. View this as a quick + indication, rather than a definite check that your backup verifies + correctly. +
    + +
    + Restore backups + + You will need the keys file created when you configured the + server. Without it, you cannot restore the files; this is the + downside of encrypted backups. However, by keeping the small keys + file safe, you indirectly keep your entire backup safe. + + The first step is to recreate the configuration of the backup + client. It's probably best to have stored the /etc/box directory + with your keys. But if you're recreating it, all you really need is + to have got the login infomation correct (ie the certs and + keys). + + Don't run bbackupd yet! It will mark all your files as deleted + if you do, which is not hugely bad in terms of losing data, just a + major inconvenience. (This assumes that you are working from a blank + slate. If you want to restore some files to a different location, + it's fine to restore while bbackupd is running, just do it outside a + backed up directory to make sure it doesn't start uploading the + restored files.) + + Type: + + /usr/local/sbin/bbackupquery + + to run it in interactive mode. + + Type: + + list + + to see a list of the locations stored on the server. + + For each location you want to restore, type: + + restore name-on-server local-dir-name + + The directory specified by local-dir-name must not exist yet. + If the restore is interrupted for any reason, repeat the above + steps, but add the -r flag to the + restore command to tell it to resume. +
    + +
    + Retrieving deleted and old files + + Box Backup makes old versions of files and files you have + deleted available, subject to there being enough disc space on the + server to hold them. + + This is how to retrieve them using bbackupquery. Future + versions will make this far more user-friendly. + + Firstly, run bbackupquery in interactive mode. It behaves in a + similar manner to a command line sftp client. + + /usr/local/sbin/bbackupquery + + Then navigate to the directory containing the file you want, + using list, cd and pwd. + + query > cd home/profiles/USERNAME + + List the directory, using the "o" option to list the files + available without filtering out everything apart from the current + version. (if you want to see deleted files as well, use list + -odt) + + query > list -ot +00000078 f--o- 2004-01-21T20:17:48 NTUSER.DAT +00000079 f--o- 2004-01-21T20:17:48 ntuser.dat.LOG +0000007a f--o- 2004-01-21T17:55:12 ntuser.ini +0000007b f---- 2004-01-12T15:32:00 ntuser.pol +0000007c -d--- 1970-01-01T00:00:00 Templates +00000089 -d--- 1970-01-01T00:00:00 Start Menu +000000a0 -d--- 1970-01-01T00:00:00 SendTo +000000a6 -d--- 1970-01-01T00:00:00 Recent +00000151 -d--- 1970-01-01T00:00:00 PrintHood +00000152 -d--- 1970-01-01T00:00:00 NetHood +00000156 -d--- 1970-01-01T00:00:00 My Documents +0000018d -d--- 1970-01-01T00:00:00 Favorites +00000215 -d--- 1970-01-01T00:00:00 Desktop +00000219 -d--- 1970-01-01T00:00:00 Cookies +0000048b -d--- 1970-01-01T00:00:00 Application Data +000005da -d--- 1970-01-01T00:00:00 UserData +0000437e f--o- 2004-01-24T02:45:43 NTUSER.DAT +0000437f f--o- 2004-01-24T02:45:43 ntuser.dat.LOG +00004380 f--o- 2004-01-23T17:01:29 ntuser.ini +00004446 f--o- 2004-01-24T02:45:43 NTUSER.DAT +00004447 f--o- 2004-01-24T02:45:43 ntuser.dat.LOG +000045f4 f---- 2004-01-26T15:54:16 NTUSER.DAT +000045f5 f---- 2004-01-26T15:54:16 ntuser.dat.LOG +000045f6 f---- 2004-01-26T16:54:31 ntuser.ini + + (this is a listing from a server which is used as a Samba + server for a network of Windows clients.) You now need to fetch the + file using it's ID, rather than it's name. The ID is the hex number + in the first column. Fetch it like this: + + query > get -i 0000437e NTUSER.DAT +Object ID 0000437e fetched successfully. + + The object is now available on your local machine. You can use + lcd to move around, and sh ls to list directories on your local + machine. +
    +
    +
    + +
    + Fixing corruptions of store data + + This section gives help on what to do if your server has suffered + corruption, for example, after an unclean shutdown or other operating + system or hardware problem. + + In general, as updates to the store are made in an atomic manner, + the most likely result is wasted disc space. However, if really bad + things happen, or you believe that there is a lot of wasted space, then + these instructions will help to restore your data. + + You know you will need to do something if you get strange errors, + and bbackupd attempts to contact the server every 100 seconds or so. Or + if one of the discs in your RAID disc set has failed. + + After following these instructions, the end result will be that + bbackupquery will be able to see all the files which were stored on your + server, and retrieve them. Some of them may be in lost+found directories + in the root of the store (or in their original position if they have + been moved) but they will all be able to be retrieved. + + After you have retrieved the files you want, bbackupd will upload + new versions where necessary, and after about two days, mark any + lost+found directories as deleted. Finally, those directories will be + removed by the housekeeping process on the server. + + These instructions assume you're working on account 1234. Replace + this with the account number that you actually want to check (the one + that is experiencing errors). These steps will need to be repeated for + all affected accounts. + +
    + Stop bbackupd + + First, make sure that bbackupd is not running on the client + machine for the account you are going to recover. Use + bbackupctl terminate to stop it. This step is not + strictly necessary, but is recommended. During any checks on the + account, bbackupd will be unable to log in, and after they are + complete, the account is marked as changed on the server so bbackupd + will perform a complete scan. +
    + +
    + Are you using RAID on the server? + + The raidfile recovery tools have not been written, and probably + will not be, since Box Backup RAID is deprecated. However, when two + out of three files are available, the server will successfully allow + access to your data, even if it complains a lot in the logs. The best + thing to do is to fix the accounts, if necessary, and retrieve any + files you need. Then move the old store directories aside (in case you + need them) and start afresh with new accounts, and let the clients + upload all their data again. +
    + +
    + Check and fix the account + + First, run the check utility, and see what errors it + reports. + + /usr/local/sbin/bbstoreaccounts check 1234 + + This will take some time, and use a fair bit of memory (about 16 + bytes per file and directory). If the output looks plausible and + reports errors which need fixing, run it again but with the fix + flag: + + /usr/local/sbin/bbstoreaccounts check 1234 fix + + This will fix any errors, and remove unrecoverable files. + Directories will be recreated if necessary. + + NOTE: The utility may adjust + the soft and hard limits on the account to make sure that housekeeping + will not remove anything -- check these afterwards. +
    + +
    + Grab any files you need with bbackupquery + + At this point, you will have a working store. Every file which + was on the server, and wasn't corrupt, will be available. + + On the client, use bbackupquery to log in and examine the store. + (type help at the prompt for instructions). Retrieve any files you + need, paying attention to any lost+found directories in the root + directory of the store. + + You can skip this step if you are sure that the client machine + is fine -- in this case, bbackupd will bring the store up to + date. +
    + +
    + Restart bbackupd + + Restart bbackupd on the client machine. The store account will + be brought up to date, and files in the wrong place will be marked for + eventual deletion. +
    +
    + +
    + Troubleshooting + + If you are trying to fix a store after your disc has been + corrupted, see Fixing corruptions of + store data. + + Unfortunately, the error messages are not particularly helpful at + the moment. This page lists some of the common errors, and the most + likely causes of them. + + When an error occurs, you will see a message like 'Exception: + RaidFile/OSFileError (2/8)' either on the screen or in your log files. + (it is recommended you set up another log file as recommended in the + server setup instructions.) + + This error may not be particularly helpful, although some do have + extra information about probable causes. To get further information, + check the ExceptionCodes.txt file in the root of the distribution. This + file is generated by the ./configure script, so you will need to have + run that first. + + Some common causes of exceptions are listed below. + + Please email me with any other codes you get, and I will let you + know what they mean, and add notes here. + +
    + RaidFile (2/8) + + This is found either when running bbstoreaccounts or in the + bbstored logs. + + Problem: The directories you + specified in the raidfile.conf are not writable by the _bbstored + user. + + Resolution: Change permissions + appropriately. +
    + +
    + Common (1/2) + + This usually occurs when the configuration files can't be + opened. + + Problem: You created your + configurations in non-standard locations, and the programs cannot find + them. + + Resolution: Explicitly specify + configuration file locations to daemons and programs. For + example + + /usr/local/sbin/bbstored /some/other/dir/bbstored.config /usr/local/sbin/bbackupquery -c /some/other/dir/bbackupd.config + + (daemons specify the name as the first argument, utility + programs with the -c option). + + Problem: bbstored can't find + the raidfile.conf file specified in bbstored.conf. + + Resolution: Edit bbstored.conf + to point to the correct location of this additional configuration + file. +
    + +
    + Server (3/16) + + The server can't listen for connections on the IP address + specified when you configured it. + + Problem: This probably means + you've specified the wrong hostname to bbstored-config -- maybe your + server is behind a NAT firewall? + + Resolution: Edit bbstored.conf + and correct the ListenAddresses line. You should replace the server + address with the IP address of your machine. +
    + +
    + Connection (7/x) + + These errors all relate to connections failing -- you may see + them during operation if there are network failures or other problems + between the client and server. The backup system will recover from + them automatically. + +
    + Connection (7/30) - SSL problems + + Log snippet from client side: + + bbackupd[1904]: Opening connection to server xxxx.xxx... +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01 +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:asn1 encoding routines:ASN1_verify:EVP lib +bbackupd[1904]: SSL err during Connect: error:xxxxxxxx:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed +bbackupd[1904]: TRACE: Exception thrown: ConnectionException(Conn_TLSHandshakeFailed) at SocketStreamTLS.cpp(237) +bbackupd[1904]: Exception caught (7/30), reset state and waiting to retry... + + And from the server: + + bbstored[19291]: Incoming connection from xx.xxx.xx.xxx port xxxxx (handling in child xxxxx) +bbstored[21588]: SSL err during Accept: error:xxxxxxxx:SSL routines:SSL3_READ_BYTES:tlsv1 alert decrypt error +bbstored[21588]: in server child, exception Connection TLSHandshakeFailed (7/30) -- terminating child + + Solution: Create a new CA on + the server side and re-generate the client certificate. Re-creating + the client certificate request is not necessary. +
    +
    + +
    + Advanced troubleshooting + + If this really doesn't help, then using the DEBUG builds of the + system will give you much more information -- a more descriptive + exception message and the file and line number where the error + occurred. + + For example, if you are having problems with bbstoreaccounts, + build the debug version with: + + cd boxbackup-0.0 +cd bin/bbstoreaccounts +make + + Within the module directories, make defaults to building the + debug version. At the top level, it defaults to release. + + This will build an executable in debug/bin/bbstoreaccounts which + you can then use instead of the release version. It will give far more + useful error messages. + + When you get an error message, use the file and line number to + locate where the error occurs in the code. There will be comments + around that line to explain why the exception happened. + + If you are using a debug version of a daemon, these extended + messages are found in the log files. +
    +
    +
    + + &__ExceptionCodes__elfjz3fu; + + + Running without root + + It is possible to run both the server and client without root + privileges. + +
    + Server + + The server, by default, runs as a non-root user. However, it + expects to be run as root and changes user to a specified user as soon + as it can, simply for administrative convenience. The server uses a port + greater than 1024, so it doesn't need root to start. + + To run it entirely as a non-root user, edit the bbstored.conf + file, and remove the User directive from the Server section. Then simply + run the server as your desired user. +
    + +
    + Client + + The client requires root for normal operation, since it must be + able to access all files to back them up. However, it is possible to run + the client as a non-root user, with certain limitations. + + Follow the installation instructions, but install the executable + files manually to somewhere in your home directory. Then use + bbackupd-config to configure the daemon, but use a directory other than + /etc/box, probably somewhere in your home directory. + + All directories you specify to be backed up must be readable, and + all files must be owned by the user and readable to that user. + + Important: If any file or directory is not readable by this user, + the backup process will skip that file or directory. Keep an eye on the + logs for reports of this failure. + + Non-root operation of the backup client is recommended only for + testing, and should not be relied on in a production environment. +
    +
    +
    diff --git a/docs/docbook/bb-book.xsl b/docs/docbook/bb-book.xsl new file mode 100644 index 00000000..a4f05fdb --- /dev/null +++ b/docs/docbook/bb-book.xsl @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/docs/docbook/bb-man.xsl b/docs/docbook/bb-man.xsl new file mode 100644 index 00000000..0f9e5c75 --- /dev/null +++ b/docs/docbook/bb-man.xsl @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/docs/docbook/bb-nochunk-book.xsl b/docs/docbook/bb-nochunk-book.xsl new file mode 100644 index 00000000..6099256c --- /dev/null +++ b/docs/docbook/bb-nochunk-book.xsl @@ -0,0 +1,17 @@ + + + + + + + + + + + + + diff --git a/docs/docbook/bbackupctl.xml b/docs/docbook/bbackupctl.xml new file mode 100644 index 00000000..b4880bfd --- /dev/null +++ b/docs/docbook/bbackupctl.xml @@ -0,0 +1,205 @@ + + + + bbackupctl + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupctl + + Control the Box Backup client daemon + + + + + bbackupctl + + -q + + -c config-file + + command + + + + + Description + + bbackupctl sends commands to a running + bbackupd daemon on a client machine. It can be used to + force an immediate backup, tell the daemon to reload its configuration + files or stop the daemon. If bbackupd is configured in + snapshot mode, it will not back up automatically, and the + bbackupctl must be used to tell it when to start a + backup. + + Communication with the bbackupd daemon takes place over a local + socket (not over the network). Some platforms (notably Windows) can't + determine if the user connecting on this socket has the correct + credentials to execute the commands. On these platforms, ANY local user + can interfere with bbackupd. To avoid this, remove the CommandSocket + option from bbackupd.conf, which will also disable bbackupctl. See the + Client Configuration page for more information. + + bbackupctl needs to read the + bbackupd configuration file to find out the name of the + CommandSocket. If you have to tell bbackupd where to + find the configuration file, you will have to tell + bbackupctl as well. The default on Unix systems is + usually /etc/box/bbackupd.conf. On Windows systems, + it is bbackupd.conf in the same directory where + bbackupd.exe is located. If + bbackupctl cannot find or read the configuration file, + it will log an error message and exit. + + bbackupctl usually writes error messages to the + console and the system logs. If it is not doing what you expect, please + check these outputs first of all. + + + + + + + Run in quiet mode. + + + + + config-file + + + Specify configuration file. + + + + + + Commands + + The following commands are available in bbackupctl: + + + + terminate + + + This command cleanly shuts down bbackupd. + This is better than killing or terminating it any other + way. + + + + + reload + + + Causes the bbackupd daemon to re-read all + its configuration files. Equivalent to kill + -HUP. + + + + + sync + + + Initiates a backup. If no files need to be backed up, no + connection will be made to the server. + + + + + force-sync + + + Initiates a backup, even if the + SyncAllowScript says that no backup should run + now. + + + + + wait-for-sync + + + Passively waits until the next backup starts of its own + accord, and then terminates. + + + + + wait-for-end + + + Passively waits until the next backup starts of its own + accord and finishes, and then terminates. + + + + + sync-and-wait + + + Initiates a backup, waits for it to finish, and then + terminates. + + + + + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd-config.xml b/docs/docbook/bbackupd-config.xml new file mode 100644 index 00000000..41bb6587 --- /dev/null +++ b/docs/docbook/bbackupd-config.xml @@ -0,0 +1,153 @@ + + + + bbackupd-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd-config + + Box Backup client daemon configuration file + generator + + + + + bbackupd-config + + config-dir + + backup-mode + + account-num + + server-hostname + + working-dir + + backup-dir + + backup-dir ... + + + + + Description + + The bbackupd-config script creates configuration files and client + certificates. It takes at least six parameters: + + + + config-dir + + + Configuration directory. Usually + /etc/box. + + + + + backup-mode + + + Either lazy or snapshot. + + + + + account-num + + + The client account number. This is set by the bbstored + administrator. + + + + + server-hostname + + + The hostname or IP address of the bbstored server. + + + + + working-dir + + + A directory to keep temporary state files. This is usually + something like /var/bbackupd. This can be + changed in bbackupd.conf later on if + required. + + + + + backup-dir + + + A space-separated list of directories to be backed up. Note + that this does not traverse mount + points. + + + + + + + Files + + /etc/box/bbackupd.conf + + /etc/box/bbackupd/NotifySysAdmin.sh + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd.conf.xml b/docs/docbook/bbackupd.conf.xml new file mode 100644 index 00000000..43ae2bed --- /dev/null +++ b/docs/docbook/bbackupd.conf.xml @@ -0,0 +1,479 @@ + + + + bbackupd.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd.conf + + Box Backup client daemon configuration file + + + + + /etc/box/bbackupd.conf + + + + + Description + + + + AccountNumber + + + The account number of this client. This is set by the admin of + the store server. + + + + + UpdateStoreInterval + + + Specifies the interval between scanning of the local discs. To + avoid cycles of load on the server, this time is randomly adjusted + by a small percentage as the daemon runs. Defaults to 1 hour. + + + + + MinimumFileAge + + + Specifies how long since a file was last modified before it + will be uploaded. Defaults to 6 hours. + + + + + MaxUploadWait + + + If a file is repeatedly modified it won't be uploaded + immediately in case it's modified again. However it should be + uploaded eventually. This is how long we should wait after first + noticing a change. Defaults to 1 day. + + + + + MaxFileTimeInFuture + + + + + + + + AutomaticBackup + + + + + + + + SyncAllowScript + + + Use this to temporarily stop bbackupd from syncronising or + connecting to the store. 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 prints 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 the script will be asked + again in that number of seconds. + + For example, you could use this on a laptop to only backup + when on a specific network. + + + + + MaximumDiffingTime + + + How much time should be spent on diffing files. + + + + + DeleteRedundantLocationsAfter + + + + + + + + FileTrackingSizeThreshold + + + + + + + + DiffingUploadSizeThreshold + + + + + + + + StoreHostname + + + The hostname or IP address of the + bbstored + + 8 + server. + + + + + StorePort + + + The port used by the server. Defaults to 2201. + + + + + ExtendedLogging + + + Logs everything that happens between the client and server. + The + bbackupd + + 8 + client must also be started with + . + + + + + ExtendedLogFile + + + + + + + + LogAllFileAccess + + + + + + + + LogFile + + + + + + + + LogFileLevel + + + + + + + + CommandSocket + + + Where the command socket is created in the filesystem. + + + + + KeepAliveTime + + + + + + + + StoreObjectInfoFile + + + + + + + + NotifyScript + + + The location of the script which runs at certain events. This + script is generated by + bbackupd-config + + 8 + . Defaults to + /etc/box/bbackupd/NotifySysAdmin.sh. + + + + + NotifyAlways + + + + + + + + CertificateFile + + + The path to the client's public certificate. + + + + + PrivateKeyFile + + + The path to the client's private key. This should only be + readable by root. + + + + + TrustedCAsFile + + + The Certificate Authority created by + bbstored-certs + + 8 + . + + + + + KeysFile + + + The data encryption key. This must be kept safe at all costs, your data is + useless without it! + + + + + DataDirectory + + + A directory to keep temporary state files. This is usually + something like /var/bbackupd. + + + + + Server + + + This section relates to the running daemon. + + + + PidFile + + + The location of the process ID file. Defaults to + /var/run/bbackupd.pid. + + + + + + + + BackupLocations + + + This section defines each directory to be backed up. Each + entry must have at least a Path entry and, optionally, include and + exclude directives. + + Multiple include and exclude directives may appear. + + + + Path + + + The path to back up. + + + + + ExcludeFile + + + Exclude a single file. + + + + + ExcludeFilesRegex + + + Exclude multiple files based on a regular expression. + See + re_format + + 7 + . + + + + + ExcludeDir + + + Exclude a single directory. + + + + + ExcludeDirsRegex + + + Exclude multiple directories based on a regular + expression. See + re_format + + 7 + . + + + + + AlwaysIncludeFile + + + Include a single file from a directory which has been + excluded. + + + + + AlwaysIncludeFilesRegex + + + Include multiple files from an excluded directory, + based on a regular expression. + + + + + AlwaysIncludeDir + + + Include a single directory from a directory which has + been excluded. + + + + + AlwaysIncludeDirsRegex + + + Include multiple directories from an excluded + directory, based on a regular expression. + + + + + + + + + + Examples + + The following is an example of a backup location: + + home +{ + Path = /home + ExcludeDir = /home/guest + ExcludeDir = /home/[^/]+/tmp + ExcludeFilesRegex = .*\.(mp3|MP3)$ + AlwaysIncludeFile = /home/someuser/importantspeech.mp3 +} + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd + + 8 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupd.xml b/docs/docbook/bbackupd.xml new file mode 100644 index 00000000..11de0f3f --- /dev/null +++ b/docs/docbook/bbackupd.xml @@ -0,0 +1,209 @@ + + + + bbackupd + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupd + + Box Backup client daemon + + + + + bbackupd + + -DFkqvVT + + -c config-file + + -t tag + + + + + Description + + bbackupd runs on client computers in the + background, finding new files to back up. When it is time for a backup, + bbackupd will connect to the server + (bbstored) to upload the files. + + A running bbackupd daemon can be controlled with + the bbackupctl command, to make it shut down, reload + its configuration, or start an immediate backup. + + bbackupd needs to be configured to tell it which + files to back up, how often, and to which server (running + bbstored). See the Client Configuration page for more + information. For this, you must write a configuration file. You must + either place it in the default location, or tell + bbackupd where to find it. + + You can check the default location with the + option. The default on Unix systems is usually + /etc/box/bbackupd.conf. On Windows systems, it is + bbackupd.conf in the same directory where + bbackupd.exe is located. If bbackupd cannot find or + read the configuration file, it will log an error message and exit. + + bbackupd usually writes log messages to the + system logs, using the facility local5, which you can + use to filter them to send them to a separate file. It can also write them + to the console, see options below. If bbackupd is not + doing what you expect, please check the logs first of all. + + + Options + + + + config-file + + + Use the specified configuration file. If + is omitted, the last argument is the configuration file. If none + is specified, the default is used (see above). + + + + + + + + Debugging mode. Do not fork into the background (do not run + as a daemon). Not available on Windows. + + + + + + + + No-fork mode. Same as for bbackupd. Not + available on Windows. + + + + + + + + Keep console open after fork, keep writing log messages to + it. Not available on Windows. + + + + + + + + Run more quietly. Reduce verbosity level by one. Available + levels are NOTHING, FATAL, + ERROR, WARNING, + NOTICE, INFO, + TRACE, EVERYTHING. Default + level is NOTICE in non-debugging builds. Use + once to drop to WARNING level, twice for + ERROR level, four times for no logging at + all. + + + + + -v + + + Run more verbosely. Increase verbosity level by one. Use + once to raise to INFO level, twice for + TRACE level, three times for + EVERYTHING (currently the same as + TRACE). + + + + + + + + Run at maximum verbosity (EVERYTHING + level). + + + + + tag + + + Tag each console message with specified marker. Mainly + useful in testing when running multiple daemons on the same + console. + + + + + + + + Timestamp each line of console output. + + + + + + + + Files + + /etc/box/bbackupd.conf + + + + See Also + + + bbackupd.conf + + 5 + , + bbackupd-config + + 8 + , + bbackupctl + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbackupquery.xml b/docs/docbook/bbackupquery.xml new file mode 100644 index 00000000..016d5c7d --- /dev/null +++ b/docs/docbook/bbackupquery.xml @@ -0,0 +1,506 @@ + + + + bbackupquery + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbackupquery + + Box Backup store query and file retrieval + + + + + bbackupquery + + -q + + -c configfile + + command ... + + + + + Description + + bbackupquery is the main way of interacting with + the backup store from a Box Backup client machine. It supports both + interactive and batch modes of operation. + + It can be used to reviewing the status of a client machine's backup + store, getting status from the store server. The main use is to retrieve + files and directories when needed. + + bbackupquery supports interactive and batch modes + of operation. Interactive mode allows for interaction with the server much + like an interactive FTP client. + + Batch mode is invoked by putting commands into the invocation of + bbackupquery. Example: + + bbackupquery "list home-dirs" quit + + Note that commands that contain spaces are enclosed in double + quotes. If the quit command is omitted, after the + preceding commands are completed, bbackupquery will + enter interactive mode. + + + + Options + + + + + + + Quiet. Suppresses status output while running. + + + + + + + + Use configfile instead of the default bbackupd.conf file. + Can be a relative or full path. + + + + + + + Commands + + The commands that can be used in bbackupquery are listed + below. + + + + help + + + Displays the basic help message, which gives information about + the commands available in bbackupquery. Use the + form help command to get help on a specific + command. + + + + + quit + + + End the session with the store server, and quit + bbackupquery. + + + + + cd options + directory-name + + + Change directory. Options: + + + + + consider deleted directories for traversal + + + + + + + + consider old versions of directories for traversal. + This option should never be useful in a correctly formed + store. + + + + + + + + lcd + local-directory-name + + + Change directory on the client machine. To list the contents + of the local directory, type sh ls (on Unix-like + machines). + + + + + list options + directory-name + + + The list (or its synonym ls) command lists + the content of the current, or specified, directory. The options are + as follows: + + + + + + + recursively list all files + + + + + + + + list deleted files and directories + + + + + + + + list old versions of files and directories + + + + + + + + don't display object IDs + + + + + + + + don't display flags + + + + + + + + show file modification time (and attr mod time, if the + object has attributes). + + + + + + + + show file size in blocks used on server. Note that + this is only a very approximate indication of local file + size. + + + + + + + + ls options + directory-name + + + Synonym for list. + + + + + pwd + + + Print current directory, always relative to the backup store + root. + + + + + sh shell-command + + + Everything after the sh is passed to a shell and run. All + output from the command is displayed in the client. + + Example: to list the contents of the current directory on the + client machine type sh ls. + + + + + compare -a + + + + + + + + compare -l + location-name + + + + + + + + compare store-dir-name + local-dir-name + + + Compare the current data in the store with the data on the + disc. Please note that all the data will be downloaded from the + store, so this can be a very lengthy process depending on the size + of the store, and the size of the part you are comparing. + + Options: + + + + + + + compare all locations. + + + + + + + + compare one backup location as specified in the + configuration file. This compares one of the top level store + directories. + + + + + + + + set return code. The return code is set to the + following values, if quit is the next command. So, if + another command is run after the compare, the return code + will not refer to the compare. This option is very useful + for automating compares. Return code values: + + -- no differences were + found + + + + -- differences were + found + + + + -- an error occured + + + + + + + + + + get object-filename + local-filename + + + + + + + + get -i object-id + local-filename + + + Gets a file from the store. Object is specified as the + filename within the current directory. Local filename is optional. + Ignores old and deleted files when searching the directory for the + file to retrieve. + + To get an old or deleted file, use the + option and select the object as a hex object ID (first column in + listing). The local filename must be specified. + + + + + getobject object-id + local-filename + + + Gets the object specified by the object id (in hex) and stores + the raw contents in the local file specified. Note: This is only + useful for debugging as it does not decode files from the stored + format, which is encrypted and compressed. + + + + + restore -d + directory-name + local-directory-name + + + + + + + + restore -r + + + Restores a directory to the local disc. The local directory + specified must not exist (unless a previous restore is being + restarted). The root cannot be restored -- restore locations + individually. + + Options: + + + + + + + restore a deleted directory + + + + + + + + resume an interrupted restore + + + If a restore operation is interrupted for any + reason, it can be restarted using the switch. + Restore progress information is saved in a file at regular intervals + during the restore operation to allow restarts. + + + + + usage -m + + + Show space used on the server for this account. Display + fields: + + Used: Total amount of space used on + the server + + + + Old files: Space used by old + files + + + + Deleted files: Space used by + deleted files + + + + Directories: Space used by the + directory structure + + + + When Used exceeds the soft limit, the + server will start to remove old and deleted files until the usage + drops below the soft limit. After a while, you should expect to see + the usage stay at just below the soft limit. You only need more + space if the space used by old and deleted files is near + zero. + + The option displays output in + machine-readable form. + + + + + + + Bugs + + If you find a bug in Box Backup and you want to let us know about + it, join the mailing + list and send us a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstoreaccounts.xml b/docs/docbook/bbstoreaccounts.xml new file mode 100644 index 00000000..08092acf --- /dev/null +++ b/docs/docbook/bbstoreaccounts.xml @@ -0,0 +1,386 @@ + + + + bbstoreaccounts + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstoreaccounts + + Box Backup store accounts manager + + + + + bbstoreaccounts + + -c config-file + + command + + account-id + + command-specific arguments + + + + + Description + + bbstoreaccounts is the tool for managing accounts + on the store server. It can be used to view information related to + accounts, as well as create, change and delete accounts on the store + server. + + bbstoreaccounts always takes at least 2 + parameters: the command name and the account ID. Some commands require + additional parameters, and some commands have optional parameters. + + + Options + + + + + + + The configfile to use for connecting to the store. Default + is /etc/box/bbstored.conf. + + + + + + + Commands + + The commands tells bbstoreaccounts what action to perform. + + + + check account-id + fix + + + The check command verifies the + integrity of the store account given, and optionally fixes any + corruptions. Note: It is + recommended to run the 'simple' check command (without + fix) before using the fix + option. This gives an overview of the extent of any problems, + before attempting to fix them. + + + + + create account-id + disc-set soft-limit + hard-limit + + + Creates a new store account with the parameters given. The + parameters are as follows: + + + + + + + The ID of the new account to be created. A 32-bit + hexadecimal number. Cannot already exist on the + server. + + + + + + + + The disc set from + raidfile.conf + + 5 + where the backups for this client will + be stored. A number. Each RAID-file set has a number in + raidfile.conf. This number is what's used. + + + + + + + + The soft limit is the amount of storage that the + server will guarantee to be available for + storage. + + + + + + + + The amount of storage that the the server will + allow, before rejecting uploads, and starting to + eliminate old and deleted files to get back down to + soft-limit. + + + + + + + + delete account-id + yes + + + Deletes the account from the store server completely. + Removes all backups and deletes all references to the account in + the config files. + + delete will ask for confirmation from + the user, when called. Using the flag, + eliminates that need. This is useful when deleting accounts from + within a script or some other automated means. 0 + + + + + info account-id + + + Display information about the given account. + Example:[root]# bbstoreaccounts info 1 + Account ID: 00000001 + Last object ID: 58757 + Blocks used: 9864063 (38531.50Mb) + Blocks used by old files: 62058 (242.41Mb) +Blocks used by deleted files: 34025 (132.91Mb) + Blocks used by directories: 6679 (26.09Mb) + Block soft limit: 11796480 (46080.00Mb) + Block hard limit: 13107200 (51200.00Mb) + Client store marker: 1139559852000000 + + Explanation: + + + + Account ID + + + The account ID being displayed. + + + + + Last Object ID + + + A counter that keeps track of the objects that + have been backed up. This number refers to the last file + that was written to the store. The ID is displayed as a + decimal number, and the object ID can be converted to a + path name to a file as follows: convert the number to + hex (e.g.: 58757 => 0xE585); The last backed up file + will be (relative from the client's store root): + e5/o85.rfw. Longer numbers infer + more directories in the structure, so as an example + 3952697264 as the last object ID gives 0xEB995FB0, which + translates to a backup pathname of + eb/99/5f/ob0.rfw. + + + + + Blocks used + + + The number of blocks used by the store. The size + in Mb depends on the number of blocks, as well as the + block size for the disc set given in + raidfile.conf + + 5 + . In this case the block size is + 4096. + + + + + Blocks used by old files + + + The number of blocks occupied by files that have + newer versions in the store. This data is at risk for + being removed during housekeeping. + + + + + Blocks used by deleted files + + + The number of blocks used by files that have been + deleted on the client. This data is at risk for being + removed during housekeeping. + + + + + Blocks used by directories + + + The number of blocks used by directories in the + store. + + + + + Block soft limit + + + The soft limit in blocks. The soft limit is the + maximum guaranteed storage space available to the + account. When housekeeping starts, and the old and + deleted files are removed, they are removed in + chronological order (oldest first), until the data used + is less than the soft limit. + + + + + Block hard limit + + + The hard limit in blocks. The hard limit is the + most amount of storage the server will allow in an + account. Any data above this amount will be rejected. + Housekeeping will reduce the storage use, so more data + can be uploaded. + + + + + Client store marker + + + + bbstored + + 8 + uses this number to determine if it + needs to rescan the entire store. If this number is + different from the last time it checked, a rescan will + take place. + + + + + + + + setlimit account-id + soft-limit hard-limit + + + Changes the storage space allocation for the given + account. No server restart is needed. + + Parameters: + + + + + + + The ID of the account to be modified. + + + + + + + + The soft limit is the amount of storage that the + server will guarantee to be available for + storage. + + + + + + + + The amount of storage that the the server will + allow before rejecting uploads and starting to eliminate + old and deleted files to get back down to + . + + + + + + + + + + + Examples + + Create an account with ID 3af on disc set 0, with a 20GB soft-limit + and a 22GB hard-limit:bbstoreaccounts create 3af 0 20G 22GAlter + existing account ID 20 to have a 50GB soft-limit and a 55GB + hard-limit:bbstoreaccounts setlimit 20 50G 55G + + + + Files + + /etc/box/bbstored/accounts.txt + + + + See Also + + + bbstored + + 8 + , + bbstored-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored-certs.xml b/docs/docbook/bbstored-certs.xml new file mode 100644 index 00000000..79308089 --- /dev/null +++ b/docs/docbook/bbstored-certs.xml @@ -0,0 +1,180 @@ + + + + bbstored-certs + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored-certs + + Manage certificates for the Box Backup system + + + + + bbstored-certs + + certs-dir + + command + + arguments + + + + + Description + + bbstored-certs creates and signs certificates for + use in Box Backup. It allows the user to create and sign the server keys, + as well as signing client keys. + + All commands must be followed by the certs-dir, + which is the directory in which the certificates are stored. + + + Commands + + There are 3 commands: + + + + init + + + Create the certs-dir, and generate the + server keys for bbstored. certs-dir cannot + exist before running the command. + + + + + sign-server + servercsrfile + + + Sign the server certificate. The + servercsrfile is the file generated by the + init command. + + + + + sign + clientcsrfile + + + Sign a client certificate. The + clientcsrfile is generated during client setup. + See + bbackupd-config + + 8 + . Send the signed certificate back to the client, + and install according to the instructions given by + bbackupd-config. + + + + + + + + Files + + + raidfile-config + + 8 + generates the + raidfile.conf + + 5 + file. + + + + Bugs + + If you find a bug in Box Backup, and you want to let us know about + it, join the mailing + list, and send a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + See Also + + + bbstored-config + + 8 + , + bbstored.conf + + 5 + , + bbstoreaccounts + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored-config.xml b/docs/docbook/bbstored-config.xml new file mode 100644 index 00000000..6d0113c1 --- /dev/null +++ b/docs/docbook/bbstored-config.xml @@ -0,0 +1,148 @@ + + + + bbstored-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored-config + + Box Backup store daemon configuration file + generator + + + + + bbstored-config + + configdir + + servername + + username + + + + + Description + + The bbstored-config script creates configuration files and server + certificates for a bbstored instance. It takes three parameters: + + + + configdir + + + The directory where config files will reside. A + bbstored subdirectory will be created where + several config files will reside. The + bbstored.conf file will be created in + configdir. + + + + + servername + + + The name of the server that is being configured. Usually the + fully qualified domain name of the machine in question. + + + + + username + + + The name of the user that should be running the + bbstored process. Recommended name: + _bbstored. + + + + + A valid + raidfile.conf + + 5 + must be found in configdir. Several steps are taken + during the run of bbstored-config: + + + + Server certificates are created. This requires interaction from + the operator. + + + + The RAID volumes are checked to ensure that the configuration is + consistent and will work. + + + + Instructions for next steps to take are shown. These steps may + be different for different OS platforms, so pay close attention to + these instructions. + + + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored.conf + + 5 + , + bbstored + + 8 + , + bbstored-certs + + 8 + , + raidfile-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored.conf.xml b/docs/docbook/bbstored.conf.xml new file mode 100644 index 00000000..136b1bd0 --- /dev/null +++ b/docs/docbook/bbstored.conf.xml @@ -0,0 +1,211 @@ + + + + bbstored.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored.conf + + Box Backup store daemon configuration file + + + + + /etc/box/bbstored.conf + + + + + Description + + The following configuration options are valid: + + + + RaidFileConf + + + Specifies the path to the + raidfile.conf + + 5 + . This is normally + /etc/box/raidfile.conf. + + + + + AccountDatabase + + + Specifies the path to the account database created by + + bbstoreaccounts + + 8 + . This is usually + /etc/box/bbstored/accounts.txt. + + + + + ExtendedLogging + + + Specifies whether extended logging should be enabled to show + what commands are being received from clients. + + + + + TimeBetweenHousekeeping + + + How long between scanning for files which need + deleting. + + + + + Server + + + These options relate to the actual daemon. + + PidFile + + + The location of the pidfile, where the daemon's + process ID is kept. + + + + + User + + + The user to run as. + + + + + ListenAddresses + + + The interface addresses to listen on. Hostnames may be + used instead of IP addresses. The format is: + or + . + + + + + CertificateFile + + + The path to the server's public certificate. + + + + + PrivateKeyFile + + + The path to the server's private key. This should only + be readable by root and/or the . + + + + + TrustedCAsFile + + + The Certificate Authority created by + bbstored-certs + + 8 + . + + + + + + + + + + Examples + + The following is an example bbstored.conf: + + RaidFileConf = /etc/box/raidfile.conf +AccountDatabase = /etc/box/bbstored/accounts.txt + +TimeBetweenHousekeeping = 900 + +Server +{ + PidFile = /var/run/bbstored.pid + User = _bbstored + ListenAddresses = inet:server.example.com + CertificateFile = /etc/box/bbstored/server.example.com-cert.pem + PrivateKeyFile = /etc/box/bbstored/server.example.com-key.pem + TrustedCAsFile = /etc/box/bbstored/clientCA.pem +} + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored + + 8 + , + bbstored-config + + 8 + , + raidfile-config + + 8 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/bbstored.xml b/docs/docbook/bbstored.xml new file mode 100644 index 00000000..81891a8d --- /dev/null +++ b/docs/docbook/bbstored.xml @@ -0,0 +1,90 @@ + + + + bbstored + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + bbstored + + Box Backup store daemon + + + + + bbstored + + config-file + + + + + Description + + bbstored runs on a central server. Clients + running bbackupd connect to the server and upload + files. + + The only argument is optional and specifies a non-default + configuration file. By default it will look for the configuration file as + /etc/box/bbstored.conf. + + + + Files + + /etc/box/bbstored.conf + + + + See Also + + + bbstored.conf + + 5 + , + bbstored-config + + 8 + , + raidfile-config + + 8 + , + raidfile.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/html/bbdoc-man.css b/docs/docbook/html/bbdoc-man.css new file mode 100644 index 00000000..0345560e --- /dev/null +++ b/docs/docbook/html/bbdoc-man.css @@ -0,0 +1,104 @@ +body { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 20px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto; } + +table { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: 10pt; + line-height: 100%; } + + +code { + font-size: 11pt; } + + + +div.navheader { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + line-height: 100%; } + +#header { + background-color: #e4e6ed; + text-align: left; + padding-top: 10px; + margin-right: -100px; + margin-left: -250px; + top: 20px; + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid white } + +#logo { + position: relative; + margin-left: 200px } + + +#page { + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 50px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto } + +#disc { } + +.informaltable td,tr {font-size: 1em; + line-height: 140%; + text-align: left; + background-color: #e4e6ed; + padding: 4px } + +tr,td {font-size: 1em; + line-height: 100%; + background-color: #edeef3; } + +pre, tt { font-size: 1.3em; + color: #088; + letter-spacing: 1px; + word-spacing: 2px} + +h1 { + color: #c00; + font-size: 16pt; + margin-bottom: 2em; + margin-left: -50px } + +h2 { + color: #324e95; + font-size: 12pt; + margin-top: 2em; + margin-left: -50px } + +h3 { + color: #324e95; + font-size: 10pt; + margin-top: 2em; + margin-left: -50px } + +dt { font-weight: bold } + +a:link { + color: #324e95; + text-decoration: none; + background-color: transparent } + +a:visited { + color: #90c; + text-decoration: none } + +a:hover { + color: #c00; + text-decoration: underline; + background-color: transparent } diff --git a/docs/docbook/html/bbdoc.css b/docs/docbook/html/bbdoc.css new file mode 100644 index 00000000..d3b4a1c2 --- /dev/null +++ b/docs/docbook/html/bbdoc.css @@ -0,0 +1,112 @@ +body { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 20px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto; } + +table { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + font-size: 10pt; + line-height: 100%; } + + + + +div.navheader { + font-family: Verdana, Geneva, Arial, sans-serif; + background-color: #edeef3; + line-height: 100%; } + +#header { + background-color: #e4e6ed; + text-align: left; + padding-top: 10px; + margin-right: -100px; + margin-left: -250px; + top: 20px; + border-top: 1px solid #c4c4d5; + border-bottom: 1px solid white } + +#logo { + position: relative; + margin-left: 200px } + + +#page { + font-size: .75em; + line-height: 180%; + text-align: left; + margin-top: 50px; + margin-right: 100px; + margin-left: 250px; + position: relative; + width: auto } + +#disc { } + +.informaltable td,tr {font-size: 1em; + line-height: 140%; + text-align: left; + background-color: #e4e6ed; + padding: 4px } + +tr,td {font-size: 1em; + line-height: 100%; + background-color: #edeef3; } + +pre, tt { font-size: 1.3em; + color: #088; + letter-spacing: 1px; + word-spacing: 2px} + +h1 { + color: #c00; + font-size: 16pt; + margin-bottom: 2em; + margin-left: -50px } + +h2 { + color: #324e95; + font-size: 12pt; + margin-top: 2em; + margin-left: -50px } + +h3 { + color: #324e95; + font-size: 10pt; + margin-top: 2em; + margin-left: -50px } + +dt { font-weight: bold } + +ul { + list-style-image: url(images/arrow.png) } + +ul li { + background-color: #e4e6ed; + margin: 1em 6em 1em -2em; + padding: 0.2em 0.5em 0.2em 1em; + border-style: solid; + border-width: 1px; + border-color: #c4c4d5 #fff #fff #c4c4d5 } + +a:link { + color: #324e95; + text-decoration: none; + background-color: transparent } + +a:visited { + color: #90c; + text-decoration: none } + +a:hover { + color: #c00; + text-decoration: underline; + background-color: transparent } diff --git a/docs/docbook/html/favicon.ico b/docs/docbook/html/favicon.ico new file mode 100644 index 00000000..f8cbb3b3 Binary files /dev/null and b/docs/docbook/html/favicon.ico differ diff --git a/docs/docbook/html/images/arrow.png b/docs/docbook/html/images/arrow.png new file mode 100644 index 00000000..4c60805d Binary files /dev/null and b/docs/docbook/html/images/arrow.png differ diff --git a/docs/docbook/html/images/bblogo.png b/docs/docbook/html/images/bblogo.png new file mode 100644 index 00000000..b33230b4 Binary files /dev/null and b/docs/docbook/html/images/bblogo.png differ diff --git a/docs/docbook/html/images/stepahead.png b/docs/docbook/html/images/stepahead.png new file mode 100644 index 00000000..9ac05b8c Binary files /dev/null and b/docs/docbook/html/images/stepahead.png differ diff --git a/docs/docbook/instguide.xml b/docs/docbook/instguide.xml new file mode 100644 index 00000000..addef297 --- /dev/null +++ b/docs/docbook/instguide.xml @@ -0,0 +1,766 @@ + + + + Box Backup Build and Installation Guide + + + License + + Copyright © 2003 - 2007, Ben Summers and contributors. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + + + Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + + + Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + + + + All use of this software and associated advertising materials + must display the following acknowledgement: This product includes + software developed by Ben Summers. + + + + The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + + + + [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. + + + + Introduction + + The backup daemon, bbackupd, runs on all machines to be backed up. + The store server daemon, bbstored runs on a central server. Data is sent + to the store server, which stores all data on local filesystems, that is, + only on local hard drives. Tape or other archive media is not used. + + The system is designed to be easy to set up and run, and cheap to + use. Once set up, there should be no need for user or administrative + intervention, apart from usual system maintenance. + +
    + Client daemon + + bbackupd is configured with a list of directories to back up. It + has a lazy approach to backing up data. Every so often, the directories + are scanned, and new data is uploaded to the server. This new data must + be over a set age before it is uploaded. This prevents rapid revisions + of a file resulting in many uploads of the same file in a short period + of time. + + It can also operate in a snapshot mode, which behaves like + traditional backup software. When instructed by an external bbackupctl + program, it will upload all changed files to the server. + + The daemon is always running, although sleeping most of the time. + In lazy mode, it is completely self contained -- scripts running under + cron jobs are not used. The objective is to keep files backed up, not to + make snapshots of the filesystem at particular points in time + available. + + If an old version of the file is present on the server, a modified + version of the rsync algorithm is used to upload only the changed + portions of the file. + + After a new version is uploaded, the old version is still + available (subject to disc space on the server). Similarly, a deleted + file is still available. The only limit to their availability is space + allocated to this account on the server + + Future versions will add the ability to mark the current state of + files on the server, and restore from this mark. This will emulate the + changing of tapes in a tape backup system. + +
    + Restoration + + Restoring files is performed using a query tool, bbackupquery. + This can be used to restore entire directories, or as an 'FTP-like' + tool to list and retrieve individual files. Old versions and deleted + files can be retrieved using this tool for as long as they are kept on + the server. +
    + +
    + Client Resource Usage + + bbackupd uses only a minimal amount of disc space to store + records on uploaded files -- less than 32 bytes per directory and file + over a set size threshold. However, it minimises the amount of queries + it must make to the server by storing, in memory, a data structure + which allows it to determine what data is new. It does not need to + store a record of all files, essentially just the directory names and + last modification times. This is not a huge amount of memory. + + If there are no changes to the directories, then the client will + not even connect to the server. +
    +
    + +
    + Security + + Box Backup is designed to be secure in several ways. The data + stored on the backup store server is encrypted using secret-key + cryptography. Additionally, the transport layer is encrypted using TLS, + to ensure that the communications can't be snooped. + +
    + Encryption + + The files, directories, filenames and file attributes are all + encrypted. By examining the stored files on the server, it is only + possible to determine the approximate sizes of a files and the tree + structure of the disc (not names, just number of files and + subdirectories in a directory). By monitoring the actions performed by + a client, it is possible to determine the frequency and approximate + scope of changes to files and directories. + + The connections between the server and client are encrypted + using TLS (latest version of SSL). Traffic analysis is possible to + some degree, but limited in usefulness. + + An attacker will not be able to recover the backed up data + without the encryption keys. Of course, you won't be able to recover + your files without the keys either, so you must make a conventional, + secure, backup of these keys. +
    + +
    + Authentication + + SSL certificates are used to authenticate clients. UNIX user + accounts are not used to minimise the dependence on the configuration + of the operating system hosting the server. + + A script is provided to run the necessary certification + authority with minimal effort. +
    +
    + +
    + Server daemon + + The server daemon is designed to be simple to deploy, and run on + the cheapest hardware possible. To avoid the necessity to use expensive + hardware RAID or software RAID with complex setup, it (optionally) + stores files using RAID techniques. + + It does not need to run as a privileged user. + + Each account has a set amount of disc space allocated, with a soft + and a hard limit. If the account exceeds the soft limit, a housekeeping + process will start deleting old versions and deleted files to reduce the + space used to below the soft limit. If the backup client attempts to + upload a file which causes the store to exceed the hard limit, the + upload will be refused. +
    +
    + + + Building and installing + +
    + Before you start + + Firstly, check that all the clocks on your clients, servers and + signing machines are accurate and in sync. A disagreement in time + between a client and a server is the biggest cause of installation + difficulties, as the times in the generated certificates will cause + login failures if the start date is in the future. +
    + +
    + Box Backup compile + + In the following instructions, replace 0.00 with the actual + version number of the archive you have downloaded. + + For help building on Windows, see the Windows + Compile Appendix. And if you want to build a Linux RPM, look here. + + You need the latest version of OpenSSL, as some of the extra APIs + it provides are required. You should have this anyway, as earlier + versions have security flaws. (If you have an earlier version installed, + the configuration script will give you instructions on enabling + experimental support for older versions.) + + See OpenSSL notes for more information + on OpenSSL issues. + + There are some notes in the archive about compiling on various + platforms within the boxbackup-0.00 directory -- read them first. For + example, if you are compiling under Linux, look for LINUX.txt as + boxbackup-0.00/LINUX.txt after untaring the archive. + + Download the archive, then in that directory type + + tar xvzf boxbackup-0.00.tgz +cd boxbackup-0.00 +./configure +make + + The server and client will be built and packaged up for + installation on this machine, or ready to be transferred as tar files to + another machine for installation. + + This builds two parcels of binaries and scripts, 'backup-client' + and 'backup-server'. The generated installation scripts assumes you want + everything installed in /usr/local/bin + + Optionally, type make test to run + all the tests. +
    + +
    + Local installation + + Type make install-backup-client + to install the backup client. + + Type make install-backup-server + to install the backup server. +
    + +
    + Remote installation + + In the parcels directory, there are tar files for each parcel. The + name reflects the version and platform you have built it for. + + Transfer this tar file to the remote server, and unpack it, then + run the install script. For example: + + tar xvzf boxbackup-0.00-backup-server-OpenBSD.tgz +cd boxbackup-0.00-backup-server-OpenBSD +./install-backup-server +
    + +
    + Configure options + + You can use arguments to the configure script to adjust the + compile and link lines in the generated Makefiles, should this be + necessary for your platform. The configure script takes the usual GNU + autoconf arguments, a full list of which can be obtained with --help. Additional options for Box Backup + include: + + + + + + --enable-gnu-readline + + Use GNU readline if present. Linking Box Backup against + GNU readline may create licence implications if you then + distribute the binaries. libeditline is also supported as a safe + alternative, and is used by default if available. + + + + --disable-largefile + + Omit support for large files + + + + --with-bdb-lib=DIR + + Specify Berkeley DB library location + + + + --with-bdb-headers=DIR + + Specify Berkeley DB headers location + + + + --with-random=FILE + + Use FILE as random number seed (normally + auto-detected) + + + + --with-tmp-dir=DIR + + Directory for temporary files (normally /tmp) + + + + + + See OpenSSL notes for the OpenSSL + specific options. +
    + +
    + Tests + + There are a number of unit tests provided. To compile and run one + type: + + ./runtest.pl bbackupd release +./runtest.pl common debug +./runtest.pl ALL + + The runtest.pl script will compile and run the test. The first + argument is the test name, and the second the type of build. Use ALL as + a test name to run all the tests. + + The output from the tests is slightly muddled using this script. + If you're developing, porting or trying out new things, it might be + better to use the following scheme: + + cd test/bbackupd +make +cd ../../debug/test/bbackupd +./t + + or in release mode... + + cd test/bbackupd +make -D RELEASE +cd ../../release/test/bbackupd +./t + + (use RELEASE=1 with GNU make) + + I tend to use two windows, one for compilation, and one for + running tests. +
    +
    + + + Box Backup and SSL + +
    + General notes + + Ideally, you need to use version 0.9.7 or later of OpenSSL. If + this is installed on your system by default (and it is on most recent + releases of UNIX like OSes) then everything should just work. + + However, if it isn't, you have a few options. + +
    + Upgrade your installation + + The best option is to upgrade your installation to use 0.9.7. + Hopefully your package manager will make this easy for you. This may + require reinstallation of lots of software which depends on OpenSSL, + so may not be ideal. + + (But as there have been a few security flaws in OpenSSL + recently, you probably want to upgrade it anyway.) +
    + +
    + Install another OpenSSL + + The second best option is to install another copy. If you + download and install from source, it will probably install into + /usr/local/ssl. You can then configure Box Backup to use it + using: + + ./configure --with-ssl-headers=/usr/local/ssl/include --with-ssl-lib=/usr/local/ssl/lib + + which will set up the various includes and libraries for + you. + + The configuration scripts may be a problem, depending on your + installation. See below for more information. +
    + +
    + Use the old version of OpenSSL + + If you have an old version installed, the configuration script + will give you instructions on how to enable support for older + versions. Read the warnings, and please, whatever you do, don't + release binary packages or ports which enable this option. + + You may have issues with the configuration scripts, see + below. +
    +
    + +
    + If you have problems with the config scripts + + If you get OpenSSL related errors with the configuration scripts, + there are two things to check: + + + + The bin directory within your OpenSSL directory is in the path + (if you have installed another version) + + + + You have an openssl.cnf file which works and can be + found. + + + +
    + OpenSSL config file + + You need to have an openssl.cnf file. The default will generally + work well (see example at end). Make sure the openssl utility can find + it, either set the OPENSSL_CONF environment variable, or install it + into the location that is mentioned in the error messages. + + Example OpenSSL config file: + + # +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +RANDFILE = /dev/arandom + +#################################################################### +[ req ] +default_bits = 1024 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +#countryName_default = AU +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +#stateOrProvinceName_default = Some-State + +localityName = Locality Name (eg, city) + +0.organizationName = Organization Name (eg, company) +#0.organizationName_default = Internet Widgits Pty Ltd + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = CryptSoft Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = + +commonName = Common Name (eg, fully qualified host name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ x509v3_extensions ] + +nsCaRevocationUrl = http://www.cryptsoft.com/ca-crl.pem +nsComment = "This is a comment" + +# under ASN.1, the 0 bit would be encoded as 80 +nsCertType = 0x40 +
    +
    +
    + + + Compiling bbackupd on Windows using Visual C++ + + This Appendix explains how to build the bbackupd daemon for Windows + using the Visual C++ compiler. + + If you have any problems following these instructions, please sign + up to the mailing + list and report them to us, or send an email to Chris Wilson. Thanks! + + Note: bbstored will not be built + with this process. bbstored is not currently supported on Windows. There + are no plans for bbstored support on Windows. + +
    + Tools + + You will need quite a bit of software to make this work. All of it + is available for free on the Internet, although Visual C++ Express has + license restrictions and a time limit. + +
    + Visual C++ + + Microsoft's Visual C++ compiler and development environment are + part of their commercial product Visual Studio. Visual + Studio 2005 is supported, and 2003 should work as well. + + You can also download + a free copy of Visual C++ 2005 Express. This copy must be registered + (activated) within 30 days, and is free for one year. + + You will need the Platform + SDK to allow you to compile Windows applications. +
    + +
    + Perl + + Download and install ActivePerl for + Windows, which you can probably find here. +
    + +
    + Libraries + + You will need to download and install several libraries. They + must all be built in the same directory, to be able to link + properly. + + 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:\Documents and Settings\Your Username\Desktop\Box + + Make sure you know the full path to this directory. + +
    + OpenSSL + + You will need to compile OpenSSL using Visual C++. The latest + release at this time, OpenSSL 0.9.8a, does not compile with Visual + C++ 2005 out of the box, so you need a + patched version. The original + source and patch + are also available. + + To compile OpenSSL: + + + + Use a Windows unzipper such as WinZip (free trial) to + extract the openssl-0.9.8a-vc2005.tar.gz archive, + which you just downloaded, into the base directory. + + + + Rename the folder from openssl-0.9.8a-vc2005 to openssl + + + + Open a command shell (run cmd.exe) and cd to the openssl directory + + + + Run the following commands: + + perl Configure VC-WIN32 +ms\do_ms +"c:\program files\Microsoft Visual Studio 8\Common7\Tools\vsvars32.bat" +nmake -f ms\ntdll.mak + + +
    + +
    + Zlib + + You will need to download the Zlib compiled DLL. + Create a directory called zlib in + the base directory, and unzip the file you just downloaded into that + directory. You don't need to compile anything. +
    +
    + +
    + Download Box Backup + + The first version of Box Backup that's known to compile and with + Visual C++ 2005 is available on the Subversion + server. However, this version has not been extensively tested + and may be out of date. + + The changes are expected to be merged into the Subversion trunk + at some point, and this page should then be updated. If in doubt, + please sign up to the mailing + list and ask us. + + To get the source code out of Subversion you will need a Subversion + client for Windows. After installing it, open a new command + prompt, go to the base directory, and type: + + svn co http://www.boxbackup.org/svn/box/chris/win32/vc2005-compile-fixes/ boxbackup + + This should create a directory called boxbackup inside the base directory. +
    + +
    + Configure Box Backup + + Open a command prompt, change to the base directory then + boxbackup, and run win32.bat to configure the sources. Otherwise, + Visual C++ will complain about missing files whose names start with + autogen, and missing config.h. +
    + +
    + Compile Box Backup + + Open Visual C++. Choose "File/Open/Project", navigate to the + base directory, then to boxbackup\infrastructure\msvc\2005 (or + 2003 if using Visual Studio 2003), + and open any project or solution file in that directory. + + Press F7 to compile Box Backup. If the compilation is + successful, boxbackup\Debug\bbackupd.exe will be + created. +
    + +
    + Install Box Backup + + 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: + + + + boxbackup\Debug\bbackupd.exe + + + + openssl\out32dll\libeay32.dll + + + + 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. + + 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. +
    + +
    + Windows Service + + Box Backup can also run as a Windows service, in which case it + will be automatically started at boot time in the background. To + install this, open a command prompt, and run: + + cd "C:\Program Files\Box Backup" +bbackupd.exe -i + + This should output Box Backup service installed. +
    +
    +
    + + + Compilation and installation by building an RPM on + Linux + + It is very easy to build an RPM of Box Backup on Linux platforms. + This should work on all Red Hat distributions (including Fedora), SuSE, + and probably others too. + + Given that you have the correct development packages installed + simply execute this command (replacing the version number): + + rpmbuild -ta boxbackup-0.00.tgz + + rpmbuild will report where the packages have been written to, and + these can be installed in the normal manner. + + If you have never built an RPM before you should set up a convenient + build area as described in the RPM + book. + + If you wish to customise the package you can find the spec file in + the contrib/rpm directory. + +
    diff --git a/docs/docbook/raidfile-config.xml b/docs/docbook/raidfile-config.xml new file mode 100644 index 00000000..78a3e94c --- /dev/null +++ b/docs/docbook/raidfile-config.xml @@ -0,0 +1,198 @@ + + + + raidfile-config + + 8 + + Box Backup + + Box Backup + + 0.11 + + + + raidfile-config + + Configure Box Backup's RAID files + + + + + raidfile-config + + config-dir + + blocksize + + dir1 dir2 dir3 + + + + + Description + + raidfile-config creates a raidfile.conf file for Box Backup. This + file holds information about the directories used to store backups in. Box + Backup supports userland RAID, in a restricted RAID5 configuration, where + 3 and only 3 'drives' are supported. You can read more about RAID5 (and + other RAID-levels) here. + + + Parameters + + The parameters are as follows: + + + + config-dir + + + The directory path where configuration files are located. + Usually this is /etc/box. + raidfile.conf will be written in this + directory. + + + + + blocksize + + + The block size used for file storage in the system, in + bytes. Using a multiple of the file system block size is a good + strategy. Depending on the size of the files you will be backing + up, this multiple varies. Of course it also depends on the native + block size of your file system. + + + + + dir1 + + + The first directory in the built-in RAID array. + + + + + dir2 + + + The second directory in the built-in RAID array. If you are + not using the built-in RAID functionality, this field should be + ignored. You should not use the built-in RAID if you have a + hardware RAID solution or if you're using another type of software + RAID (like md on Linux). + + + + + dir3 + + + The third directory in the built-in RAID array. The same + notes that apply to dir2 also apply to + dir3. + + + + + Note that there are currently no way to add multiple disk sets to + the raidfile.conf file using command line tools, etc. See + raidfile.conf + + 5 + for details on adding more disks. + + + + + Bugs + + If you find a bug in Box Backup, and you want to let us know about + it, join the mailing + list, and send a description of the problem there. + + To report a bug, give us at least the following information: + + + + The version of Box Backup you are running + + + + The platform you are running on (hardware and OS), for both + client and server. + + + + If possible attach your config files (bbstored.conf, + bbackupd.conf) to the bug report. + + + + Also attach any log file output that helps shed light on the + problem you are seeing. + + + + And last but certainly not least, a description of what you are + seeing, in as much detail as possible. + + + + + + Files + + raidfile-config generates the + raidfile.conf + + 5 + file. + + + + See Also + + + bbstored-config + + 8 + , + bbstored.conf + + 5 + , + raidfile.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/docbook/raidfile.conf.xml b/docs/docbook/raidfile.conf.xml new file mode 100644 index 00000000..7a8b6410 --- /dev/null +++ b/docs/docbook/raidfile.conf.xml @@ -0,0 +1,143 @@ + + + + raidfile.conf + + 5 + + Box Backup + + Box Backup + + 0.11 + + + + raidfile.conf + + Userland RAID for Box Backup + + + + + /etc/box/raidfile.conf + + + + + Description + + The raidfile.conf is usually generated by + raidfile-config + + 8 + but may be manually edited if the store locations move + or if more than one disc set is required. + + + + discX + + + Specifies a set of discs. + + + + SetNumber + + + The set number of the RAID disc, referenced by each + account. + + + + + BlockSize + + + The block size of the file system (usually 2048). + Under BSD with FFS, set this to your file system's + fragment size (most likely an 8th of the block + size). + + + + + Dir0 + + + The first directory in the RAID array. + + + + + Dir1 + + + The second directory in the RAID array. If you do + not wish to use the built-in RAID functionality, this + field should be set to the same as + Dir0. You should not use the built-in + RAID if you have a hardware RAID solution or if you're + using another type of software RAID (like md on + Linux). + + + + + Dir2 + + + The third directory in the RAID array. The same + notes that apply to Dir2 also apply to + Dir3. + + + + + + + + + + Files + + /etc/box/raidfile.conf + + + + See Also + + + raidfile-config + + 8 + , + bbstored.conf + + 5 + + + + + Authors + + + Ben Summers + + + + Per Thomsen + + + + James O'Gorman + + + diff --git a/docs/images/bblogo-alpha.xcf b/docs/images/bblogo-alpha.xcf new file mode 100644 index 00000000..18f494f9 Binary files /dev/null and b/docs/images/bblogo-alpha.xcf differ diff --git a/docs/images/box-alpha.png b/docs/images/box-alpha.png new file mode 100644 index 00000000..10137b7d Binary files /dev/null and b/docs/images/box-alpha.png differ diff --git a/docs/images/box-alpha.xcf b/docs/images/box-alpha.xcf new file mode 100644 index 00000000..8155a679 Binary files /dev/null and b/docs/images/box-alpha.xcf differ diff --git a/docs/tools/generate_except_xml.pl b/docs/tools/generate_except_xml.pl new file mode 100644 index 00000000..38ee9074 --- /dev/null +++ b/docs/tools/generate_except_xml.pl @@ -0,0 +1,74 @@ +#!/usr/bin/perl -w +use strict; + +open (EXCEPT, "< $ARGV[0]") or die "Can't open $ARGV[0]: $!\n"; +open (DOCBOOK, "> $ARGV[1]") or die "Can't open $ARGV[1] for writing: $!\n"; + +print DOCBOOK < + + + Exception codes + +EOD +my $sectionName; +my $sectionNum; +my $sectionDesc; +my $exceptionCode; +my $exceptionShortDesc; +my $exceptionLongDesc; +while() +{ + next if(m/^#/); + chomp; + if(m/^EXCEPTION TYPE (\w+) (\d+)/) + { + $sectionName = ucfirst(lc($1)); + $sectionNum = $2; + if($sectionName ne "Common") + { + $sectionDesc = "the " . $sectionName; + } + else + { + $sectionDesc = "any"; + } + print DOCBOOK < + $sectionName Exceptions ($sectionNum) + + These are exceptions that can occur in $sectionDesc module + of the system. + + +EOD + } + + # The END TYPE line + if(m/^END TYPE$/) + { + print DOCBOOK " \n \n"; + } + + # The actual exceptions + if(m/(\(\d+\/\d+\)) - (\w+ \w+)(?: - )?(.*)$/) + { + $exceptionCode = $1; + $exceptionShortDesc = $2; + $exceptionLongDesc = $3; + + print DOCBOOK " \n "; + print DOCBOOK $exceptionCode . ": " . $exceptionShortDesc . ""; + if($exceptionLongDesc ne "") + { + print DOCBOOK " -- " . $exceptionLongDesc; + } + print DOCBOOK "\n \n"; + } +} + +print DOCBOOK "\n"; + +close EXCEPT; +close DOCBOOK; + diff --git a/docs/xsl-generic/VERSION b/docs/xsl-generic/VERSION new file mode 100644 index 00000000..14b04f23 --- /dev/null +++ b/docs/xsl-generic/VERSION @@ -0,0 +1,113 @@ + + + + + + + + + + + +docbook-xsl +1.72.0 +6549 +$Revision: 7388 $ +$URL: https://docbook.svn.sourceforge.net/svnroot/docbook/trunk/xsl/VERSION $ + + + + + DocBook + XSL Stylesheets + 1.73.2 + + + + + + + +Minor bugfixes + + + + + http://sourceforge.net/projects/docbook/ + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.tar.gz?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.zip?download + http://prdownloads.sourceforge.net/docbook/{DISTRONAME-VERSION}.bz2?download + http://sourceforge.net/project/shownotes.php?release_id={SFRELID} + http://docbook.svn.sourceforge.net/viewvc/docbook/ + http://lists.oasis-open.org/archives/docbook-apps/ + This is a bug-fix update to the 1.73.1 release. + + + + + + + + + + + + + + + + + + + + + You must specify the sf-relid as a parameter. + + + + + + + + + + + + + + + + + + : + + + + + + + + + : + + + + + + + + + : + + + + + diff --git a/docs/xsl-generic/common/af.xml b/docs/xsl-generic/common/af.xml new file mode 100644 index 00000000..a60fe6a3 --- /dev/null +++ b/docs/xsl-generic/common/af.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/am.xml b/docs/xsl-generic/common/am.xml new file mode 100644 index 00000000..10be46ee --- /dev/null +++ b/docs/xsl-generic/common/am.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ምልክቶች +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ar.xml b/docs/xsl-generic/common/ar.xml new file mode 100644 index 00000000..64d6f3bc --- /dev/null +++ b/docs/xsl-generic/common/ar.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/autoidx-kimber.xsl b/docs/xsl-generic/common/autoidx-kimber.xsl new file mode 100644 index 00000000..151d3af2 --- /dev/null +++ b/docs/xsl-generic/common/autoidx-kimber.xsl @@ -0,0 +1,43 @@ + + + + + + +]> + + + + + + + + + + ERROR: the 'kimber' index method requires the + Saxon version 6 or 8 XSLT processor. + + + 1 + + + + + + + + diff --git a/docs/xsl-generic/common/autoidx-kosek.xsl b/docs/xsl-generic/common/autoidx-kosek.xsl new file mode 100644 index 00000000..386e4528 --- /dev/null +++ b/docs/xsl-generic/common/autoidx-kosek.xsl @@ -0,0 +1,150 @@ + + + +]> + + + + + + + + + + ERROR: the 'kosek' index method does not + work with the xsltproc XSLT processor. + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of index grouping letters exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of index grouping letters exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/az.xml b/docs/xsl-generic/common/az.xml new file mode 100644 index 00000000..b1b14c59 --- /dev/null +++ b/docs/xsl-generic/common/az.xml @@ -0,0 +1,666 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +İşarələr +A +a +B +b +C +c +Ç +ç +D +d +E +e +e +e +Ə +ə +G +g +Ğ +ğ +H +h +X +x +I +ı +İ +i +J +j +K +k +Q +q +L +l +M +m +N +n +O +o +Ö +ö +P +p +R +r +S +s +Ş +ş +T +t +U +u +Ü +ü +V +v +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/bg.xml b/docs/xsl-generic/common/bg.xml new file mode 100644 index 00000000..1e6e7a13 --- /dev/null +++ b/docs/xsl-generic/common/bg.xml @@ -0,0 +1,718 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Цифри и знаци +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +П +п +Р +р +С +с +Т +т +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ь +ь +Ю +ю +Я +я +Э +э +Ы +ы +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/bn.xml b/docs/xsl-generic/common/bn.xml new file mode 100644 index 00000000..f31e01c4 --- /dev/null +++ b/docs/xsl-generic/common/bn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/bs.xml b/docs/xsl-generic/common/bs.xml new file mode 100644 index 00000000..341c211d --- /dev/null +++ b/docs/xsl-generic/common/bs.xml @@ -0,0 +1,656 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboli +A +a +B +b +C +c +Ć +ć +Č +č +D +d +Đ +đ +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +R +r +S +s +Š +š +T +t +U +u +V +v +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/ca.xml b/docs/xsl-generic/common/ca.xml new file mode 100644 index 00000000..fa4332e5 --- /dev/null +++ b/docs/xsl-generic/common/ca.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/charmap.xml b/docs/xsl-generic/common/charmap.xml new file mode 100644 index 00000000..6644f3e5 --- /dev/null +++ b/docs/xsl-generic/common/charmap.xml @@ -0,0 +1,185 @@ + + + + + Common » Character-Map Template Reference + + $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the + character-map templates in the DocBook XSL Stylesheets. + + + +These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + + +The character-map templates don’t import or include + any DocBook XSL stylesheet parameters, so the + character-map templates can be used without importing the + whole set of parameters. + + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +apply-character-map +Applies an XSLT character map + + +<xsl:template name="apply-character-map"> +<xsl:param name="content"/> +<xsl:param name="map.contents"/> + ... +</xsl:template> + + + +<para>This template applies an <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">XSLT character map</link>; that is, it causes certain + individual characters to be substituted with strings of one + or more characters. It is useful mainly for replacing + multiple “special” characters or symbols in the same target + content. It uses the value of + <parameter>map.contents</parameter> to do substitution on + <parameter>content</parameter>, and then returns the + modified contents.</para> + + <note> + +<para>This template is a very slightly modified version of + Jeni Tennison’s <function>replace_strings</function> + template in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/StringReplace.html#d9351e13">multiple string replacements</link> section of Dave Pawson’s + <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT FAQ</link>.</para> + + +<para>The <function>apply-string-subst-map</function> + template is essentially the same template as the + <function>apply-character-map</function> template; the + only difference is that in the map that + <function>apply-string-subst-map</function> expects, <tag class="attribute">oldstring</tag> and <tag class="attribute">newstring</tag> attributes are used + instead of <tag class="attribute">character</tag> and <tag class="attribute">string</tag> attributes.</para> + + </note> + </refsect1><refsect1><title>Parameters + + + content + + +The content on which to perform the character-map + substitution. + + + + map.contents + + +A node set of elements, with each element having + the following attributes: + + + + character, a + character to be replaced + + + string, a + string with which to replace character + + + + + + + + + + + + + +read-character-map +Reads in all or part of an XSLT character map + + +<xsl:template name="read-character-map"> +<xsl:param name="use.subset"/> +<xsl:param name="subset.profile"/> +<xsl:param name="uri"/> + ... +</xsl:template> + + + +<para>The XSLT 2.0 specification describes <link xlink:href="http://www.w3.org/TR/xslt20/#character-maps">character maps</link> and explains how they may be used + to allow a specific character appearing in a text or + attribute node in a final result tree to be substituted by + a specified string of characters during serialization. The + <function>read-character-map</function> template provides a + means for reading and using character maps with XSLT + 1.0-based tools.</para> + + +<para>This template reads the character-map contents from + <parameter>uri</parameter> (in full or in part, depending on + the value of the <parameter>use.subset</parameter> + parameter), then passes those contents to the + <function>apply-character-map</function> template, along with + <parameter>content</parameter>, the data on which to perform + the character substitution.</para> + + +<para>Using the character map “in part” means that it uses only + those <tag>output-character</tag> elements that match the + XPath expression given in the value of the + <parameter>subset.profile</parameter> parameter. The current + implementation of that capability here relies on the + <function>evaluate</function> extension XSLT function.</para> + + </refsect1><refsect1><title>Parameters + + + use.subset + + +Specifies whether to use a subset of the character + map instead of the whole map; boolean + 0 or 1 + + + + subset.profile + + +XPath expression that specifies what subset of the + character map to use + + + + uri + + +URI for a character map + + + + + + + + diff --git a/docs/xsl-generic/common/charmap.xsl b/docs/xsl-generic/common/charmap.xsl new file mode 100644 index 00000000..3e0f5d4d --- /dev/null +++ b/docs/xsl-generic/common/charmap.xsl @@ -0,0 +1,221 @@ + + + + + + + Common » Character-Map Template Reference + + $Id: charmap.xsl 7266 2007-08-22 11:58:42Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the + character-map templates in the DocBook XSL Stylesheets. + + These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + The character-map templates don’t import or include + any DocBook XSL stylesheet parameters, so the + character-map templates can be used without importing the + whole set of parameters. + + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + Applies an XSLT character map + + This template applies an XSLT character map; that is, it causes certain + individual characters to be substituted with strings of one + or more characters. It is useful mainly for replacing + multiple “special” characters or symbols in the same target + content. It uses the value of + map.contents to do substitution on + content, and then returns the + modified contents. + + This template is a very slightly modified version of + Jeni Tennison’s replace_strings + template in the multiple string replacements section of Dave Pawson’s + XSLT FAQ. + The apply-string-subst-map + template is essentially the same template as the + apply-character-map template; the + only difference is that in the map that + apply-string-subst-map expects, oldstring and newstring attributes are used + instead of character and string attributes. + + + + + content + + The content on which to perform the character-map + substitution. + + + map.contents + + A node set of elements, with each element having + the following attributes: + + + character, a + character to be replaced + + + string, a + string with which to replace character + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Reads in all or part of an XSLT character map + + The XSLT 2.0 specification describes character maps and explains how they may be used + to allow a specific character appearing in a text or + attribute node in a final result tree to be substituted by + a specified string of characters during serialization. The + read-character-map template provides a + means for reading and using character maps with XSLT + 1.0-based tools. + This template reads the character-map contents from + uri (in full or in part, depending on + the value of the use.subset + parameter), then passes those contents to the + apply-character-map template, along with + content, the data on which to perform + the character substitution. + Using the character map “in part” means that it uses only + those output-character elements that match the + XPath expression given in the value of the + subset.profile parameter. The current + implementation of that capability here relies on the + evaluate extension XSLT function. + + + + use.subset + + Specifies whether to use a subset of the character + map instead of the whole map; boolean + 0 or 1 + + + subset.profile + + XPath expression that specifies what subset of the + character map to use + + + uri + + URI for a character map + + + + + + + + + + + + + + + + + + + + + + + +Error: To process character-map subsets, you must use an XSLT engine +that supports the evaluate() XSLT extension function. Your XSLT engine +does not support it. + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/common.xml b/docs/xsl-generic/common/common.xml new file mode 100644 index 00000000..93ed030b --- /dev/null +++ b/docs/xsl-generic/common/common.xml @@ -0,0 +1,622 @@ + + + + + Common » Base Template Reference + + $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the “base” + set of common templates in the DocBook XSL Stylesheets. + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +is.component +Tests if a given node is a component-level element + + +<xsl:template name="is.component"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The node which is to be tested. + + + + + +Returns + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + + +is.section +Tests if a given node is a section-level element + + +<xsl:template name="is.section"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The node which is to be tested. + + + + + +Returns + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + + +section.level +Returns the hierarchical level of a section + + +<xsl:template name="section.level"> +<xsl:param name="node" select="."/> + ... +</xsl:template> + + + +<para>This template calculates the hierarchical level of a section. +The element <tag>sect1</tag> is at level 1, <tag>sect2</tag> is +at level 2, etc.</para> + + + +<para>Recursive sections are calculated down to the fifth level.</para> + +</refsect1><refsect1><title>Parameters + + +node + + +The section node for which the level should be calculated. +Defaults to the context node. + + + + + +Returns + +The section level, 1, 2, etc. + + + + + + +qanda.section.level +Returns the hierarchical level of a QandASet + + +<xsl:template name="qanda.section.level"/> + + + +<para>This template calculates the hierarchical level of a QandASet. +</para> + +</refsect1><refsect1><title>Returns + +The level, 1, 2, etc. + + + + + + +select.mediaobject +Selects and processes an appropriate media object from a list + + +<xsl:template name="select.mediaobject"> +<xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> + ... +</xsl:template> + + + +<para>This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and processes +the "right" object.</para> + + + +<para>This template relies on a template named +"select.mediaobject.index" to determine which object +in the list is appropriate.</para> + + + +<para>If no acceptable object is located, nothing happens.</para> + +</refsect1><refsect1><title>Parameters + + +olist + + +The node list of potential objects to examine. + + + + + +Returns + +Calls <xsl:apply-templates> on the selected object. + + + + + +select.mediaobject.index +Selects the position of the appropriate media object from a list + + +<xsl:template name="select.mediaobject.index"> +<xsl:param name="olist" select="imageobject|imageobjectco |videoobject|audioobject|textobject"/> +<xsl:param name="count">1</xsl:param> + ... +</xsl:template> + + + +<para>This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and determines +the "right" object. It returns the position of that object +to be used by the calling template.</para> + + + +<para>If the parameter <parameter>use.role.for.mediaobject</parameter> +is nonzero, then it first checks for an object with +a role attribute of the appropriate value. It takes the first +of those. Otherwise, it takes the first acceptable object +through a recursive pass through the list.</para> + + + +<para>This template relies on a template named "is.acceptable.mediaobject" +to determine if a given object is an acceptable graphic. The semantics +of media objects is that the first acceptable graphic should be used. +</para> + + + +<para>If no acceptable object is located, no index is returned.</para> + +</refsect1><refsect1><title>Parameters + + +olist + + +The node list of potential objects to examine. + + + +count + + +The position in the list currently being considered by the +recursive process. + + + + + +Returns + +Returns the position in the original list of the selected object. + + + + + +is.acceptable.mediaobject +Returns '1' if the specified media object is recognized + + +<xsl:template name="is.acceptable.mediaobject"> +<xsl:param name="object"/> + ... +</xsl:template> + + + +<para>This template examines a media object and returns '1' if the +object is recognized as a graphic.</para> + +</refsect1><refsect1><title>Parameters + + +object + + +The media object to consider. + + + + + +Returns + +0 or 1 + + + + + +check.id.unique +Warn users about references to non-unique IDs + + +<xsl:template name="check.id.unique"> +<xsl:param name="linkend"/> + ... +</xsl:template> + + + +<para>If passed an ID in <varname>linkend</varname>, +<function>check.id.unique</function> prints +a warning message to the user if either the ID does not exist or +the ID is not unique.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.check.idref.targets"> +<refnamediv> +<refname>check.idref.targets</refname> +<refpurpose>Warn users about incorrectly typed references</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="check.idref.targets"> +<xsl:param name="linkend"/> +<xsl:param name="element-list"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>If passed an ID in <varname>linkend</varname>, +<function>check.idref.targets</function> makes sure that the element +pointed to by the link is one of the elements listed in +<varname>element-list</varname> and warns the user otherwise.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.copyright.years"> +<refnamediv> +<refname>copyright.years</refname> +<refpurpose>Print a set of years with collapsed ranges</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="copyright.years"> +<xsl:param name="years"/> +<xsl:param name="print.ranges" select="1"/> +<xsl:param name="single.year.ranges" select="0"/> +<xsl:param name="firstyear" select="0"/> +<xsl:param name="nextyear" select="0"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>This template prints a list of year elements with consecutive +years printed as a range. In other words:</para> + + +<screen><year>1992</year> +<year>1993</year> +<year>1994</year></screen> + + +<para>is printed <quote>1992-1994</quote>, whereas:</para> + + +<screen><year>1992</year> +<year>1994</year></screen> + + +<para>is printed <quote>1992, 1994</quote>.</para> + + + +<para>This template assumes that all the year elements contain only +decimal year numbers, that the elements are sorted in increasing +numerical order, that there are no duplicates, and that all the years +are expressed in full <quote>century+year</quote> +(<quote>1999</quote> not <quote>99</quote>) notation.</para> + +</refsect1><refsect1><title>Parameters + + +years + + +The initial set of year elements. + + + +print.ranges + + +If non-zero, multi-year ranges are collapsed. If zero, all years +are printed discretely. + + + +single.year.ranges + + +If non-zero, two consecutive years will be printed as a range, +otherwise, they will be printed discretely. In other words, a single +year range is 1991-1992 but discretely it's +1991, 1992. + + + + + +Returns + +This template returns the formatted list of years. + + + + + +find.path.params +Search in a table for the "best" match for the node + + +<xsl:template name="find.path.params"> +<xsl:param name="node" select="."/> +<xsl:param name="table" select="''"/> +<xsl:param name="location"> + <xsl:call-template name="xpath.location"> + <xsl:with-param name="node" select="$node"/> + </xsl:call-template> + </xsl:param> + ... +</xsl:template> + + + +<para>This template searches in a table for the value that most-closely +(in the typical best-match sense of XSLT) matches the current (element) +node location.</para> + +</refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.string.upper"> +<refnamediv> +<refname>string.upper</refname> +<refpurpose>Converts a string to all uppercase letters</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="string.upper"> +<xsl:param name="string" select="''"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>Given a string, this template does a language-aware conversion +of that string to all uppercase letters, based on the values of the +<literal>lowercase.alpha</literal> and +<literal>uppercase.alpha</literal> gentext keys for the current +locale. It affects only those characters found in the values of +<literal>lowercase.alpha</literal> and +<literal>uppercase.alpha</literal>. All other characters are left +unchanged.</para> + +</refsect1><refsect1><title>Parameters + + +string + + +The string to convert to uppercase. + + + + + + + + + +string.lower +Converts a string to all lowercase letters + + +<xsl:template name="string.lower"> +<xsl:param name="string" select="''"/> + ... +</xsl:template> + + + +<para>Given a string, this template does a language-aware conversion +of that string to all lowercase letters, based on the values of the +<literal>uppercase.alpha</literal> and +<literal>lowercase.alpha</literal> gentext keys for the current +locale. It affects only those characters found in the values of +<literal>uppercase.alpha</literal> and +<literal>lowercase.alpha</literal>. All other characters are left +unchanged.</para> + +</refsect1><refsect1><title>Parameters + + +string + + +The string to convert to lowercase. + + + + + + + + + +select.choice.separator +Returns localized choice separator + + +<xsl:template name="select.choice.separator"/> + + + +<para>This template enables auto-generation of an appropriate + localized "choice" separator (for example, "and" or "or") before + the final item in an inline list (though it could also be useful + for generating choice separators for non-inline lists).</para> + + +<para>It currently works by evaluating a processing instruction + (PI) of the form <?dbchoice choice="foo"?> : + +<itemizedlist> + <listitem> + <simpara>if the value of the <tag>choice</tag> + pseudo-attribute is "and" or "or", returns a localized "and" + or "or"</simpara> + </listitem> + <listitem> + <simpara>otherwise returns the literal value of the + <tag>choice</tag> pseudo-attribute</simpara> + </listitem> + </itemizedlist> + + The latter is provided only as a temporary workaround because the + locale files do not currently have translations for the word + <wordasword>or</wordasword>. So if you want to generate a a + logical "or" separator in French (for example), you currently need + to do this: + <literallayout><?dbchoice choice="ou"?></literallayout> + </para> + + <warning> + +<para>The <tag>dbchoice</tag> processing instruction is + an unfortunate hack; support for it may disappear in the future + (particularly if and when a more appropriate means for marking + up "choice" lists becomes available in DocBook).</para> + + </warning> + </refsect1></refentry> + +<refentry xmlns:xlink="http://www.w3.org/1999/xlink" xml:id="template.evaluate.info.profile"> +<refnamediv> +<refname>evaluate.info.profile</refname> +<refpurpose>Evaluates an info profile</refpurpose> +</refnamediv> +<refsynopsisdiv> +<synopsis><xsl:template name="evaluate.info.profile"> +<xsl:param name="profile"/> +<xsl:param name="info"/> + ... +</xsl:template></synopsis> +</refsynopsisdiv> +<refsect1><title/> + +<para>This template evaluates an "info profile" matching the XPath + expression given by the <parameter>profile</parameter> + parameter. It relies on the XSLT <function>evaluate()</function> + extension function.</para> + + + +<para>The value of the <parameter>profile</parameter> parameter + can include the literal string <literal>$info</literal>. If found + in the value of the <parameter>profile</parameter> parameter, the + literal string <literal>$info</literal> string is replaced with + the value of the <parameter>info</parameter> parameter, which + should be a set of <replaceable>*info</replaceable> nodes; the + expression is then evaluated using the XSLT + <function>evaluate()</function> extension function.</para> + + </refsect1><refsect1><title>Parameters + + + + profile + + +A string representing an XPath expression + + + + + info + + +A set of *info nodes + + + + + + Returns + +Returns a node (the result of evaluating the + profile parameter) + + + + diff --git a/docs/xsl-generic/common/common.xsl b/docs/xsl-generic/common/common.xsl new file mode 100644 index 00000000..0854ea68 --- /dev/null +++ b/docs/xsl-generic/common/common.xsl @@ -0,0 +1,1981 @@ + + + + + + + + Common » Base Template Reference + + $Id: common.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the “base” + set of common templates in the DocBook XSL Stylesheets. + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + + + + + + + +Tests if a given node is a component-level element + + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + +node + +The node which is to be tested. + + + + + + +This template returns '1' if the specified node is a component +(Chapter, Appendix, etc.), and '0' otherwise. + + + + + + + 1 + 0 + + + + + + +Tests if a given node is a section-level element + + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + +node + +The node which is to be tested. + + + + + + +This template returns '1' if the specified node is a section +(Section, Sect1, Sect2, etc.), and '0' otherwise. + + + + + + + 1 + 0 + + + + + + +Returns the hierarchical level of a section + + +This template calculates the hierarchical level of a section. +The element sect1 is at level 1, sect2 is +at level 2, etc. + +Recursive sections are calculated down to the fifth level. + + + + +node + +The section node for which the level should be calculated. +Defaults to the context node. + + + + + + +The section level, 1, 2, etc. + + + + + + + + 1 + 2 + 3 + 4 + 5 + + + 6 + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + 2 + 3 + 4 + 5 + 5 + + + 5 + 4 + 3 + 2 + + + 1 + + + 1 + + + + +Returns the hierarchical level of a QandASet + + +This template calculates the hierarchical level of a QandASet. + + + + +The level, 1, 2, etc. + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + 2 + 3 + + + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + question + answer + qandadiv + qandaset + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [FAMILY Given] + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +[ +] +{ +} + + +[ +] +... + + + | +4pi + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Selects and processes an appropriate media object from a list + + +This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and processes +the "right" object. + +This template relies on a template named +"select.mediaobject.index" to determine which object +in the list is appropriate. + +If no acceptable object is located, nothing happens. + + + + +olist + +The node list of potential objects to examine. + + + + + + +Calls <xsl:apply-templates> on the selected object. + + + + + + + + + + + + + + + + + + + + + +Selects the position of the appropriate media object from a list + + +This template takes a list of media objects (usually the +children of a mediaobject or inlinemediaobject) and determines +the "right" object. It returns the position of that object +to be used by the calling template. + +If the parameter use.role.for.mediaobject +is nonzero, then it first checks for an object with +a role attribute of the appropriate value. It takes the first +of those. Otherwise, it takes the first acceptable object +through a recursive pass through the list. + +This template relies on a template named "is.acceptable.mediaobject" +to determine if a given object is an acceptable graphic. The semantics +of media objects is that the first acceptable graphic should be used. + + +If no acceptable object is located, no index is returned. + + + + +olist + +The node list of potential objects to examine. + + +count + +The position in the list currently being considered by the +recursive process. + + + + + + +Returns the position in the original list of the selected object. + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + 0 + + + + 0 + + + + 1 + + + + 0 + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Returns '1' if the specified media object is recognized + + +This template examines a media object and returns '1' if the +object is recognized as a graphic. + + + + +object + +The media object to consider. + + + + + + +0 or 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 1 + 1 + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + . + + + + + + + + + + + + . + + + + + + + + + + + + + + + + +Warn users about references to non-unique IDs + +If passed an ID in linkend, +check.id.unique prints +a warning message to the user if either the ID does not exist or +the ID is not unique. + + + + + + + + + + + + Error: no ID for constraint linkend: + + . + + + + + + + Warning: multiple "IDs" for constraint linkend: + + . + + + + + + +Warn users about incorrectly typed references + +If passed an ID in linkend, +check.idref.targets makes sure that the element +pointed to by the link is one of the elements listed in +element-list and warns the user otherwise. + + + + + + + + + + + + + + Error: linkend ( + + ) points to " + + " not (one of): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unexpected context in procedure.step.numeration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + 2 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + loweralpha + lowerroman + upperalpha + upperroman + arabic + arabic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + circle + square + disc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Print a set of years with collapsed ranges + + +This template prints a list of year elements with consecutive +years printed as a range. In other words: + +1992
    +1993 +1994]]> + +is printed 1992-1994, whereas: + +1992
    +1994]]> + +is printed 1992, 1994. + +This template assumes that all the year elements contain only +decimal year numbers, that the elements are sorted in increasing +numerical order, that there are no duplicates, and that all the years +are expressed in full century+year +(1999 not 99) notation. + + + + +years + +The initial set of year elements. + + +print.ranges + +If non-zero, multi-year ranges are collapsed. If zero, all years +are printed discretely. + + +single.year.ranges + +If non-zero, two consecutive years will be printed as a range, +otherwise, they will be printed discretely. In other words, a single +year range is 1991-1992 but discretely it's +1991, 1992. + + + + + + +This template returns the formatted list of years. + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + , + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + , + + , + + + + - + + , + + + + + + + + + + + + + + + + +Search in a table for the "best" match for the node + + +This template searches in a table for the value that most-closely +(in the typical best-match sense of XSLT) matches the current (element) +node location. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + +Converts a string to all uppercase letters + + +Given a string, this template does a language-aware conversion +of that string to all uppercase letters, based on the values of the +lowercase.alpha and +uppercase.alpha gentext keys for the current +locale. It affects only those characters found in the values of +lowercase.alpha and +uppercase.alpha. All other characters are left +unchanged. + + + + +string + +The string to convert to uppercase. + + + + + + + + + + + + + + + + + + + + + + + +Converts a string to all lowercase letters + + +Given a string, this template does a language-aware conversion +of that string to all lowercase letters, based on the values of the +uppercase.alpha and +lowercase.alpha gentext keys for the current +locale. It affects only those characters found in the values of +uppercase.alpha and +lowercase.alpha. All other characters are left +unchanged. + + + + +string + +The string to convert to lowercase. + + + + + + + + + + + + + + + + + + + + + + + + Returns localized choice separator + + This template enables auto-generation of an appropriate + localized "choice" separator (for example, "and" or "or") before + the final item in an inline list (though it could also be useful + for generating choice separators for non-inline lists). + It currently works by evaluating a processing instruction + (PI) of the form <?dbchoice choice="foo"?> : + + + if the value of the choice + pseudo-attribute is "and" or "or", returns a localized "and" + or "or" + + + otherwise returns the literal value of the + choice pseudo-attribute + + + The latter is provided only as a temporary workaround because the + locale files do not currently have translations for the word + or. So if you want to generate a a + logical "or" separator in French (for example), you currently need + to do this: + <?dbchoice choice="ou"?> + + + The dbchoice processing instruction is + an unfortunate hack; support for it may disappear in the future + (particularly if and when a more appropriate means for marking + up "choice" lists becomes available in DocBook). + + + + + + + + + + + + + + + + + + + + + + + + + + Evaluates an info profile + + This template evaluates an "info profile" matching the XPath + expression given by the profile + parameter. It relies on the XSLT evaluate() + extension function. + + The value of the profile parameter + can include the literal string $info. If found + in the value of the profile parameter, the + literal string $info string is replaced with + the value of the info parameter, which + should be a set of *info nodes; the + expression is then evaluated using the XSLT + evaluate() extension function. + + + + + profile + + A string representing an XPath expression + + + + info + + A set of *info nodes + + + + + + + Returns a node (the result of evaluating the + profile parameter) + + + + + + + + + + + + + + + + +Error: The "info profiling" mechanism currently requires an XSLT +engine that supports the evaluate() XSLT extension function. Your XSLT +engine does not support it. + + + + + diff --git a/docs/xsl-generic/common/cs.xml b/docs/xsl-generic/common/cs.xml new file mode 100644 index 00000000..b00f83fd --- /dev/null +++ b/docs/xsl-generic/common/cs.xml @@ -0,0 +1,694 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symboly +A +a +Á +á +B +b +C +c +Č +č +D +d +Ď +ď +E +e +É +é +Ě +ě +Ë +ë +F +f +G +g +H +h +Ch +ch +cH +CH +I +i +Í +í +J +j +K +k +L +l +M +m +N +n +Ň +ň +O +o +Ó +ó +Ö +ö +P +p +Q +q +R +r +Ř +ř +S +s +Š +š +T +t +Ť +ť +U +u +Ú +ú +Ů +ů +Ü +ü +V +v +W +w +X +x +Y +y +Ý +ý +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/cy.xml b/docs/xsl-generic/common/cy.xml new file mode 100644 index 00000000..0790f5a2 --- /dev/null +++ b/docs/xsl-generic/common/cy.xml @@ -0,0 +1,1239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +Ch +ch +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +Dd +dd +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +Ff +ff +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +Ng +ng +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +Ll +ll +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Ph +ph +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +Rh +rh +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +Th +th +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/da.xml b/docs/xsl-generic/common/da.xml new file mode 100644 index 00000000..c415b8a2 --- /dev/null +++ b/docs/xsl-generic/common/da.xml @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Æ +æ +Ø +ø +Å +å + + diff --git a/docs/xsl-generic/common/de.xml b/docs/xsl-generic/common/de.xml new file mode 100644 index 00000000..bc2aedf6 --- /dev/null +++ b/docs/xsl-generic/common/de.xml @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbole +A +a +Ä +ä +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +P +p +Q +q +R +r +S +s +T +t +U +u +Ü +ü +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/el.xml b/docs/xsl-generic/common/el.xml new file mode 100644 index 00000000..a0556621 --- /dev/null +++ b/docs/xsl-generic/common/el.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/en.xml b/docs/xsl-generic/common/en.xml new file mode 100644 index 00000000..1aa99856 --- /dev/null +++ b/docs/xsl-generic/common/en.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/entities.ent b/docs/xsl-generic/common/entities.ent new file mode 100644 index 00000000..e6b7b8eb --- /dev/null +++ b/docs/xsl-generic/common/entities.ent @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + normalize.sort.input + + + + + + normalize.sort.output + + +'> diff --git a/docs/xsl-generic/common/eo.xml b/docs/xsl-generic/common/eo.xml new file mode 100644 index 00000000..14a66e15 --- /dev/null +++ b/docs/xsl-generic/common/eo.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/es.xml b/docs/xsl-generic/common/es.xml new file mode 100644 index 00000000..11592e1e --- /dev/null +++ b/docs/xsl-generic/common/es.xml @@ -0,0 +1,670 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Símbolos +A +a +á +Á +B +b +C +c +CH +ch +D +d +E +e +É +é +F +f +G +g +H +h +I +i +Í +í +J +j +K +k +L +l +LL +ll +M +m +N +n +Ñ +ñ +O +o +Ó +ó +P +p +Q +q +R +r +S +s +T +t +U +u +Ú +ú +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/et.xml b/docs/xsl-generic/common/et.xml new file mode 100644 index 00000000..e63621f7 --- /dev/null +++ b/docs/xsl-generic/common/et.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/eu.xml b/docs/xsl-generic/common/eu.xml new file mode 100644 index 00000000..f58f0d4d --- /dev/null +++ b/docs/xsl-generic/common/eu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/fa.xml b/docs/xsl-generic/common/fa.xml new file mode 100644 index 00000000..ac2aed2c --- /dev/null +++ b/docs/xsl-generic/common/fa.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/fi.xml b/docs/xsl-generic/common/fi.xml new file mode 100644 index 00000000..d28802ae --- /dev/null +++ b/docs/xsl-generic/common/fi.xml @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbole +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +Š +š +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Ž +ž +Å +å +Ä +ä +Ö +ö + + diff --git a/docs/xsl-generic/common/fr.xml b/docs/xsl-generic/common/fr.xml new file mode 100644 index 00000000..58826b96 --- /dev/null +++ b/docs/xsl-generic/common/fr.xml @@ -0,0 +1,684 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symboles +A +a +à +À +â + +Æ +æ +B +b +C +c +ç +D +d +E +e +ê +Ê +é +É +è +È +ë +Ë + +F +f +G +g +H +h +I +i +Î +î +Ï +ï +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +Œ +œ +P +p +Q +q +R +r +S +s +T +t +U +u +Ù +ù +Û +û +Ü +ü +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/ga.xml b/docs/xsl-generic/common/ga.xml new file mode 100644 index 00000000..78274b29 --- /dev/null +++ b/docs/xsl-generic/common/ga.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Siombailí +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/gentext.xsl b/docs/xsl-generic/common/gentext.xsl new file mode 100644 index 00000000..e1da36d9 --- /dev/null +++ b/docs/xsl-generic/common/gentext.xsl @@ -0,0 +1,831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .formal + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + object.xref.markup: empty xref template + for linkend=" + + " and @xrefstyle=" + + " + + + + + + + + + + + + + + + + + + + + + + + + + + + Xref is only supported to listitems in an + orderedlist: + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + %n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + Attempt to use %d in gentext with no referrer! + + + + + + + % + + + % + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + labelnumber + + + labelname + + + label + + + + + + + + quotedtitle + + + title + + + + + + + + + + + + + + nopage + + + pagenumber + + + pageabbrev + + + Page + + + page + + + + + + + + + + + nodocname + + + docnamelong + + + docname + + + + + + + + + + + + + + + + + + + + + + %n + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %t + + + + + + %t + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %p + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/gu.xml b/docs/xsl-generic/common/gu.xml new file mode 100644 index 00000000..050483e4 --- /dev/null +++ b/docs/xsl-generic/common/gu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/he.xml b/docs/xsl-generic/common/he.xml new file mode 100644 index 00000000..8e234b8d --- /dev/null +++ b/docs/xsl-generic/common/he.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hi.xml b/docs/xsl-generic/common/hi.xml new file mode 100644 index 00000000..690a2cd2 --- /dev/null +++ b/docs/xsl-generic/common/hi.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hr.xml b/docs/xsl-generic/common/hr.xml new file mode 100644 index 00000000..d8378e4d --- /dev/null +++ b/docs/xsl-generic/common/hr.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/hu.xml b/docs/xsl-generic/common/hu.xml new file mode 100644 index 00000000..be795c7f --- /dev/null +++ b/docs/xsl-generic/common/hu.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/id.xml b/docs/xsl-generic/common/id.xml new file mode 100644 index 00000000..7ad10d0a --- /dev/null +++ b/docs/xsl-generic/common/id.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/insertfile.xsl b/docs/xsl-generic/common/insertfile.xsl new file mode 100644 index 00000000..66bcf410 --- /dev/null +++ b/docs/xsl-generic/common/insertfile.xsl @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/it.xml b/docs/xsl-generic/common/it.xml new file mode 100644 index 00000000..fc473dfe --- /dev/null +++ b/docs/xsl-generic/common/it.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ja.xml b/docs/xsl-generic/common/ja.xml new file mode 100644 index 00000000..aedc1b12 --- /dev/null +++ b/docs/xsl-generic/common/ja.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/kn.xml b/docs/xsl-generic/common/kn.xml new file mode 100644 index 00000000..7128add7 --- /dev/null +++ b/docs/xsl-generic/common/kn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ko.xml b/docs/xsl-generic/common/ko.xml new file mode 100644 index 00000000..02a4ec01 --- /dev/null +++ b/docs/xsl-generic/common/ko.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/l10n.dtd b/docs/xsl-generic/common/l10n.dtd new file mode 100644 index 00000000..1d6f8361 --- /dev/null +++ b/docs/xsl-generic/common/l10n.dtd @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/l10n.xml b/docs/xsl-generic/common/l10n.xml new file mode 100644 index 00000000..23b2636b --- /dev/null +++ b/docs/xsl-generic/common/l10n.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +]> + +⁡ +&am; +&ar; +&az; +&bg; +&bn; +&bs; +&ca; +&cs; +&cy; +&da; +&de; +⪙ +&en; +&eo; +&es; +&et; +&eu; +&fa; +&fi; +&fr; +&ga; +&gu; +&he; +&hi; +&hr; +&hu; +&id; +⁢ +&ja; +&kn; +&ko; +&la; +&lit; +&lv; +&mn; +&nl; +&nn; +&no; +∨ +&pa; +&pl; +&pt; +&pt_br; +&ro; +&ru; +&sk; +&sl; +&sq; +&sr; +&sr_Latn; +&sv; +&ta; +&th; +&tl; +&tr; +&uk; +&vi; +&xh; +&zh_cn; +&zh_tw; + diff --git a/docs/xsl-generic/common/l10n.xsl b/docs/xsl-generic/common/l10n.xsl new file mode 100644 index 00000000..c385a788 --- /dev/null +++ b/docs/xsl-generic/common/l10n.xsl @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _ + + + + + + + + + + + + + + + + + + + + No localization exists for " + + " or " + + ". Using default " + + ". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization of " + + " exists + + + . + + + ; using "en". + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bullet + + + + + + + + + + + + + + + + + + No " + + " localization of dingbat + + exists; using "en". + + + + + + + + + + startquote + + + + + + endquote + + + + + + nestedstartquote + + + + + + nestedendquote + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No " + + " localization exists. + + + + + + + + + + No context named " + + " exists in the " + + " localization. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No template for " + + " (or any of its leaves) exists +in the context named " + + " in the " + + " localization. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + diff --git a/docs/xsl-generic/common/la.xml b/docs/xsl-generic/common/la.xml new file mode 100644 index 00000000..ee1a4ec4 --- /dev/null +++ b/docs/xsl-generic/common/la.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/labels.xsl b/docs/xsl-generic/common/labels.xsl new file mode 100644 index 00000000..5596f1b7 --- /dev/null +++ b/docs/xsl-generic/common/labels.xsl @@ -0,0 +1,869 @@ + + + + + + + + + + +Provides access to element labels + +Processing an element in the +label.markup mode produces the +element label. +Trailing punctuation is not added to the label. + + + + + + . + + + + + + + Request for label of unexpected element: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + label.markup: this can't happen! + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + a + i + A + I + + + + Unexpected numeration: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + +Returns true if $section should be labelled + +Returns true if the specified section should be labelled. +By default, this template returns zero unless +the section level is less than or equal to the value of the +$section.autolabel.max.depth parameter, in +which case it returns +$section.autolabel. +Custom stylesheets may override it to get more selective behavior. + + + + + + + + + + + + + + + 1 + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + Unexpected .autolabel value: + ; using default. + + + + + + + + + +Returns format for autolabel parameters + +Returns format passed as parameter if non zero. Supported + format are 'arabic' or '1', 'loweralpha' or 'a', 'lowerroman' or 'i', + 'upperlapha' or 'A', 'upperroman' or 'I', 'arabicindic' or '١'. + If its not one of these then + returns the default format. + + + + + + diff --git a/docs/xsl-generic/common/lt.xml b/docs/xsl-generic/common/lt.xml new file mode 100644 index 00000000..7ee48f07 --- /dev/null +++ b/docs/xsl-generic/common/lt.xml @@ -0,0 +1,672 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboliai +A +a +Ą +ą +B +b +C +c +Č +č +D +d +E +e +Ę +ę +Ė +ė +F +f +G +g +H +h +I +i +Į +į +Y +y +J +j +K +k +L +l +M +m +N +n +O +o +P +p +R +r +S +s +Š +š +T +t +U +u +Ų +ų +Ū +ū +V +v +Z +z +Ž +ž +Q +q +W +w +X +x + + diff --git a/docs/xsl-generic/common/lv.xml b/docs/xsl-generic/common/lv.xml new file mode 100644 index 00000000..b1eb73a6 --- /dev/null +++ b/docs/xsl-generic/common/lv.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/mn.xml b/docs/xsl-generic/common/mn.xml new file mode 100644 index 00000000..ce24b213 --- /dev/null +++ b/docs/xsl-generic/common/mn.xml @@ -0,0 +1,724 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Тэмдэгтүүд +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ё +ё +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +Ө +ө +П +п +Р +р +С +с +Т +т +У +у +Ү +ү +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ы +ы +Ь +ь +Э +э +Ю +ю +Я +я + + diff --git a/docs/xsl-generic/common/nl.xml b/docs/xsl-generic/common/nl.xml new file mode 100644 index 00000000..41b69ed6 --- /dev/null +++ b/docs/xsl-generic/common/nl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/nn.xml b/docs/xsl-generic/common/nn.xml new file mode 100644 index 00000000..fa1db047 --- /dev/null +++ b/docs/xsl-generic/common/nn.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/no.xml b/docs/xsl-generic/common/no.xml new file mode 100644 index 00000000..27e2279e --- /dev/null +++ b/docs/xsl-generic/common/no.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/olink.xsl b/docs/xsl-generic/common/olink.xsl new file mode 100644 index 00000000..91849263 --- /dev/null +++ b/docs/xsl-generic/common/olink.xsl @@ -0,0 +1,1149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olinks not processed: must specify a + $target.database.document parameter + when using olinks with targetdoc + and targetptr attributes. + + + + + + Olink error: could not open target database ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: cases for targetdoc=' + + ' and targetptr=' + + ' in language ' + + '. + + + + + + + + + + + + + + Olink debug: CaseA matched. + + + + Olink debug: CaseA NOT matched + + + + + + + + + + + + + + + + Olink debug: CaseB matched. + + + + Olink debug: CaseB NOT matched + + + + + + + + + + + + + + + + + Olink debug: CaseC matched. + + + + Olink debug: CaseC NOT matched. + + + + + + + + + + + + + + + + + Olink debug: CaseD matched. + + + + Olink debug: CaseD NOT matched + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: CaseE matched. + + + + Olink debug: CaseE NOT matched. + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink debug: CaseF matched. + + + + Olink debug: CaseF NOT matched. + + + + + + + + + + + + + + Olink debug: CaseB key is the final selection: + + + + + + + + + Olink debug: CaseA key is the final selection: + + + + + + + + + Olink debug: CaseC key is the final selection: + + + + + + + + + Olink debug: CaseD key is the final selection: + + + + + + + + + Olink debug: CaseF key is the final selection: + + + + + + + + + Olink debug: CaseE key is the final selection: + + + + + + + + Olink debug: No case matched for lang ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: cannot compute relative + sitemap path because $current.docid ' + + ' not found in target database. + + + + + + + Olink warning: cannot compute relative + sitemap path without $current.docid parameter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + xrefstyle is ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref-number-and-title + '. Using template without @style. + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref-number + '. Using template without @style. + + + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + ' in context 'xref + '. Using template without @style. + + + + + + Olink error: no gentext template + exists for xrefstyle ' + + ' for element ' + + ' in language ' + + '. Trying '%t'. + + + + + + + + + + + Olink debug: xrefstyle template is ' + + '. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: no generated text for + targetdoc/targetptr/lang = ' + + '. + + ???? + + + + + + + Olink error: no generated text for + targetdoc/targetptr/lang = ' + + '. + + + ???? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Olink error: cannot locate targetdoc in sitemap + + + + + + + / + + + + + + + + + + + ../ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/or.xml b/docs/xsl-generic/common/or.xml new file mode 100644 index 00000000..0c3593f7 --- /dev/null +++ b/docs/xsl-generic/common/or.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pa.xml b/docs/xsl-generic/common/pa.xml new file mode 100644 index 00000000..c54bf9dd --- /dev/null +++ b/docs/xsl-generic/common/pa.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pi.xsl b/docs/xsl-generic/common/pi.xsl new file mode 100644 index 00000000..bab86644 --- /dev/null +++ b/docs/xsl-generic/common/pi.xsl @@ -0,0 +1,346 @@ + + + + + +Common Processing Instruction Reference + + $Id: pi.xsl 7107 2007-07-22 10:22:06Z xmldoc $ + + + + Introduction + This is generated reference documentation for all + user-specifiable processing instructions (PIs) in the + “common” part of the DocBook XSL stylesheets. + + You add these PIs at particular points in a document to + cause specific “exceptions” to formatting/output behavior. To + make global changes in formatting/output behavior across an + entire document, it’s better to do it by setting an + appropriate stylesheet parameter (if there is one). + + + + + + + + Generates a localized choice separator + + Use the dbchoice choice PI to + generate an appropriate localized “choice” separator (for + example, and or or) + before the final item in an inline simplelist + + This PI is a less-than-ideal hack; support for it may + disappear in the future (particularly if and when a more + appropriate means for marking up "choice" lists becomes + available in DocBook). + + + + dbchoice choice="and"|"or"|string" + + + + choice="and" + + generates a localized and separator + + + choice="or" + + generates a localized or separator + + + choice="string" + + generates a literal string separator + + + + + + + + + + choice + + + + + Inserts a date timestamp + + Use the dbtimestamp PI at any point in a + source document to cause a date timestamp (a formatted + string representing the current date and time) to be + inserted in output of the document. + + + dbtimestamp format="formatstring" [padding="0"|"1"] + + + + format="formatstring" + + Specifies format in which the date and time are + output + + For details of the content of the format string, + see Date and time. + + + + padding="0"|"1" + + Specifies padding behavior; if non-zero, padding is is added + + + + + + + + + + + format + + + + + + + + + + + + + + + + + + + padding + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + Timestamp processing requires XSLT processor with EXSLT date support. + + + + + + + Generates delimiters around embedded TeX equations + in output + + Use the dbtex delims PI as a + child of a textobject containing embedded TeX + markup, to cause that markup to be surrounded by + $ delimiter characters in output. + + + dbtex delims="no"|"yes" + + + + dbtex delims="no"|"yes" + + Specifies whether delimiters are output + + + + + + tex.math.delims + + + DBTeXMath + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + 0 + + + + + + + 0 + + + + 0 + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + Timestamp processing requires an XSLT processor with support + for the EXSLT node-set() function. + + + + + + + diff --git a/docs/xsl-generic/common/pl.xml b/docs/xsl-generic/common/pl.xml new file mode 100644 index 00000000..5112a8ee --- /dev/null +++ b/docs/xsl-generic/common/pl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pt.xml b/docs/xsl-generic/common/pt.xml new file mode 100644 index 00000000..d5b32b1e --- /dev/null +++ b/docs/xsl-generic/common/pt.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/pt_br.xml b/docs/xsl-generic/common/pt_br.xml new file mode 100644 index 00000000..56c1c910 --- /dev/null +++ b/docs/xsl-generic/common/pt_br.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á + +â +à +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/refentry.xml b/docs/xsl-generic/common/refentry.xml new file mode 100644 index 00000000..c5e6776b --- /dev/null +++ b/docs/xsl-generic/common/refentry.xml @@ -0,0 +1,781 @@ + + + + + Common » Refentry Metadata Template Reference + + $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the “refentry + metadata” templates in the DocBook XSL Stylesheets. + + +This is not intended to be user documentation. It is provided + for developers writing customization layers for the stylesheets. + + + +Currently, only the manpages stylesheets make use of these + templates. They are, however, potentially useful elsewhere. + + + + + + +get.refentry.metadata +Gathers metadata from a refentry and its ancestors + + +<xsl:template name="get.refentry.metadata"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>Reference documentation for particular commands, functions, + etc., is sometimes viewed in isolation from its greater "context". For + example, users view Unix man pages as, well, individual pages, not as + part of a "book" of some kind. Therefore, it is sometimes necessary to + embed "context" information in output for each <tag>refentry</tag>.</para> + + + +<para>However, one problem is that different users mark up that + context information in different ways. Often (usually), the + context information is not actually part of the content of the + <tag>refentry</tag> itself, but instead part of the content of a + parent or ancestor element to the the <tag>refentry</tag>. And + even then, DocBook provides a variety of elements that users might + potentially use to mark up the same kind of information. One user + might use the <tag>productnumber</tag> element to mark up version + information about a particular product, while another might use + the <tag>releaseinfo</tag> element.</para> + + + +<para>Taking all that in mind, the + <function>get.refentry.metadata</function> template tries to gather + metadata from a <tag>refentry</tag> element and its ancestor + elements in an intelligent and user-configurable way. The basic + mechanism used in the XPath expressions throughout this stylesheet + is to select the relevant metadata from the *info element that is + closest to the actual <tag>refentry</tag> – either on the + <tag>refentry</tag> itself, or on its nearest ancestor.</para> + + + <note> + +<para>The <function>get.refentry.metadata</function> + template is actually just sort of a "driver" template; it + calls other templates that do the actual data collection, + then returns the data as a set.</para> + + </note> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing user preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a node set with the following elements. The + descriptions are verbatim from the man(7) man + page. + + + + title + + +the title of the man page (e.g., MAN) + + + + + section + + +the section number the man page should be placed in (e.g., + 7) + + + + + date + + +the date of the last revision + + + + + source + + +the source of the command + + + + + manual + + +the title of the manual (e.g., Linux + Programmer's Manual) + + + + + + + + + + + +get.refentry.title +Gets title metadata for a refentry + + +<xsl:template name="get.refentry.title"> +<xsl:param name="refname"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + title of the man page (e.g., <literal>MAN</literal>). This differs + from <tag>refname</tag> in that, if the <tag>refentry</tag> has a + <tag>refentrytitle</tag>, we use that as the <tag>title</tag>; + otherwise, we just use first <tag>refname</tag> in the first + <tag>refnamediv</tag> in the source.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + + Returns + +Returns a title node. + + + + +get.refentry.section +Gets section metadata for a refentry + + +<xsl:template name="get.refentry.section"> +<xsl:param name="refname"/> +<xsl:param name="quiet" select="0"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + section number the man page should be placed in (e.g., + <literal>7</literal>)". If we do not find a <tag>manvolnum</tag> + specified in the source, and we find that the <tag>refentry</tag> is + for a function, we use the section number <literal>3</literal> + ["Library calls (functions within program libraries)"]; otherwise, we + default to using <literal>1</literal> ["Executable programs or shell + commands"].</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + quiet + + +If non-zero, no "missing" message is emitted + + + + + + Returns + +Returns a string representing a section number. + + + + +get.refentry.date +Gets date metadata for a refentry + + +<xsl:template name="get.refentry.date"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + date of the last revision". If we cannot find a date in the source, we + generate one.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global stylesheet parameters) + + + + + + Returns + +Returns a date node. + + + + + +get.refentry.source +Gets source metadata for a refentry + + +<xsl:template name="get.refentry.source"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + source of the command", and provides the following examples: + +<itemizedlist> + <listitem> + +<para>For binaries, use something like: GNU, NET-2, SLS + Distribution, MCC Distribution.</para> + + </listitem> + <listitem> + +<para>For system calls, use the version of the kernel that you are + currently looking at: Linux 0.99.11.</para> + + </listitem> + <listitem> + +<para>For library calls, use the source of the function: GNU, BSD + 4.3, Linux DLL 4.4.1.</para> + + </listitem> + </itemizedlist> + + </para> + + + +<para>The <literal>solbook(5)</literal> man page describes + something very much like what <literal>man(7)</literal> calls + "source", except that <literal>solbook(5)</literal> names it + "software" and describes it like this: + <blockquote> + +<para>This is the name of the software product that the topic + discussed on the reference page belongs to. For example UNIX + commands are part of the <literal>SunOS x.x</literal> + release.</para> + + </blockquote> + </para> + + + +<para>In practice, there are many pages that simply have a version + number in the "source" field. So, it looks like what we have is a + two-part field, + <replaceable>Name</replaceable> <replaceable>Version</replaceable>, + where: + +<variablelist> + <varlistentry> + <term>Name</term> + <listitem> + +<para>product name (e.g., BSD) or org. name (e.g., GNU)</para> + + </listitem> + </varlistentry> + <varlistentry> + <term>Version</term> + <listitem> + +<para>version name</para> + + </listitem> + </varlistentry> + </variablelist> + + Each part is optional. If the <replaceable>Name</replaceable> is a + product name, then the <replaceable>Version</replaceable> is probably + the version of the product. Or there may be no + <replaceable>Name</replaceable>, in which case, if there is a + <replaceable>Version</replaceable>, it is probably the version of the + item itself, not the product it is part of. Or, if the + <replaceable>Name</replaceable> is an organization name, then there + probably will be no <replaceable>Version</replaceable>. + </para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a source node. + + + + + +get.refentry.source.name +Gets source-name metadata for a refentry + + +<xsl:template name="get.refentry.source.name"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>A "source name" is one part of a (potentially) two-part + <replaceable>Name</replaceable> <replaceable>Version</replaceable> + source field. For more details, see the documentation for the + <function>get.refentry.source</function> template.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "source name" data. + + + + + +get.refentry.version +Gets version metadata for a refentry + + +<xsl:template name="get.refentry.version"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>A "version" is one part of a (potentially) two-part + <replaceable>Name</replaceable> <replaceable>Version</replaceable> + source field. For more details, see the documentation for the + <function>get.refentry.source</function> template.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "version" data. + + + + + +get.refentry.manual +Gets source metadata for a refentry + + +<xsl:template name="get.refentry.manual"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="prefs"/> + ... +</xsl:template> + + + +<para>The <literal>man(7)</literal> man page describes this as "the + title of the manual (e.g., <citetitle>Linux Programmer's + Manual</citetitle>)". Here are some examples from existing man pages: + +<itemizedlist> + <listitem> + +<para><citetitle>dpkg utilities</citetitle> + (<command>dpkg-name</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>User Contributed Perl Documentation</citetitle> + (<command>GET</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>GNU Development Tools</citetitle> + (<command>ld</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>Emperor Norton Utilities</citetitle> + (<command>ddate</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>Debian GNU/Linux manual</citetitle> + (<command>faked</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>GIMP Manual Pages</citetitle> + (<command>gimp</command>)</para> + + </listitem> + <listitem> + +<para><citetitle>KDOC Documentation System</citetitle> + (<command>qt2kdoc</command>)</para> + + </listitem> + </itemizedlist> + + </para> + + + +<para>The <literal>solbook(5)</literal> man page describes + something very much like what <literal>man(7)</literal> calls + "manual", except that <literal>solbook(5)</literal> names it + "sectdesc" and describes it like this: + <blockquote> + +<para>This is the section title of the reference page; for + example <literal>User Commands</literal>.</para> + + </blockquote> + </para> + + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A set of info nodes (from a refentry + element and its ancestors) + + + + + prefs + + +A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns + +Returns a manual node. + + + + + +get.refentry.metadata.prefs +Gets user preferences for refentry metadata gathering + + +<xsl:template name="get.refentry.metadata.prefs"/> + + + +<para>The DocBook XSL stylesheets include several user-configurable + global stylesheet parameters for controlling <tag>refentry</tag> + metadata gathering. Those parameters are not read directly by the + other <tag>refentry</tag> metadata-gathering + templates. Instead, they are read only by the + <function>get.refentry.metadata.prefs</function> template, + which assembles them into a structure that is then passed to + the other <tag>refentry</tag> metadata-gathering + templates.</para> + + + +<para>So the, <function>get.refentry.metadata.prefs</function> + template is the only interface to collecting stylesheet parameters for + controlling <tag>refentry</tag> metadata gathering.</para> + + </refsect1><refsect1><title>Parameters + +There are no local parameters for this template; however, it + does rely on a number of global parameters. + + Returns + +Returns a manual node. + + + + + +set.refentry.metadata +Sets content of a refentry metadata item + + +<xsl:template name="set.refentry.metadata"> +<xsl:param name="refname"/> +<xsl:param name="info"/> +<xsl:param name="contents"/> +<xsl:param name="context"/> +<xsl:param name="preferred"/> + ... +</xsl:template> + + + +<para>The <function>set.refentry.metadata</function> template is + called each time a suitable source element is found for a certain + metadata field.</para> + + </refsect1><refsect1><title>Parameters + + + + refname + + +The first refname in the refentry + + + + + info + + +A single *info node that contains the selected source element. + + + + + contents + + +A node containing the selected source element. + + + + + context + + +A string describing the metadata context in which the + set.refentry.metadata template was + called: either "date", "source", "version", or "manual". + + + + + + Returns + +Returns formatted contents of a selected source element. + + + diff --git a/docs/xsl-generic/common/refentry.xsl b/docs/xsl-generic/common/refentry.xsl new file mode 100644 index 00000000..75bc84b0 --- /dev/null +++ b/docs/xsl-generic/common/refentry.xsl @@ -0,0 +1,1277 @@ + + + + + + + + + Common » Refentry Metadata Template Reference + + $Id: refentry.xsl 7056 2007-07-17 13:56:09Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the “refentry + metadata” templates in the DocBook XSL Stylesheets. + This is not intended to be user documentation. It is provided + for developers writing customization layers for the stylesheets. + + Currently, only the manpages stylesheets make use of these + templates. They are, however, potentially useful elsewhere. + + + + + + + Gathers metadata from a refentry and its ancestors + + Reference documentation for particular commands, functions, + etc., is sometimes viewed in isolation from its greater "context". For + example, users view Unix man pages as, well, individual pages, not as + part of a "book" of some kind. Therefore, it is sometimes necessary to + embed "context" information in output for each refentry. + + However, one problem is that different users mark up that + context information in different ways. Often (usually), the + context information is not actually part of the content of the + refentry itself, but instead part of the content of a + parent or ancestor element to the the refentry. And + even then, DocBook provides a variety of elements that users might + potentially use to mark up the same kind of information. One user + might use the productnumber element to mark up version + information about a particular product, while another might use + the releaseinfo element. + + Taking all that in mind, the + get.refentry.metadata template tries to gather + metadata from a refentry element and its ancestor + elements in an intelligent and user-configurable way. The basic + mechanism used in the XPath expressions throughout this stylesheet + is to select the relevant metadata from the *info element that is + closest to the actual refentry – either on the + refentry itself, or on its nearest ancestor. + + + The get.refentry.metadata + template is actually just sort of a "driver" template; it + calls other templates that do the actual data collection, + then returns the data as a set. + + + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing user preferences (from global + stylesheet parameters) + + + + + + Returns a node set with the following elements. The + descriptions are verbatim from the man(7) man + page. + + + title + + the title of the man page (e.g., MAN) + + + + section + + the section number the man page should be placed in (e.g., + 7) + + + + date + + the date of the last revision + + + + source + + the source of the command + + + + manual + + the title of the manual (e.g., Linux + Programmer's Manual) + + + + + + + + + + + + <xsl:call-template name="get.refentry.title"> + <xsl:with-param name="refname" select="$refname"/> + </xsl:call-template> + +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    + + + + Gets title metadata for a refentry + + The man(7) man page describes this as "the + title of the man page (e.g., MAN). This differs + from refname in that, if the refentry has a + refentrytitle, we use that as the title; + otherwise, we just use first refname in the first + refnamediv in the source. + + + + + refname + + The first refname in the refentry + + + + + + Returns a title node. + + + + + + + + + + + + + + + + + + Gets section metadata for a refentry + + The man(7) man page describes this as "the + section number the man page should be placed in (e.g., + 7)". If we do not find a manvolnum + specified in the source, and we find that the refentry is + for a function, we use the section number 3 + ["Library calls (functions within program libraries)"]; otherwise, we + default to using 1 ["Executable programs or shell + commands"]. + + + + + refname + + The first refname in the refentry + + + + quiet + + If non-zero, no "missing" message is emitted + + + + + + Returns a string representing a section number. + + + + + + + + + + + + + Note + + meta manvol + + no refentry/refmeta/manvolnum + + + + Note + + meta manvol + + see http://docbook.sf.net/el/manvolnum + + + + + + + + + + Note + + meta manvol + + Setting man section to 3 + + + + + 3 + + + 1 + + + + + + + + + Gets date metadata for a refentry + + The man(7) man page describes this as "the + date of the last revision". If we cannot find a date in the source, we + generate one. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global stylesheet parameters) + + + + + + Returns a date node. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + + meta date + + no date; using generated date + + + + Note + + meta date + + see http://docbook.sf.net/el/date + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Gets source metadata for a refentry + + The man(7) man page describes this as "the + source of the command", and provides the following examples: + + + For binaries, use something like: GNU, NET-2, SLS + Distribution, MCC Distribution. + + + For system calls, use the version of the kernel that you are + currently looking at: Linux 0.99.11. + + + For library calls, use the source of the function: GNU, BSD + 4.3, Linux DLL 4.4.1. + + + + + The solbook(5) man page describes + something very much like what man(7) calls + "source", except that solbook(5) names it + "software" and describes it like this: +
    + This is the name of the software product that the topic + discussed on the reference page belongs to. For example UNIX + commands are part of the SunOS x.x + release. +
    +
    + + In practice, there are many pages that simply have a version + number in the "source" field. So, it looks like what we have is a + two-part field, + Name Version, + where: + + + Name + + product name (e.g., BSD) or org. name (e.g., GNU) + + + + Version + + version name + + + + Each part is optional. If the Name is a + product name, then the Version is probably + the version of the product. Or there may be no + Name, in which case, if there is a + Version, it is probably the version of the + item itself, not the product it is part of. Or, if the + Name is an organization name, then there + probably will be no Version. + +
    + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns a source node. + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warn + + meta source + + no valid fallback for source; leaving empty + + + + + + + + + + Warn + + meta source + + no source fallback specified; leaving empty + + + + + + + + + + Gets source-name metadata for a refentry + + A "source name" is one part of a (potentially) two-part + Name Version + source field. For more details, see the documentation for the + get.refentry.source template. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "source name" data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + source + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + + + source + productname + + + + + + Note + + meta source + + no *info/productname or alternative + + + + Note + + meta source + + see http://docbook.sf.net/el/productname + + + + Note + + meta source + + no refentry/refmeta/refmiscinfo@class=source + + + + Note + + meta source + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + Gets version metadata for a refentry + + A "version" is one part of a (potentially) two-part + Name Version + source field. For more details, see the documentation for the + get.refentry.source template. + + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Depending on what output method is used for the + current stylesheet, either returns a text node or possibly an element + node, containing "version" data. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + version + + + + + + + + version + productnumber + + + + + + + + version + productnumber + + + + + + Note + + meta version + + no *info/productnumber or alternative + + + + Note + + meta version + + see http://docbook.sf.net/el/productnumber + + + + Note + + meta version + + no refentry/refmeta/refmiscinfo@class=version + + + + Note + + meta version + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + Gets source metadata for a refentry + + The man(7) man page describes this as "the + title of the manual (e.g., Linux Programmer's + Manual)". Here are some examples from existing man pages: + + + dpkg utilities + (dpkg-name) + + + User Contributed Perl Documentation + (GET) + + + GNU Development Tools + (ld) + + + Emperor Norton Utilities + (ddate) + + + Debian GNU/Linux manual + (faked) + + + GIMP Manual Pages + (gimp) + + + KDOC Documentation System + (qt2kdoc) + + + + + The solbook(5) man page describes + something very much like what man(7) calls + "manual", except that solbook(5) names it + "sectdesc" and describes it like this: +
    + This is the section title of the reference page; for + example User Commands. +
    +
    + +
    + + + + refname + + The first refname in the refentry + + + + info + + A set of info nodes (from a refentry + element and its ancestors) + + + + prefs + + A node containing users preferences (from global + stylesheet parameters) + + + + + + Returns a manual node. + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + manual + + + + + + + + manual + + + + + + Note + + meta manual + + no titled ancestor of refentry + + + + Note + + meta manual + + no refentry/refmeta/refmiscinfo@class=manual + + + + Note + + meta manual + + see http://docbook.sf.net/el/refmiscinfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warn + + meta manual + + no valid fallback for manual; leaving empty + + + + + + + + + + + Warn + + meta manual + + no manual fallback specified; leaving empty + + + + + + + + + + Gets user preferences for refentry metadata gathering + + The DocBook XSL stylesheets include several user-configurable + global stylesheet parameters for controlling refentry + metadata gathering. Those parameters are not read directly by the + other refentry metadata-gathering + templates. Instead, they are read only by the + get.refentry.metadata.prefs template, + which assembles them into a structure that is then passed to + the other refentry metadata-gathering + templates. + + So the, get.refentry.metadata.prefs + template is the only interface to collecting stylesheet parameters for + controlling refentry metadata gathering. + + + There are no local parameters for this template; however, it + does rely on a number of global parameters. + + + Returns a manual node. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sets content of a refentry metadata item + + The set.refentry.metadata template is + called each time a suitable source element is found for a certain + metadata field. + + + + + refname + + The first refname in the refentry + + + + info + + A single *info node that contains the selected source element. + + + + contents + + A node containing the selected source element. + + + + context + + A string describing the metadata context in which the + set.refentry.metadata template was + called: either "date", "source", "version", or "manual". + + + + + + Returns formatted contents of a selected source element. + + + + + + + + + + + Note + + + + + + Note + + + + no refentry/refmeta/refmiscinfo@class= + + + + + Note + + + + + + + + + +
    diff --git a/docs/xsl-generic/common/ro.xml b/docs/xsl-generic/common/ro.xml new file mode 100644 index 00000000..50bd8dc7 --- /dev/null +++ b/docs/xsl-generic/common/ro.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/ru.xml b/docs/xsl-generic/common/ru.xml new file mode 100644 index 00000000..290a9b1b --- /dev/null +++ b/docs/xsl-generic/common/ru.xml @@ -0,0 +1,720 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +А +а +Б +б +В +в +Г +г +Д +д +Е +е +Ё +ё +Ж +ж +З +з +И +и +Й +й +К +к +Л +л +М +м +Н +н +О +о +П +п +Р +р +С +с +Т +т +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Ш +ш +Щ +щ +Ъ +ъ +Ы +ы +Ь +ь +Э +э +Ю +ю +Я +я + + diff --git a/docs/xsl-generic/common/sk.xml b/docs/xsl-generic/common/sk.xml new file mode 100644 index 00000000..50f7e2cd --- /dev/null +++ b/docs/xsl-generic/common/sk.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sl.xml b/docs/xsl-generic/common/sl.xml new file mode 100644 index 00000000..43d229ea --- /dev/null +++ b/docs/xsl-generic/common/sl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sq.xml b/docs/xsl-generic/common/sq.xml new file mode 100644 index 00000000..66c97b94 --- /dev/null +++ b/docs/xsl-generic/common/sq.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/sr.xml b/docs/xsl-generic/common/sr.xml new file mode 100644 index 00000000..db5db1d9 --- /dev/null +++ b/docs/xsl-generic/common/sr.xml @@ -0,0 +1,714 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Симболи +А +а +Б +б +В +в +Г +г +Д +д +Ђ +ђ +Е +е +Ж +ж +З +з +И +и +Ј +ј +К +к +Л +л +Љ +љ +М +м +Н +н +Њ +њ +О +о +П +п +Р +р +С +с +Т +т +Ћ +ћ +У +у +Ф +ф +Х +х +Ц +ц +Ч +ч +Џ +џ +Ш +ш +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +Q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/sr_Latn.xml b/docs/xsl-generic/common/sr_Latn.xml new file mode 100644 index 00000000..a6e03d0e --- /dev/null +++ b/docs/xsl-generic/common/sr_Latn.xml @@ -0,0 +1,673 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Simboli +A +a +B +b +C +c +Č +č +Ć +ć +D +d + + + +Đ +đ +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +LJ +Lj +lj +M +m +N +n +NJ +Nj +nj +O +o +P +p +Q +Q +R +r +S +s +Š +š +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Ž +ž + + diff --git a/docs/xsl-generic/common/stripns.xsl b/docs/xsl-generic/common/stripns.xsl new file mode 100644 index 00000000..dd1cd7f2 --- /dev/null +++ b/docs/xsl-generic/common/stripns.xsl @@ -0,0 +1,342 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + info + + + objectinfo + + blockinfo + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WARNING: cannot add @xml:base to node + set root element. + Relative paths may not work. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + + + + + + + + + + + Stripping namespace from DocBook 5 document. + + + + Processing stripped document. + + + + + + + + + diff --git a/docs/xsl-generic/common/subtitles.xsl b/docs/xsl-generic/common/subtitles.xsl new file mode 100644 index 00000000..8211c84b --- /dev/null +++ b/docs/xsl-generic/common/subtitles.xsl @@ -0,0 +1,155 @@ + + + + + + + + + + +Provides access to element subtitles + +Processing an element in the +subtitle.markup mode produces the +subtitle of the element. + + + + + + + Request for subtitle of unexpected element: + + + ???SUBTITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/sv.xml b/docs/xsl-generic/common/sv.xml new file mode 100644 index 00000000..1e7adbbe --- /dev/null +++ b/docs/xsl-generic/common/sv.xml @@ -0,0 +1,658 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z +Å +å +Ä +ä +Ö +ö + + diff --git a/docs/xsl-generic/common/ta.xml b/docs/xsl-generic/common/ta.xml new file mode 100644 index 00000000..448136a1 --- /dev/null +++ b/docs/xsl-generic/common/ta.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/table.xsl b/docs/xsl-generic/common/table.xsl new file mode 100644 index 00000000..72d50d38 --- /dev/null +++ b/docs/xsl-generic/common/table.xsl @@ -0,0 +1,514 @@ + + + + + + + + + + + 0: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0: + + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + +Determine the column number in which a given entry occurs + +If an entry has a +colname or +namest attribute, this template +will determine the number of the column in which the entry should occur. +For other entrys, nothing is returned. + + + +entry + +The entry-element which is to be tested. + + + + + + +This template returns the column number if it can be determined, +or 0 (the empty string) + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/targetdatabase.dtd b/docs/xsl-generic/common/targetdatabase.dtd new file mode 100644 index 00000000..2ace1e00 --- /dev/null +++ b/docs/xsl-generic/common/targetdatabase.dtd @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/targets.xsl b/docs/xsl-generic/common/targets.xsl new file mode 100644 index 00000000..8098d9ba --- /dev/null +++ b/docs/xsl-generic/common/targets.xsl @@ -0,0 +1,272 @@ + + + + + + + + + + +Collects information for potential cross reference targets + +Processing the root element in the +collect.targets mode produces +a set of target database elements that can be used by +the olink mechanism to resolve external cross references. +The collection process is controlled by the +collect.xref.targets parameter, which can be +yes to collect targets and process +the document for output, only to +only collect the targets, and no +(default) to not collect the targets and only process the document. + + +A targets.filename parameter must be +specified to receive the output if +collect.xref.targets is +set to yes so as to +redirect the target data to a file separate from the +document output. + + + + + + + + + + + Must specify a $targets.filename parameter when + $collect.xref.targets is set to 'yes'. + The xref targets were not collected. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/common/th.xml b/docs/xsl-generic/common/th.xml new file mode 100644 index 00000000..9728900a --- /dev/null +++ b/docs/xsl-generic/common/th.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/titles.xsl b/docs/xsl-generic/common/titles.xsl new file mode 100644 index 00000000..62e1db56 --- /dev/null +++ b/docs/xsl-generic/common/titles.xsl @@ -0,0 +1,740 @@ + + + + + + + + + + +Provides access to element titles + +Processing an element in the +title.markup mode produces the +title of the element. This does not include the label. + + + + + + + + + + + + + + + + + + + + + + + Request for title of element with no title: + + + + (id=" + + ") + + + (xml:id=" + + ") + + + + + ???TITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + REFENTRY WITHOUT TITLE??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ERROR: glossdiv missing its required title + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + Important + Caution + Warning + Tip + + + + + + + + + + Question + + + + + Answer + + + + + Question + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + XRef to nonexistent id: + + + ??? + + + + + + + + + + Endterm points to nonexistent ID: + + + ??? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/tl.xml b/docs/xsl-generic/common/tl.xml new file mode 100644 index 00000000..b4894f23 --- /dev/null +++ b/docs/xsl-generic/common/tl.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/tr.xml b/docs/xsl-generic/common/tr.xml new file mode 100644 index 00000000..b17b9408 --- /dev/null +++ b/docs/xsl-generic/common/tr.xml @@ -0,0 +1,660 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Semboller +A +a +B +b +C +c +Ç +ç +D +d +E +e +F +f +G +g +Ğ +ğ +H +h +I +ı +İ +i +J +j +K +k +L +l +M +m +N +n +O +o +Ö +ö +P +p +R +r +S +s +Ş +ş +T +t +U +u +Ü +ü +V +v +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/uk.xml b/docs/xsl-generic/common/uk.xml new file mode 100644 index 00000000..17678e55 --- /dev/null +++ b/docs/xsl-generic/common/uk.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/utility.xml b/docs/xsl-generic/common/utility.xml new file mode 100644 index 00000000..ead9419e --- /dev/null +++ b/docs/xsl-generic/common/utility.xml @@ -0,0 +1,259 @@ + + + + + Common » Utility Template Reference + + $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ + + + + + Introduction + +This is technical reference documentation for the + miscellaneous utility templates in the DocBook XSL + Stylesheets. + + + +These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + + +The utility templates don’t import or include any DocBook + XSL stylesheet parameters, so the utility templates can be used + without importing the whole set of parameters. + + + +This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + +log.message +Logs/emits formatted notes and warnings + + +<xsl:template name="log.message"> +<xsl:param name="level"/> +<xsl:param name="source"/> +<xsl:param name="context-desc"/> +<xsl:param name="context-desc-field-length">12</xsl:param> +<xsl:param name="context-desc-padded"> + <xsl:if test="not($context-desc = '')"> + <xsl:call-template name="pad-string"> + <xsl:with-param name="leftRight">right</xsl:with-param> + <xsl:with-param name="padVar" select="substring($context-desc, 1, $context-desc-field-length)"/> + <xsl:with-param name="length" select="$context-desc-field-length"/> + </xsl:call-template> + </xsl:if> + </xsl:param> +<xsl:param name="message"/> +<xsl:param name="message-field-length" select="45"/> +<xsl:param name="message-padded"> + <xsl:variable name="spaces-for-blank-level"> + <!-- * if the level field is blank, we'll need to pad out --> + <!-- * the message field with spaces to compensate --> + <xsl:choose> + <xsl:when test="$level = ''"> + <xsl:value-of select="4 + 2"/> + <!-- * 4 = hard-coded length of comment text ("Note" or "Warn") --> + <!-- * + 2 = length of colon-plus-space separator ": " --> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:variable name="spaces-for-blank-context-desc"> + <!-- * if the context-description field is blank, we'll need --> + <!-- * to pad out the message field with spaces to compensate --> + <xsl:choose> + <xsl:when test="$context-desc = ''"> + <xsl:value-of select="$context-desc-field-length + 2"/> + <!-- * + 2 = length of colon-plus-space separator ": " --> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:variable name="extra-spaces" select="$spaces-for-blank-level + $spaces-for-blank-context-desc"/> + <xsl:call-template name="pad-string"> + <xsl:with-param name="leftRight">right</xsl:with-param> + <xsl:with-param name="padVar" select="substring($message, 1, ($message-field-length + $extra-spaces))"/> + <xsl:with-param name="length" select="$message-field-length + $extra-spaces"/> + </xsl:call-template> + </xsl:param> + ... +</xsl:template> + + + +<para>The <function>log.message</function> template is a utility + template for logging/emitting formatted messages – that is, + notes and warnings, along with a given log “level” and an + identifier for the “source” that the message relates to.</para> + + </refsect1><refsect1><title>Parameters + + + level + + +Text to log/emit in the message-level field to + indicate the message level + (Note or + Warning) + + + + source + + +Text to log/emit in the source field to identify the + “source” to which the notification/warning relates. + This can be any arbitrary string, but because the + message lacks line and column numbers to identify the + exact part of the source document to which it + relates, the intention is that the value you pass + into the source parameter should + give the user some way to identify the portion of + their source document on which to take potentially + take action in response to the log message (for + example, to edit, change, or add content). + + +So the source value should be, + for example, an ID, book/chapter/article title, title + of some formal object, or even a string giving an + XPath expression. + + + + context-desc + + +Text to log/emit in the context-description field to + describe the context for the message. + + + + context-desc-field-length + + +Specifies length of the context-description field + (in characters); default is 12 + + +If the text specified by the + context-desc parameter is longer + than the number of characters specified in + context-desc-field-length, it is + truncated to context-desc-field-length + (12 characters by default). + + +If the specified text is shorter than + context-desc-field-length, + it is right-padded out to + context-desc-field-length (12 by + default). + + +If no value has been specified for the + context-desc parameter, the field is + left empty and the text of the log message begins with + the value of the message + parameter. + + + + message + + +Text to log/emit in the actual message field + + + + message-field-length + + +Specifies length of the message + field (in characters); default is 45 + + + + + + Returns + +Outputs a message (generally, to standard error). + + + + +get.doc.title +Gets a title from the current document + + +<xsl:template name="get.doc.title"/> + + + +<para>The <function>get.doc.title</function> template is a + utility template for returning the first title found in the + current document.</para> + + </refsect1><refsect1><title>Returns + +Returns a string containing some identifying title for the + current document . + + + + +pad-string +Right-pads or left-pads a string out to a certain length + + +<xsl:template name="pad-string"> +<xsl:param name="padChar" select="' '"/> +<xsl:param name="leftRight">left</xsl:param> +<xsl:param name="padVar"/> +<xsl:param name="length"/> + ... +</xsl:template> + + + +<para>This function takes string <parameter>padVar</parameter> and + pads it out in the direction <parameter>rightLeft</parameter> to + the string-length <parameter>length</parameter>, using string + <parameter>padChar</parameter> (a space character by default) as + the padding string (note that <parameter>padChar</parameter> can + be a string; it is not limited to just being a single + character).</para> + + <note> + +<para>This function began as a copy of Nate Austin's + <function>prepend-pad</function> function in the <link xlink:href="http://www.dpawson.co.uk/xsl/sect2/padding.html">Padding + Content</link> section of Dave Pawson's <link xlink:href="http://www.dpawson.co.uk/xsl/index.html">XSLT + FAQ</link>.</para> + + </note> + </refsect1><refsect1><title>Returns + +Returns a (padded) string. + + + diff --git a/docs/xsl-generic/common/utility.xsl b/docs/xsl-generic/common/utility.xsl new file mode 100644 index 00000000..37092b7c --- /dev/null +++ b/docs/xsl-generic/common/utility.xsl @@ -0,0 +1,290 @@ + + + + + + + Common » Utility Template Reference + + $Id: utility.xsl 7101 2007-07-20 15:32:12Z xmldoc $ + + + + + Introduction + This is technical reference documentation for the + miscellaneous utility templates in the DocBook XSL + Stylesheets. + + These templates are defined in a separate file from the set + of “common” templates because some of the common templates + reference DocBook XSL stylesheet parameters, requiring the + entire set of parameters to be imported/included in any + stylesheet that imports/includes the common templates. + The utility templates don’t import or include any DocBook + XSL stylesheet parameters, so the utility templates can be used + without importing the whole set of parameters. + + This is not intended to be user documentation. It is + provided for developers writing customization layers for the + stylesheets. + + + + + + + Logs/emits formatted notes and warnings + + + The log.message template is a utility + template for logging/emitting formatted messages – that is, + notes and warnings, along with a given log “level” and an + identifier for the “source” that the message relates to. + + + + + level + + Text to log/emit in the message-level field to + indicate the message level + (Note or + Warning) + + + source + + Text to log/emit in the source field to identify the + “source” to which the notification/warning relates. + This can be any arbitrary string, but because the + message lacks line and column numbers to identify the + exact part of the source document to which it + relates, the intention is that the value you pass + into the source parameter should + give the user some way to identify the portion of + their source document on which to take potentially + take action in response to the log message (for + example, to edit, change, or add content). + So the source value should be, + for example, an ID, book/chapter/article title, title + of some formal object, or even a string giving an + XPath expression. + + + context-desc + + Text to log/emit in the context-description field to + describe the context for the message. + + + context-desc-field-length + + Specifies length of the context-description field + (in characters); default is 12 + If the text specified by the + context-desc parameter is longer + than the number of characters specified in + context-desc-field-length, it is + truncated to context-desc-field-length + (12 characters by default). + If the specified text is shorter than + context-desc-field-length, + it is right-padded out to + context-desc-field-length (12 by + default). + If no value has been specified for the + context-desc parameter, the field is + left empty and the text of the log message begins with + the value of the message + parameter. + + + message + + Text to log/emit in the actual message field + + + message-field-length + + Specifies length of the message + field (in characters); default is 45 + + + + + + Outputs a message (generally, to standard error). + + + + + + 12 + + + + right + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + right + + + + + + + + + : + + + + : + + + + + + + + + + Gets a title from the current document + + The get.doc.title template is a + utility template for returning the first title found in the + current document. + + + Returns a string containing some identifying title for the + current document . + + + + + + + + + + + + + + + Right-pads or left-pads a string out to a certain length + + This function takes string padVar and + pads it out in the direction rightLeft to + the string-length length, using string + padChar (a space character by default) as + the padding string (note that padChar can + be a string; it is not limited to just being a single + character). + + This function began as a copy of Nate Austin's + prepend-pad function in the Padding + Content section of Dave Pawson's XSLT + FAQ. + + + + Returns a (padded) string. + + + + + + left + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/common/vi.xml b/docs/xsl-generic/common/vi.xml new file mode 100644 index 00000000..9363b8d2 --- /dev/null +++ b/docs/xsl-generic/common/vi.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/xh.xml b/docs/xsl-generic/common/xh.xml new file mode 100644 index 00000000..8d10e300 --- /dev/null +++ b/docs/xsl-generic/common/xh.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/common/zh_cn.xml b/docs/xsl-generic/common/zh_cn.xml new file mode 100644 index 00000000..6a782fca --- /dev/null +++ b/docs/xsl-generic/common/zh_cn.xml @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +其它 +A +a +B +b +C +c +D +d +E +e +F +f +G +g +H +h +I +i +J +j +K +k +L +l +M +m +N +n +O +o +P +p +Q +q +R +r +S +s +T +t +U +u +V +v +W +w +X +x +Y +y +Z +z + + diff --git a/docs/xsl-generic/common/zh_tw.xml b/docs/xsl-generic/common/zh_tw.xml new file mode 100644 index 00000000..db63cdca --- /dev/null +++ b/docs/xsl-generic/common/zh_tw.xml @@ -0,0 +1,1223 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Symbols +A +a +À +à +Á +á +Â +â +Ã +ã +Ä +ä +Å +å +Ā +ā +Ă +ă +Ą +ą +Ǎ +ǎ +Ǟ +ǟ +Ǡ +ǡ +Ǻ +ǻ +Ȁ +ȁ +Ȃ +ȃ +Ȧ +ȧ + + + + + + + + + + + + + + + + + + + + + + + + + + + +B +b +ƀ +Ɓ +ɓ +Ƃ +ƃ + + + + + + +C +c +Ç +ç +Ć +ć +Ĉ +ĉ +Ċ +ċ +Č +č +Ƈ +ƈ +ɕ + + +D +d +Ď +ď +Đ +đ +Ɗ +ɗ +Ƌ +ƌ +Dž +Dz +ȡ +ɖ + + + + + + + + + + +E +e +È +è +É +é +Ê +ê +Ë +ë +Ē +ē +Ĕ +ĕ +Ė +ė +Ę +ę +Ě +ě +Ȅ +ȅ +Ȇ +ȇ +Ȩ +ȩ + + + + + + + + + + + + + + + + + +ế + + + + + + + + +F +f +Ƒ +ƒ + + +G +g +Ĝ +ĝ +Ğ +ğ +Ġ +ġ +Ģ +ģ +Ɠ +ɠ +Ǥ +ǥ +Ǧ +ǧ +Ǵ +ǵ + + +H +h +Ĥ +ĥ +Ħ +ħ +Ȟ +ȟ +ɦ + + + + + + + + + + + +I +i +Ì +ì +Í +í +Î +î +Ï +ï +Ĩ +ĩ +Ī +ī +Ĭ +ĭ +Į +į +İ +Ɨ +ɨ +Ǐ +ǐ +Ȉ +ȉ +Ȋ +ȋ + + + + + + + + +J +j +Ĵ +ĵ +ǰ +ʝ +K +k +Ķ +ķ +Ƙ +ƙ +Ǩ +ǩ + + + + + + +L +l +Ĺ +ĺ +Ļ +ļ +Ľ +ľ +Ŀ +ŀ +Ł +ł +ƚ +Lj +ȴ +ɫ +ɬ +ɭ + + + + + + + + +M +m +ɱ + +ḿ + + + + +N +n +Ñ +ñ +Ń +ń +Ņ +ņ +Ň +ň +Ɲ +ɲ +ƞ +Ƞ +Nj +Ǹ +ǹ +ȵ +ɳ + + + + + + + + +O +o +Ò +ò +Ó +ó +Ô +ô +Õ +õ +Ö +ö +Ø +ø +Ō +ō +Ŏ +ŏ +Ő +ő +Ɵ +Ơ +ơ +Ǒ +ǒ +Ǫ +ǫ +Ǭ +ǭ +Ǿ +ǿ +Ȍ +ȍ +Ȏ +ȏ +Ȫ +ȫ +Ȭ +ȭ +Ȯ +ȯ +Ȱ +ȱ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +P +p +Ƥ +ƥ + + + + +Q +q +ʠ +R +r +Ŕ +ŕ +Ŗ +ŗ +Ř +ř +Ȑ +ȑ +Ȓ +ȓ +ɼ +ɽ +ɾ + + + + + + + + +S +s +Ś +ś +Ŝ +ŝ +Ş +ş +Š +š +Ș +ș +ʂ + + + + + + + + + + +T +t +Ţ +ţ +Ť +ť +Ŧ +ŧ +ƫ +Ƭ +ƭ +Ʈ +ʈ +Ț +ț +ȶ + + + + + + + + + +U +u +Ù +ù +Ú +ú +Û +û +Ü +ü +Ũ +ũ +Ū +ū +Ŭ +ŭ +Ů +ů +Ű +ű +Ų +ų +Ư +ư +Ǔ +ǔ +Ǖ +ǖ +Ǘ +ǘ +Ǚ +ǚ +Ǜ +ǜ +Ȕ +ȕ +Ȗ +ȗ + + + + + + + + + + + + + + + + + + + + + + + + +V +v +Ʋ +ʋ + + + +ṿ +W +w +Ŵ +ŵ + + + + + + + + + + + +X +x + + + + +Y +y +Ý +ý +ÿ +Ÿ +Ŷ +ŷ +Ƴ +ƴ +Ȳ +ȳ + + + + + + + + + + + +Z +z +Ź +ź +Ż +ż +Ž +ž +Ƶ +ƶ +Ȥ +ȥ +ʐ +ʑ + + + + + + + + diff --git a/docs/xsl-generic/highlighting/c-hl.xml b/docs/xsl-generic/highlighting/c-hl.xml new file mode 100644 index 00000000..ad86bb3f --- /dev/null +++ b/docs/xsl-generic/highlighting/c-hl.xml @@ -0,0 +1,105 @@ + + + + + /* + */ + + + + // + + + + # + + + + " + \ + + + + ' + \ + + + + <<< + + + + and + auto + break + case + char + class + __CLASS__ + const + continue + declare + default + do + double + else + enum + exit + extern + __FILE__ + float + for + global + goto + if + include + int + __LINE__ + long + new + or + private + protected + public + register + return + short + signed + sizeof + static + struct + switch + typedef + union + unsigned + void + volatile + while + + + + + \ No newline at end of file diff --git a/docs/xsl-generic/highlighting/common.xsl b/docs/xsl-generic/highlighting/common.xsl new file mode 100644 index 00000000..32f1bbcf --- /dev/null +++ b/docs/xsl-generic/highlighting/common.xsl @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/highlighting/delphi-hl.xml b/docs/xsl-generic/highlighting/delphi-hl.xml new file mode 100644 index 00000000..3fab5c2e --- /dev/null +++ b/docs/xsl-generic/highlighting/delphi-hl.xml @@ -0,0 +1,174 @@ + + + + + { + } + + + + (* + *) + + + + // + + + + ' + + + + + + + and + else + inherited + packed + then + array + end + initialization + procedure + threadvar + as + except + inline + program + to + asm + exports + interface + property + try + begin + file + is + raise + type + case + final + label + record + unit + class + finalization + library + repeat + unsafe + const + finally + mod + resourcestring + until + constructor + for + nil + sealed + uses + destructor + function + not + set + var + dispinterface + goto + object + shl + while + div + if + of + shr + with + do + implementation + or + static + xor + downto + in + out + string + + + at + on + + + absolute + dynamic + local + platform + requires + abstract + export + message + private + resident + assembler + external + name + protected + safecall + automated + far + near + public + stdcall + cdecl + forward + nodefault + published + stored + contains + implements + overload + read + varargs + default + index + override + readonly + virtual + deprecated + inline + package + register + write + dispid + library + pascal + reintroduce + writeonly + + + + + + diff --git a/docs/xsl-generic/highlighting/ini-hl.xml b/docs/xsl-generic/highlighting/ini-hl.xml new file mode 100644 index 00000000..dbebfa7d --- /dev/null +++ b/docs/xsl-generic/highlighting/ini-hl.xml @@ -0,0 +1,43 @@ + + + + + (?m)(;.*)$ + + + + + (?m)^(\[.+\]\s*)$ + + + + + (?m)^(.+=) + + + + + diff --git a/docs/xsl-generic/highlighting/java-hl.xml b/docs/xsl-generic/highlighting/java-hl.xml new file mode 100644 index 00000000..419b7cb8 --- /dev/null +++ b/docs/xsl-generic/highlighting/java-hl.xml @@ -0,0 +1,98 @@ + + + + + /* + */ + + + + // + + + + " + \ + + + + ' + \ + + + + abstract + boolean + break + byte + case + catch + char + class + const + continue + default + do + double + else + extends + final + finally + float + for + goto + if + implements + import + instanceof + int + interface + long + native + new + package + private + protected + public + return + short + static + strictfp + super + switch + synchronized + this + throw + throws + transient + try + void + volatile + while + + + + diff --git a/docs/xsl-generic/highlighting/m2-hl.xml b/docs/xsl-generic/highlighting/m2-hl.xml new file mode 100644 index 00000000..2d2c7693 --- /dev/null +++ b/docs/xsl-generic/highlighting/m2-hl.xml @@ -0,0 +1,86 @@ + + + + + (* + *) + + + + " + + + + ' + + + + and + array + begin + by + case + const + definition + div + do + else + elsif + end + exit + export + for + from + if + implementation + import + in + loop + mod + module + not + of + or + pointer + procedure + qualified + record + repeat + return + set + then + to + type + until + var + while + with + + + + + + diff --git a/docs/xsl-generic/highlighting/myxml-hl.xml b/docs/xsl-generic/highlighting/myxml-hl.xml new file mode 100644 index 00000000..495d4e87 --- /dev/null +++ b/docs/xsl-generic/highlighting/myxml-hl.xml @@ -0,0 +1,131 @@ + + + + + + + A + ABBR + ACRONYM + ADDRESS + APPLET + AREA + B + BASE + BASEFONT + BDO + BIG + BLOCKQUOTE + BODY + BR + BUTTON + CAPTION + CENTER + CITE + CODE + COL + COLGROUP + DD + DEL + DFN + DIR + DIV + DL + DT + EM + FIELDSET + FONT + FORM + FRAME + FRAMESET + H1 + H2 + H3 + H4 + H5 + H6 + HEAD + HR + HTML + I + IFRAME + IMG + INPUT + INS + ISINDEX + KBD + LABEL + LEGEND + LI + LINK + MAP + MENU + META + NOFRAMES + NOSCRIPT + OBJECT + OL + OPTGROUP + OPTION + P + PARAM + PRE + Q + S + SAMP + SCRIPT + SELECT + SMALL + SPAN + STRIKE + STRONG + STYLE + SUB + SUP + TABLE + TBODY + TD + TEXTAREA + TFOOT + TH + THEAD + TITLE + TR + TT + U + UL + VAR + XMP + + + + + xsl: + + + + + diff --git a/docs/xsl-generic/highlighting/php-hl.xml b/docs/xsl-generic/highlighting/php-hl.xml new file mode 100644 index 00000000..7cd0d6a7 --- /dev/null +++ b/docs/xsl-generic/highlighting/php-hl.xml @@ -0,0 +1,127 @@ + + + + + /* + */ + + + + // + + + + # + + + + " + \ + + + + ' + \ + + + + <<< + + + + and + or + xor + __FILE__ + exception + __LINE__ + array + as + break + case + class + const + continue + declare + default + die + do + echo + else + elseif + empty + enddeclare + endfor + endforeach + endif + endswitch + endwhile + eval + exit + extends + for + foreach + function + global + if + include + include_once + isset + list + new + print + require + require_once + return + static + switch + unset + use + var + while + __FUNCTION__ + __CLASS__ + __METHOD__ + final + php_user_filter + interface + implements + extends + public + private + protected + abstract + clone + try + catch + throw + cfunction + old_function + + + + + diff --git a/docs/xsl-generic/highlighting/xslthl-config.xml b/docs/xsl-generic/highlighting/xslthl-config.xml new file mode 100644 index 00000000..7c77f6fc --- /dev/null +++ b/docs/xsl-generic/highlighting/xslthl-config.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/docs/xsl-generic/html/admon.xsl b/docs/xsl-generic/html/admon.xsl new file mode 100644 index 00000000..6a3e9f8e --- /dev/null +++ b/docs/xsl-generic/html/admon.xsl @@ -0,0 +1,132 @@ + + + + + + + + 25 + + + + + + + + + + + + + + + + + + note + warning + caution + tip + important + note + + + + + + + + Note + Warning + Caution + Tip + Important + Note + + + + + + + + + +
    + + + + + + + + + + + + : + + + + + + + + + + +
    + + + + [{$alt}] + + + + + + + + + +
    + +
    +
    +
    + + +
    + + + + + + + + +

    + + +

    +
    + + +
    +
    + + + + + + + +
    diff --git a/docs/xsl-generic/html/annotations.xsl b/docs/xsl-generic/html/annotations.xsl new file mode 100644 index 00000000..f0106320 --- /dev/null +++ b/docs/xsl-generic/html/annotations.xsl @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Note + + + namesp. cut + + + stripped namespace before processing + + + + + + + + Note + + + namesp. cut + + + processing stripped document + + + + + + + + Unable to strip the namespace from DB5 document, + cannot proceed. + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + diff --git a/docs/xsl-generic/html/ebnf.xsl b/docs/xsl-generic/html/ebnf.xsl new file mode 100644 index 00000000..94a66c55 --- /dev/null +++ b/docs/xsl-generic/html/ebnf.xsl @@ -0,0 +1,329 @@ + + + + + + + + +$Id: ebnf.xsl 6910 2007-06-28 23:23:30Z xmldoc $ + +Walsh +Norman +19992000 +Norman Walsh + + +HTML EBNF Reference + + +
    Introduction + +This is technical reference documentation for the DocBook XSL +Stylesheets; it documents (some of) the parameters, templates, and +other elements of the stylesheets. + +This reference describes the templates and parameters relevant +to formatting EBNF markup. + +This is not intended to be user documentation. +It is provided for developers writing customization layers for the +stylesheets, and for anyone who's interested in how it +works. + +Although I am trying to be thorough, this documentation is known +to be incomplete. Don't forget to read the source, too :-) +
    +
    +
    + + + + + + + + + + + + 1 + + + + + + EBNF + + for + + + + + + + + + + + + +
    + + +
    + + + + + + + + + + EBNF productions + +
    +
    +
    + + + + + + + + + + [ + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + Error: no ID for productionrecap linkend: + + . + + + + + + Warning: multiple "IDs" for productionrecap linkend: + + . + + + + + + + + + + + + + + + + | +
    +
    +
    + + + + + + + + + + + + + + + production + + + + + + + + + Non-terminals with no content must point to + production elements in the current document. + + + Invalid xpointer for empty nt: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + /*  + +  */ +
    +
    + + + + + + + + + constraintdef + + + + + + + + + + + + + + + + : + + + + + + + : + + + + + + + + + +  ] + +
    +
    +
    + + +
    + + + +
    +
    + + +

    +
    + + + +
    diff --git a/docs/xsl-generic/html/footnote.xsl b/docs/xsl-generic/html/footnote.xsl new file mode 100644 index 00000000..ca996380 --- /dev/null +++ b/docs/xsl-generic/html/footnote.xsl @@ -0,0 +1,299 @@ + + + + + + + + + + + #ftn. + + + + + + + [ + + + + + ] + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + #ftn. + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ftn. + + + + + + # + + + + +

    + + + + + + + [ + + + + + ] + + +

    +
    + + + + + + ftn. + + + + + + # + + + + + + + [ + + + + + ] + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + +
    +
    + + +
    +
    +

    The following annotations are from this essay. You are seeing + them here because your browser doesn’t support the user-interface + techniques used to make them appear as ‘popups’ on modern browsers.

    +
    + + +
    +
    +
    + + + + + + + + +
    + + +
    +
    + + +
    + + + +
    +
    + + + + Warning: footnote number may not be generated + correctly; + + unexpected as first child of footnote. + +
    + + +
    +
    +
    +
    + + + + + + + + +
    diff --git a/docs/xsl-generic/html/formal.xsl b/docs/xsl-generic/html/formal.xsl new file mode 100644 index 00000000..b264fef9 --- /dev/null +++ b/docs/xsl-generic/html/formal.xsl @@ -0,0 +1,400 @@ + + + + + +1 + + + + + + + + + + +
    + + + + + + + +
    + +
    + + + + + +

    + + +

    +

    + + + + + + + +
    +
    + +
    +
    +
    + + + + + + + + + -float + + + + + + + + + +
    + + + + + + + + + +

    + + + +

    +
    + + + + + +
    +

    + + + + + + + + +

    +

    +
    + + + + + + + + + -float + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Broken table: tr descendent of CALS Table. + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + Broken table: row descendent of HTML table. + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + float: + + ; + + + +
    +
    + +
    diff --git a/docs/xsl-generic/html/glossary.xsl b/docs/xsl-generic/html/glossary.xsl new file mode 100644 index 00000000..92178067 --- /dev/null +++ b/docs/xsl-generic/html/glossary.xsl @@ -0,0 +1,482 @@ + + +%common.entities; +]> + + + + + + + + &setup-language-variable; + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    +
    + + + +
    + + + + +
    +
    + + + + + + + + + + + &setup-language-variable; +
    + + + + + +
    + + + + + + + + + + +
    +
    +
    + + + + + &setup-language-variable; + + +
    + + + +
    + + + + + + + + + + +
    +
    +
    + + +

    + + +

    +
    + + + + + + + + +
    + + + + 0 + 1 + + + + + + + + ( + + ) + + + + + +
    +
    + +
    + + + + 0 + 1 + + + + + + + + ( + + ) + +
    +
    + +
    + + + + 0 + 1 + + + + + +
    +
    +
    + + +
    + + + + , + + + + + , + + + + + , + + + + + + + + + + +
    +

    + + + + + + + + + + + + + + + + + + + + + + + Warning: glosssee @otherterm reference not found: + + + + + + + + + + + + + + + . +

    +
    +
    + + +
    + + +

    + + + + + + + + + + + + + +

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + Warning: glossseealso @otherterm reference not found: + + + + + + + + + + + + . + + + , + + + + + + + + + + &setup-language-variable; + + + + + + + + Warning: processing automatic glossary + without a glossary.collection file. + + + + + + Warning: processing automatic glossary but unable to + open glossary.collection file ' + + ' + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + +
    +
    + + + + + + + + + + &setup-language-variable; + +
    + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + +
    diff --git a/docs/xsl-generic/html/graphics.xsl b/docs/xsl-generic/html/graphics.xsl new file mode 100644 index 00000000..3a03edb6 --- /dev/null +++ b/docs/xsl-generic/html/graphics.xsl @@ -0,0 +1,1489 @@ + + + + + + + + + + + + + + 1 + + + + + + 1 + + + + + +
    + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 0 + + 1 + 0 + + + + + + 1.0 + 1.0 + + + + 1.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + px + + + + + + + + + + + px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + px + + + + + + + + + + + px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: imagemaps not supported + on scaled images + + + + 0 + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + height: + + px + + + + + + + + + + + +
    + + + + + background-color: + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + calspair + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + , + + , + + + + + + + + + + + + Warning: only calspair or + otherunits='imagemap' supported + in imageobjectco + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + middle + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No insertfile extension available. + + + + + + + + + + + + + +
    + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/highlight.xsl b/docs/xsl-generic/html/highlight.xsl new file mode 100644 index 00000000..30f2153e --- /dev/null +++ b/docs/xsl-generic/html/highlight.xsl @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/html-rtf.xsl b/docs/xsl-generic/html/html-rtf.xsl new file mode 100644 index 00000000..5855f64b --- /dev/null +++ b/docs/xsl-generic/html/html-rtf.xsl @@ -0,0 +1,336 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/html.xsl b/docs/xsl-generic/html/html.xsl new file mode 100644 index 00000000..79ce1555 --- /dev/null +++ b/docs/xsl-generic/html/html.xsl @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + # + + + + + + + + + # + + + + + + + + + + + + + + + + + + + bullet + + + + + + + + + bullet + + + © + + + ® + (SM) +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ID recommended on + + + : + + + + ... + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/htmltbl.xsl b/docs/xsl-generic/html/htmltbl.xsl new file mode 100644 index 00000000..5c7e3073 --- /dev/null +++ b/docs/xsl-generic/html/htmltbl.xsl @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/index.xsl b/docs/xsl-generic/html/index.xsl new file mode 100644 index 00000000..e8dfb93e --- /dev/null +++ b/docs/xsl-generic/html/index.xsl @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    +
    +
    + + + + + + + + + + +
    +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + +
    + + + + + + + + + +
    + +
    +
    +
    + + +

    + + +

    +
    + + + + + + + + + +
    + + + + + + + + + + + + +
    + +
    + + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    + + +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    +
    + + +
    + +
    + +
    +
    + +
    +
    +
    +
    + + +
    + +
    +
    + + diff --git a/docs/xsl-generic/html/info.xsl b/docs/xsl-generic/html/info.xsl new file mode 100644 index 00000000..61ade512 --- /dev/null +++ b/docs/xsl-generic/html/info.xsl @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/inline.xsl b/docs/xsl-generic/html/inline.xsl new file mode 100644 index 00000000..a3e10ded --- /dev/null +++ b/docs/xsl-generic/html/inline.xsl @@ -0,0 +1,1439 @@ + + +]> + + + + + + + + + + + + + + + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + + + + XLink to nonexistent id: + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + span + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + , + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + abbr + + + + + + acronym + + + + + + + + + + + + + + + + + + + + + + + + + + http://example.com/cgi-bin/man.cgi? + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: glossary.collection specified, but there are + + automatic glossaries + + + + + + + + + + + + + + + + + + + + + + + + There's no entry for + + in + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Error: no glossentry for glossterm: + + . + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + element + + + + + + + + + + + + + + + + </ + + > + + + & + + ; + + + &# + + ; + + + % + + ; + + + <? + + > + + + <? + + ?> + + + < + + > + + + < + + /> + + + <!-- + + --> + + + + + + + + + + + + + + + + + + + + + + + < + + + + + mailto: + + + + + + > + + + + + + + + + + + + + - + - + - + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + [ + + + + + + + + + + + + + + + + + + + ] + + + [ + + ] + + + + + + + + + + + + +

    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/keywords.xsl b/docs/xsl-generic/html/keywords.xsl new file mode 100644 index 00000000..c12e39fc --- /dev/null +++ b/docs/xsl-generic/html/keywords.xsl @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + , + + + + + diff --git a/docs/xsl-generic/html/lists.xsl b/docs/xsl-generic/html/lists.xsl new file mode 100644 index 00000000..2ada156c --- /dev/null +++ b/docs/xsl-generic/html/lists.xsl @@ -0,0 +1,1103 @@ + + + + + + + + +
    + + + + + + + + + +
      + + + + + + + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + circle + disc + square + + + + + + +
  • + + + list-style-type: + + + + + + + + + + + +
    + +
    +
    + + + +
    +
  • +
    + + + + + + + + + + + + + 1 + a + i + A + I + + + + Unexpected numeration: + + + + + + + +
    + + + + + + + + + + +
      + + + + + + + + + + + + + + + + +
    +
    +
    + + + + + + +
  • + + + + + + + + + + + + + +
    + +
    +
    + + + +
    +
  • +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + +

    +
    +
    +
    + + +
    + + +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + + + + +
    + + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + +
    + +
    +
    + + + +
    +
    + + + + + + + + + + + + + + 1 + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + 1 + + + +
    +
    + + + + + + + + + + + 1 + + + +
    +
    + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + + 1 + + + + + + + + +   + + + + + + + + + + + + + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + 1 + 1 + + 1 + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + before + + + + + + + + + +
    + + + + + 0 + 1 + + + + + + + + + + + + +
      + +
    +
    + +
      + + + + +
    +
    +
    + + + + +
    +
    + + + + + + + + + + + + +
      + +
    +
    + + +
  • + + +
  • +
    + + + +
      + +
    +
    + + +

    + + + +

    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + +
    +
    + + +
    + +
    +
    + + + + + + + + + +
    + + +
    +
    + + + + + + + + +
    + + + + : + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + +

    + + + + +

    + + + + + +
    + +
    + + + + +
    +
    +
    +
    +
    + + + + + + + + + +

    + + + + + + + + +

    +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ??? + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/maketoc.xsl b/docs/xsl-generic/html/maketoc.xsl new file mode 100644 index 00000000..1ba3931e --- /dev/null +++ b/docs/xsl-generic/html/maketoc.xsl @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filename=" + + " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/manifest.xsl b/docs/xsl-generic/html/manifest.xsl new file mode 100644 index 00000000..01faaccf --- /dev/null +++ b/docs/xsl-generic/html/manifest.xsl @@ -0,0 +1,22 @@ + + + + + + + + + + + diff --git a/docs/xsl-generic/html/math.xsl b/docs/xsl-generic/html/math.xsl new file mode 100644 index 00000000..5bb4377c --- /dev/null +++ b/docs/xsl-generic/html/math.xsl @@ -0,0 +1,270 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unsupported TeX math notation: + + + + + + + + + + + + + \nopagenumbers + + + + \bye + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $ + + + + $ + + \vfill\eject + + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $$ + + + + $$ + + \vfill\eject + + + + + + + + + \documentclass{article} + \pagestyle{empty} + \begin{document} + + + + \end{document} + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $ + + + + $ + + \newpage + + + + + + + + + + + + + + + + + + + + + + + + \special{dvi2bitmap outputfile + + } + + $$ + + + + $$ + + \newpage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + 1 + + + + + + diff --git a/docs/xsl-generic/html/oldchunker.xsl b/docs/xsl-generic/html/oldchunker.xsl new file mode 100644 index 00000000..fe6b17c3 --- /dev/null +++ b/docs/xsl-generic/html/oldchunker.xsl @@ -0,0 +1,202 @@ + + + + + + + + + + + + + +Encoding used in generated HTML pages + +This encoding is used in files generated by chunking stylesheet. Currently +only Saxon is able to change output encoding. + + + + + + + + + +Saxon character representation used in generated HTML pages + +This character representation is used in files generated by chunking stylesheet. If +you want to suppress entity references for characters with direct representation +in default.encoding, set this parameter to value native. + + + + + + + + + + + + + + + + + + + + + + + + Chunking isn't supported with + + + + + + + + + + + + + + + Writing + + + for + + + ( + + ) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can't make chunks with + + 's processor. + + + + + + + + + + + + + + + + Writing + + + for + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can't make chunks with + + 's processor. + + + + + + diff --git a/docs/xsl-generic/html/onechunk.xsl b/docs/xsl-generic/html/onechunk.xsl new file mode 100644 index 00000000..527dccfd --- /dev/null +++ b/docs/xsl-generic/html/onechunk.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + +1 + + + + # + + + + + + diff --git a/docs/xsl-generic/html/param.xsl b/docs/xsl-generic/html/param.xsl new file mode 100644 index 00000000..389068fc --- /dev/null +++ b/docs/xsl-generic/html/param.xsl @@ -0,0 +1,412 @@ + + + + + + +.png + +images/ + + margin-left: 0.5in; margin-right: 0.5in; + + + + +/* ====================================================================== + Annotations +*/ + +div.annotation-list { visibility: hidden; + } + +div.annotation-nocss { position: absolute; + visibility: hidden; + } + +div.annotation-popup { position: absolute; + z-index: 4; + visibility: hidden; + padding: 0px; + margin: 2px; + border-style: solid; + border-width: 1px; + width: 200px; + background-color: white; + } + +div.annotation-title { padding: 1px; + font-weight: bold; + border-bottom-style: solid; + border-bottom-width: 1px; + color: white; + background-color: black; + } + +div.annotation-body { padding: 2px; + } + +div.annotation-body p { margin-top: 0px; + padding-top: 0px; + } + +div.annotation-close { position: absolute; + top: 2px; + right: 2px; + } + + + +http://docbook.sourceforge.net/release/script/AnchorPosition.js http://docbook.sourceforge.net/release/script/PopupWindow.js + +http://docbook.sourceforge.net/release/images/annot-open.png + +http://docbook.sourceforge.net/release/images/annot-close.png + +A + +. + + +. +http://docbook.sourceforge.net/release/bibliography/bibliography.xml + + +normal + + +60 +.png + + +15 + +images/callouts/ + + +10 +10102 + + + + + + + + + + + +no + +1 + + + + + left + before + + + + +all +maybe +http://docbook.sourceforge.net/release/images/draft.png +#F5DCB3 + + +::= + + + + + +DocBook Online Help Sample +com.example.help +Example provider +1 + + + + + +1 + + + +figure before +example before +equation before +table before +procedure before +task before + + +kr +40 + + + + + + + + + +appendix toc,title +article/appendix nop +article toc,title +book toc,title,figure,table,example,equation +chapter toc,title +part toc,title +preface toc,title +qandadiv toc +qandaset toc +reference toc,title +sect1 toc +sect2 toc +sect3 toc +sect4 toc +sect5 toc +section toc +set toc,title + + + + +no + + + + + + + + + + + + +.html + +copyright + + + + +text/css +alias.h + + + + + + + +User1 + + +User2 + + + + + + + + + +htmlhelp.chm + + +iso-8859-1 + + + + + +toc.hhc +5 + + +index.hhk +htmlhelp.hhp + +Main + +context.h + + + + + + + + + + + +basic + + + + + + + + +no +iso-8859-1 + + +en + + + + +5 + + +3 + + + + + + HTML.manifest + + + + ++ +.gif + +images/ +1 + + +6in + + + + replace + +no + + + +no +fragid= +.olink +pubid + /cgi-bin/olink +sysid + +0 + +I + +90 +10 + + + + + + + + + + + + + + + +; + + + + + +. +number + + + + + + + + + I + +index + +. +.!?: + +8 + + + + + 0 + #E0E0E0 + + + + + + +0 + + + + + +solid +0.5pt +a + + + +solid +0.5pt + + olinkdb.xml +target.db + + +tex-math-equations.tex + + +dl +2 +8 +_top + + + + + + + + +, +0 + +: + + + + diff --git a/docs/xsl-generic/html/pi.xsl b/docs/xsl-generic/html/pi.xsl new file mode 100644 index 00000000..44e323b4 --- /dev/null +++ b/docs/xsl-generic/html/pi.xsl @@ -0,0 +1,1240 @@ + + + + + +HTML Processing Instruction Reference + + $Id: pi.xsl 7250 2007-08-18 10:19:00Z xmldoc $ + + + + Introduction + This is generated reference documentation for all + user-specifiable processing instructions (PIs) in the DocBook + XSL stylesheets for HTML output. + + You add these PIs at particular points in a document to + cause specific “exceptions” to formatting/output behavior. To + make global changes in formatting/output behavior across an + entire document, it’s better to do it by setting an + appropriate stylesheet parameter (if there is one). + + + + + + + + + Sets background color for an image + + Use the dbhtml background-color PI before or + after an image (graphic, inlinegraphic, + imagedata, or videodata element) as a + sibling to the element, to set a background color for the + image. + + + dbhtml background-color="color" + + + + background-color="color" + + An HTML color value + + + + + + Background color + + + + + + + + + + + + Sets background color on a table row or table cell + + Use the dbhtml bgcolor PI as child of a table row + or cell to set a background color for that table row or cell. + + + dbhtml bgcolor="color" + + + + bgcolor="color" + + An HTML color value + + + + + + Cell background color + + + + + + + + + + + + Specifies cellpadding in table or qandaset output + + Use the dbhtml cellpadding PI as a child of a + table or qandaset to specify the value + for the HTML cellpadding attribute in the + output HTML table. + + + dbhtml cellpadding="number" + + + + cellpadding="number" + + Specifies the cellpadding + + + + + + html.cellpadding + + + Cell spacing and cell padding, + Q and A formatting + + + + + + + + + + + + Specifies cellspacing in table or qandaset output + + Use the dbhtml cellspacing PI as a child of a + table or qandaset to specify the value + for the HTML cellspacing attribute in the + output HTML table. + + + dbhtml cellspacing="number" + + + + cellspacing="number" + + Specifies the cellspacing + + + + + + html.cellspacing + + + Cell spacing and cell padding, + Q and A formatting + + + + + + + + + + + + Set value of the class attribute for a table row + + Use the dbhtml class PI as a child of a + row to specify a class + attribute and value in the HTML output for that row. + + + dbhtml class="name" + + + + class="name" + + Specifies the class name + + + + + + Table styles in HTML output + + + + + + + + + + + + Specifies a directory name in which to write files + + When chunking output, use the dbhtml dir PI + as a child of a chunk source to cause the output of that + chunk to be written to the specified directory; also, use it + as a child of a mediaobject to specify a + directory into which any long-description files for that + mediaobject will be written. + + + dbhtml dir="path" + + + + dir="path" + + Specifies the pathname for the directory + + + + + + base.dir + + + dbhtml dir processing instruction + + + + + + + + + + + + Specifies a filename for a chunk + + When chunking output, use the dbhtml filename + PI as a child of a chunk source to specify a filename for + the output file for that chunk. + + + dbhtml filename="filename" + + + + filename="path" + + Specifies the filename for the file + + + + + + use.id.as.filename + + + dbhtml filenames + + + + + + + + + + + + Specifies presentation style for a funcsynopsis + + Use the dbhtml funcsynopsis-style PI as a child of + a funcprototype or anywhere within a funcprototype + control the presentation style for the funcsynopsis + in output. + + + dbhtml funcsynopsis-style="kr"|"ansi" + + + + funcsynopsis-style="kr" + + Displays the funcprototype in K&R style + + + funcsynopsis-style="ansi" + + Displays the funcprototype in ANSI style + + + + + + funcsynopsis.style + + + + + + + + + + + + Specifies a path to the location of an image file + + Use the dbhtml img.src.path PI before or + after an image (graphic, + inlinegraphic, imagedata, or + videodata element) as a sibling to the element, + to specify a path to the location of the image; in HTML + output, the value specified for the + img.src.path attribute is prepended to the + filename. + + + dbhtml img.src.path="path" + + + + img.src.path="path" + + Specifies the pathname to prepend to the name of the image file + + + + + + img.src.path + + + Using fileref + + + + + + + + + + + + Specifies the label width for a qandaset + + Use the dbhtml label-width PI as a child of a + qandaset to specify the width of labels. + + + dbhtml label-width="width" + + + + label-width="width" + + Specifies the label width (including units) + + + + + + Q and A formatting + + + + + + + + + + + + Specifies interval for lines numbers in verbatims + + Use the dbhtml linenumbering.everyNth PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the interval at which lines are numbered. + + + dbhtml linenumbering.everyNth="N" + + + + linenumbering.everyNth="N" + + Specifies numbering interval; a number is output + before every Nth line + + + + + + linenumbering.everyNth + + + Line numbering + + + + + + + + + + + + Specifies separator text for line numbers in verbatims + + Use the dbhtml linenumbering.separator PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the separator text output between the line numbers and content. + + + dbhtml linenumbering.separator="text" + + + + linenumbering.separator="text" + + Specifies the text (zero or more characters) + + + + + + linenumbering.separator + + + Line numbering + + + + + + + + + + + + Specifies width for line numbers in verbatims + + Use the dbhtml linenumbering.width PI as a child + of a “verbatim” element – programlisting, + screen, synopsis — to specify + the width set aside for line numbers. + + + dbhtml linenumbering.width="width" + + + + linenumbering.width="width" + + Specifies the width (inluding units) + + + + + + linenumbering.width + + + Line numbering + + + + + + + + + + + + Specifies presentation style for a variablelist or + segmentedlist + + Use the dbhtml list-presentation PI as a child of + a variablelist or segmentedlist to + control the presentation style for the list (to cause it, for + example, to be displayed as a table). + + + dbhtml list-presentation="list"|"table" + + + + list-presentation="list" + + Displays the list as a list + + + list-presentation="table" + + Displays the list as a table + + + + + + + + variablelist.as.table + + + segmentedlist.as.table + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the width of a variablelist or simplelist + + Use the dbhtml list-width PI as a child of a + variablelist or a simplelist presented + as a table, to specify the output width. + + + dbhtml list-width="width" + + + + list-width="width" + + Specifies the output width (including units) + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the height for a table row + + Use the dbhtml row-height PI as a child of a + row to specify the height of the row. + + + dbhtml row-height="height" + + + + row-height="height" + + Specifies the label height (including units) + + + + + + Row height + + + + + + + + + + + + (obsolete) Sets the starting number on an ordered list + + This PI is obsolete. The intent of + this PI was to provide a means for setting a specific starting + number for an ordered list. Instead of this PI, set a value + for the override attribute on the first + listitem in the list; that will have the same + effect as what this PI was intended for. + + + dbhtml start="character" + + + + start="character" + + Specifies the character to use as the starting + number; use 0-9, a-z, A-Z, or lowercase or uppercase + Roman numerals + + + + + + List starting number + + + + + + + + + + + + Specifies summary for table, variablelist, segmentedlist, or qandaset output + + Use the dbhtml table-summary PI as a child of + a table, variablelist, + segmentedlist, or qandaset to specify + the text for the HTML summary attribute + in the output HTML table. + + + dbhtml table-summary="text" + + + + table-summary="text" + + Specifies the summary text (zero or more characters) + + + + + + Variable list formatting in HTML, + Table summary text + + + + + + + + + + + + Specifies the width for a table + + Use the dbhtml table-width PI as a child of a + table to specify the width of the table in + output. + + + dbhtml table-width="width" + + + + table-width="width" + + Specifies the table width (including units or as a percentage) + + + + + + default.table.width + + + Table width + + + + + + + + + + + + Sets character formatting for terms in a variablelist + + Use the dbhtml term-presentation PI as a child + of a variablelist to set character formatting for + the term output of the list. + + + dbhtml term-presentation="bold"|"italic"|"bold-italic" + + + + term-presentation="bold" + + Specifies that terms are displayed in bold + + + term-presentation="italic" + + Specifies that terms are displayed in italic + + + term-presentation="bold-italic" + + Specifies that terms are displayed in bold-italic + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies separator text among terms in a varlistentry + + Use the dbhtml term-separator PI as a child + of a variablelist to specify the separator text + among term instances. + + + dbhtml term-separator="text" + + + + term-separator="text" + + Specifies the text (zero or more characters) + + + + + + variablelist.term.separator + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies the term width for a variablelist + + Use the dbhtml term-width PI as a child of a + variablelist to specify the width for + term output. + + + dbhtml term-width="width" + + + + term-width="width" + + Specifies the term width (including units) + + + + + + Variable list formatting in HTML + + + + + + + + + + + + Specifies whether a TOC should be generated for a qandaset + + Use the dbhtml toc PI as a child of a + qandaset to specify whether a table of contents + (TOC) is generated for the qandaset. + + + dbhtml toc="0"|"1" + + + + toc="0" + + If zero, no TOC is generated + + + toc="1" + + If 1 (or any non-zero value), + a TOC is generated + + + + + + Q and A list of questions, + Q and A formatting + + + + + + + + + + + + Generates a hyperlinked list of commands + + Use the dbcmdlist PI as the child of any + element (for example, refsynopsisdiv) containing multiple + cmdsynopsis instances; a hyperlinked navigational + “command list” will be generated at the top of output for that + element, enabling users to quickly jump + to each command synopsis. + + + dbcmdlist + + + [No parameters] + + + + + + No cmdsynopsis elements matched dbcmdlist PI, perhaps it's nested too deep? + + +
    + + + +
    +
    + + + Generates a hyperlinked list of functions + + Use the dbfunclist PI as the child of any + element (for example, refsynopsisdiv) containing multiple + funcsynopsis instances; a hyperlinked + navigational “function list” will be generated at the top of + output for that element, enabling users to quickly + jump to to each function synopsis. + + + dbfunclist + + + [No parameters] + + + + + + No funcsynopsis elements matched dbfunclist PI, perhaps it's nested too deep? + + +
    + + + +
    +
    + + + Copies an external well-formed HTML/XML file into current doc + + Use the dbhtml-include href PI anywhere in a + document to cause the contents of the file referenced by the + href pseudo-attribute to be copied/inserted “as + is” into your HTML output at the point in document order + where the PI occurs in the source. + + The referenced file may contain plain text (as long as + it is “wrapped” in an html element — see the + note below) or markup in any arbitrary vocabulary, + including HTML — but it must conform to XML + well-formedness constraints (because the feature in XSLT + 1.0 for opening external files, the + document() function, can only handle + files that meet XML well-formedness constraints). + Among other things, XML well-formedness constraints + require a document to have a single root + element. So if the content you want to + include is plain text or is markup that does + not have a single root element, + wrap the content in an + html element. The stylesheets will + strip out that surrounding html “wrapper” when + they find it, leaving just the content you want to + insert. + + + + dbhtml-include href="URI" + + + + href="URI" + + Specifies the URI for the file to include; the URI + can be, for example, a remote http: + URI, or a local filesystem file: + URI + + + + + + textinsert.extension + + + Inserting external HTML code, + External code files + + + + + + + href + + + + + + + + + + + + + + + + + + + + ERROR: dbhtml-include processing instruction + href has no content. + + + + + + + ERROR: dbhtml-include processing instruction has + missing or empty href value. + + + + + + + + + + + + filename + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + # + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    + + + + + + + + + + + + + + + +
    + + + # + + + + + + + + + + + + + + + + + + +
    + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + / + + + + / + + + + + + + Sets topic name and topic id for context-sensitive HTML Help + + Use the dbhh PI as a child of components + that should be used as targets for context-sensitive help requests. + + + dbhh topicname="name" topicid="id" + + + + topicname="name" + + Specifies a unique string constant that identifies a help topic + + + topicid="id" + + Specifies a unique integer value for the topicname string + + + + + + Context-sensitive help + + + + +
    diff --git a/docs/xsl-generic/html/profile-chunk-code.xsl b/docs/xsl-generic/html/profile-chunk-code.xsl new file mode 100644 index 00000000..364bcc95 --- /dev/null +++ b/docs/xsl-generic/html/profile-chunk-code.xsl @@ -0,0 +1,609 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + bk + + + + + + + + + + + + + + + ar + + + + + + + + + + + + + + + pr + + + + + + + + + + + + + + + ch + + + + + + + + + + + + + + + ap + + + + + + + + + + + + + + + + + + + pt + + + + + + + + + + + + + + + + + + + rn + + + + + + + + + + + + + + + + + + re + + + + + + + + + + + + + + + + + + + co + + + + + + + + + + + s + + + + + + + + + + + + + + + + + + + bi + + + + + + + + + + + + + + + + + + + go + + + + + + + + + + + + + + + + + + + ix + + + + + + + + si + + + + + + + + chunk-filename-error- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document + + + + + + + + + + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/profile-chunk.xsl b/docs/xsl-generic/html/profile-chunk.xsl new file mode 100644 index 00000000..02920b12 --- /dev/null +++ b/docs/xsl-generic/html/profile-chunk.xsl @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + diff --git a/docs/xsl-generic/html/profile-docbook.xsl b/docs/xsl-generic/html/profile-docbook.xsl new file mode 100644 index 00000000..e680eede --- /dev/null +++ b/docs/xsl-generic/html/profile-docbook.xsl @@ -0,0 +1,411 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Element + + in namespace ' + + ' encountered + + in + + + , but no template matches. + + + + < + + > + + </ + + > + + + + + + + + + white + black + #0000FF + #840084 + #0000FF + + + + + + + + + + <xsl:copy-of select="$title"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Note: namesp. cut : stripped namespace before processingNote: namesp. cut : processing stripped document + + + + + + + + + + + + + + + + + + ID ' + + ' not found in document. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + diff --git a/docs/xsl-generic/html/profile-onechunk.xsl b/docs/xsl-generic/html/profile-onechunk.xsl new file mode 100644 index 00000000..325b8b12 --- /dev/null +++ b/docs/xsl-generic/html/profile-onechunk.xsl @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + +1 + + + + # + + + + + + diff --git a/docs/xsl-generic/html/qandaset.xsl b/docs/xsl-generic/html/qandaset.xsl new file mode 100644 index 00000000..0bc45773 --- /dev/null +++ b/docs/xsl-generic/html/qandaset.xsl @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + + + + + + + +
    + + + + + + + + + + +
    + + +
    +
    + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + +
    + + + + +
    + + + +
    + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1% + + + + + + +
    +
    + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/refentry.xsl b/docs/xsl-generic/html/refentry.xsl new file mode 100644 index 00000000..bce630b9 --- /dev/null +++ b/docs/xsl-generic/html/refentry.xsl @@ -0,0 +1,309 @@ + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + +

    +
    + + + + +
    + + + + + + +
    +
    +
    +
    + + + + + + +
    +
    + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + +
    + + + + + + + + + + + +

    + + + +

    +
    + +

    + + + + + + + + +

    +
    +
    + +

    + +

    +
    +
    + + + + + + , + + + + + + + + + em-dash + + + + + + + + + + + + +

    + + + + : + + + +

    +
    +
    + + +
    + + + + + +

    + + + + + + + + + + +

    + +
    +
    + + + + + + + + + + + +
    + + + + + + + + + + + +
    +
    + + + + + + 0 + 1 + + + + 6 + + + + + + + + + + + + +

    + +

    +
    + + + +

    + +

    +
    + + + +

    + +

    +
    + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/sections.xsl b/docs/xsl-generic/html/sections.xsl new file mode 100644 index 00000000..efa45298 --- /dev/null +++ b/docs/xsl-generic/html/sections.xsl @@ -0,0 +1,622 @@ + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + 1 + 2 + 3 + 4 + 5 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + +
    + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6 + + + + + + + + + + clear: both + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + 1 + + + + + + + 2 + 3 + 4 + 5 + 6 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/docs/xsl-generic/html/synop.xsl b/docs/xsl-generic/html/synop.xsl new file mode 100644 index 00000000..1d52c520 --- /dev/null +++ b/docs/xsl-generic/html/synop.xsl @@ -0,0 +1,1596 @@ + + +]> + + + + + + + + + + + +
    + +

    + + + + + + + + + + + + +

    +
    +
    + + +
    + + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    + + + + + + + + + + + ( + + ) + +   + + + + + + + + + + + + +

    + + + + + ( + + ) + + + +

    +
    + + + + + + + + + + + + + + + + + +
    +    
    +    
    +  
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    + + +
    + +
    +

    +
    + + + + + + ( + + + + + + + + + + + + + + + + ) + ; + + + + ... + ) + ; + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + +
    + + + + ; +
    + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + + + + + + + + + +
    + +
     
    + + + + padding-bottom: 1em + + +
    +
    +
    + + + + + + ( + + + + + + + + + + + + + + + + + ) + ; + +   + + + + + ... + ) + ; + +   + + + + + + + + , + + + ) + ; + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + ; + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + ; + + + + + + +

    + +

    +
    + + + + + + ( + + + + + + + + + + + + + + + + void) + ; + + + + ... + ) + ; + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + + + + + + padding-bottom: 1em + + + + + + + + + + + +
    + +
     
    +
    + + + + + + ( + + + + + + + + + + + + + + + + + void) + ; + +   + + + + + ... + ) + ; + +   + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + , + + + ) + ; + + + + + + + +   + + + + + + + , + + + ) + ; + + + + + + + + + + + + + + + + + + + + + + + + ( + + ) + + + + +java + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Unrecognized language on + + : + + + + + + + + + + + +
    +
    +
    + + + + + +
    +    
    +    
    +    
    +       extends
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + + , + + + + + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + 0 + + , +
    + + +   + + + +
    + + + + +
    + + + + + + + + + + + + + + +    + + + + + + + + + + + + + + + ( + + + + ) + +
    +     throws  + +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    
    +    
    +      : 
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + +    + + + + + + + + + + ( + + ) + +
    +     throws  + +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    interface 
    +    
    +    
    +      : 
    +      
    +      
    +        
    +      +
    +
    + + implements + + +
    +      +
    +
    + + throws + + +  { +
    + + } +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + +    + + + + + + + + + + ( + + ) + +
    +     raises( + + ) +
    + + + + + ; +
    + +
    + + + + +
    +    
    +    package 
    +    
    +    ;
    +    
    + + + @ISA = ( + + ); +
    +
    + + +
    +
    + + + + + + + + , + + + + + + + + + + + + +   + + + + + + + , + + + + + + + + + + , + + + + + + + + + + , + + + + + + + + + + + +    + + + ; + + + + + + + + +   + + + + + + + +   + + + + + + + + + + + + + + + void  + + + + + + + + + + + + + , + + + + + + + + + + + + + + + + + + + + + + sub + + + { ... }; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/table.xsl b/docs/xsl-generic/html/table.xsl new file mode 100644 index 00000000..9e85d6d0 --- /dev/null +++ b/docs/xsl-generic/html/table.xsl @@ -0,0 +1,1120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + border- + + : + + + + + + ; + + + + + border- + + -width: + + ; + + + + border- + + -style: + + ; + + + + border- + + -color: + + ; + + + + + + + + + + + Error: CALS tables must specify the number of columns. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 100% + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + border-collapse: collapse; + + + + + + + + + + + + + + + + + border: none; + + + + + border-collapse: collapse; + + + + + + + 0 + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + 100% + + + + + + + + + + + + + + + + + + + + + + + + No convertLength function available. + + + + + + + + + + + + + + + + + + + + + + + + + + No adjustColumnWidths function available. + + + + + + + + + + + + + + + + + + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Warning: overlapped row contains content! + + + This row intentionally left blank + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + th + th + td + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + : + + + + + + + + 0: + + + + + + + + + + + + + + + 0 + + : + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + 1 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    + diff --git a/docs/xsl-generic/html/task.xsl b/docs/xsl-generic/html/task.xsl new file mode 100644 index 00000000..d4d22f60 --- /dev/null +++ b/docs/xsl-generic/html/task.xsl @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + before + + + + + + + + +
    + + + + + + + + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    diff --git a/docs/xsl-generic/html/titlepage.templates.xml b/docs/xsl-generic/html/titlepage.templates.xml new file mode 100644 index 00000000..664dc14e --- /dev/null +++ b/docs/xsl-generic/html/titlepage.templates.xml @@ -0,0 +1,662 @@ + + + + + + + + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="set" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="book" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="part" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="division.title" + param:node="ancestor-or-self::part[1]"/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="partintro" t:wrapper="div"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="reference" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <hr/> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="refentry" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> +<!-- uncomment this if you want refentry titlepages + <title t:force="1" + t:named-template="refentry.title" + param:node="ancestor-or-self::refentry[1]"/> +--> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator/> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + + <t:titlepage t:element="dedication" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::dedication[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="preface" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="chapter" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="appendix" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="section" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect1" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect2" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect3" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect4" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="sect5" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<t:titlepage t:element="simplesect" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title/> + <subtitle/> + <corpauthor/> + <authorgroup/> + <author/> + <othercredit/> + <releaseinfo/> + <copyright/> + <legalnotice/> + <pubdate/> + <revision/> + <revhistory/> + <abstract/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + <xsl:if test="count(parent::*)='0'"><hr/></xsl:if> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="bibliography" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::bibliography[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="glossary" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::glossary[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="index" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::index[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +<t:titlepage t:element="setindex" t:wrapper="div" class="titlepage"> + <t:titlepage-content t:side="recto"> + <title + t:force="1" + t:named-template="component.title" + param:node="ancestor-or-self::setindex[1]"/> + <subtitle/> + </t:titlepage-content> + + <t:titlepage-content t:side="verso"> + </t:titlepage-content> + + <t:titlepage-separator> + </t:titlepage-separator> + + <t:titlepage-before t:side="recto"> + </t:titlepage-before> + + <t:titlepage-before t:side="verso"> + </t:titlepage-before> +</t:titlepage> + +<!-- ==================================================================== --> + +</t:templates> diff --git a/docs/xsl-generic/html/titlepage.templates.xsl b/docs/xsl-generic/html/titlepage.templates.xsl new file mode 100644 index 00000000..5fef8eca --- /dev/null +++ b/docs/xsl-generic/html/titlepage.templates.xsl @@ -0,0 +1,3622 @@ +<?xml version="1.0"?> + +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="exsl"> + +<!-- This stylesheet was created by template/titlepage.xsl--> + +<xsl:template name="article.titlepage.recto"> + <xsl:choose> + <xsl:when test="articleinfo/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/title"/> + </xsl:when> + <xsl:when test="artheader/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="articleinfo/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/subtitle"/> + </xsl:when> + <xsl:when test="artheader/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="articleinfo/abstract"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="artheader/abstract"/> + <xsl:apply-templates mode="article.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="article.titlepage.verso"> +</xsl:template> + +<xsl:template name="article.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="article.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="article.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="article.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="article.titlepage.before.recto"/> + <xsl:call-template name="article.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="article.titlepage.before.verso"/> + <xsl:call-template name="article.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="article.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="article.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="article.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="article.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="article.titlepage.recto.style"> +<xsl:apply-templates select="." mode="article.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="set.titlepage.recto"> + <xsl:choose> + <xsl:when test="setinfo/title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="setinfo/subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/corpauthor"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/authorgroup"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/author"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/othercredit"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/releaseinfo"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/copyright"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/legalnotice"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/pubdate"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/revision"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/revhistory"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="setinfo/abstract"/> + <xsl:apply-templates mode="set.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="set.titlepage.verso"> +</xsl:template> + +<xsl:template name="set.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="set.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="set.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="set.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="set.titlepage.before.recto"/> + <xsl:call-template name="set.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="set.titlepage.before.verso"/> + <xsl:call-template name="set.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="set.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="set.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="set.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="set.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="set.titlepage.recto.style"> +<xsl:apply-templates select="." mode="set.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="book.titlepage.recto"> + <xsl:choose> + <xsl:when test="bookinfo/title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="bookinfo/subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/corpauthor"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/authorgroup"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/author"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/othercredit"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/releaseinfo"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/copyright"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/legalnotice"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/pubdate"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/revision"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/revhistory"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="bookinfo/abstract"/> + <xsl:apply-templates mode="book.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="book.titlepage.verso"> +</xsl:template> + +<xsl:template name="book.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="book.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="book.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="book.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="book.titlepage.before.recto"/> + <xsl:call-template name="book.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="book.titlepage.before.verso"/> + <xsl:call-template name="book.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="book.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="book.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="book.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="book.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="book.titlepage.recto.style"> +<xsl:apply-templates select="." mode="book.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="part.titlepage.recto"> + <div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:call-template name="division.title"> +<xsl:with-param name="node" select="ancestor-or-self::part[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="partinfo/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="partinfo/abstract"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="part.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="part.titlepage.verso"> +</xsl:template> + +<xsl:template name="part.titlepage.separator"> +</xsl:template> + +<xsl:template name="part.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="part.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="part.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="part.titlepage.before.recto"/> + <xsl:call-template name="part.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="part.titlepage.before.verso"/> + <xsl:call-template name="part.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="part.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="part.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="part.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="part.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="part.titlepage.recto.style"> +<xsl:apply-templates select="." mode="part.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="partintro.titlepage.recto"> + <xsl:choose> + <xsl:when test="partintroinfo/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="partintroinfo/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="partintroinfo/abstract"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="partintro.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="partintro.titlepage.verso"> +</xsl:template> + +<xsl:template name="partintro.titlepage.separator"> +</xsl:template> + +<xsl:template name="partintro.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="partintro.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="partintro.titlepage"> + <div> + <xsl:variable name="recto.content"> + <xsl:call-template name="partintro.titlepage.before.recto"/> + <xsl:call-template name="partintro.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="partintro.titlepage.before.verso"/> + <xsl:call-template name="partintro.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="partintro.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="partintro.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="partintro.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="partintro.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="partintro.titlepage.recto.style"> +<xsl:apply-templates select="." mode="partintro.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="reference.titlepage.recto"> + <xsl:choose> + <xsl:when test="referenceinfo/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="referenceinfo/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="referenceinfo/abstract"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="reference.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="reference.titlepage.verso"> +</xsl:template> + +<xsl:template name="reference.titlepage.separator"><hr/> +</xsl:template> + +<xsl:template name="reference.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="reference.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="reference.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="reference.titlepage.before.recto"/> + <xsl:call-template name="reference.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="reference.titlepage.before.verso"/> + <xsl:call-template name="reference.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="reference.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="reference.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="reference.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="reference.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="reference.titlepage.recto.style"> +<xsl:apply-templates select="." mode="reference.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="refentry.titlepage.recto"> +</xsl:template> + +<xsl:template name="refentry.titlepage.verso"> +</xsl:template> + +<xsl:template name="refentry.titlepage.separator"> +</xsl:template> + +<xsl:template name="refentry.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="refentry.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="refentry.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="refentry.titlepage.before.recto"/> + <xsl:call-template name="refentry.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="refentry.titlepage.before.verso"/> + <xsl:call-template name="refentry.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="refentry.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="refentry.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="refentry.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template name="dedication.titlepage.recto"> + <div xsl:use-attribute-sets="dedication.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::dedication[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="dedicationinfo/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="dedicationinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="dedication.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="dedication.titlepage.verso"> +</xsl:template> + +<xsl:template name="dedication.titlepage.separator"> +</xsl:template> + +<xsl:template name="dedication.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="dedication.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="dedication.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="dedication.titlepage.before.recto"/> + <xsl:call-template name="dedication.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="dedication.titlepage.before.verso"/> + <xsl:call-template name="dedication.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="dedication.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="dedication.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="dedication.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="dedication.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="dedication.titlepage.recto.style"> +<xsl:apply-templates select="." mode="dedication.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="preface.titlepage.recto"> + <xsl:choose> + <xsl:when test="prefaceinfo/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="prefaceinfo/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="prefaceinfo/abstract"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="preface.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="preface.titlepage.verso"> +</xsl:template> + +<xsl:template name="preface.titlepage.separator"> +</xsl:template> + +<xsl:template name="preface.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="preface.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="preface.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="preface.titlepage.before.recto"/> + <xsl:call-template name="preface.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="preface.titlepage.before.verso"/> + <xsl:call-template name="preface.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="preface.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="preface.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="preface.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="preface.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="preface.titlepage.recto.style"> +<xsl:apply-templates select="." mode="preface.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="chapter.titlepage.recto"> + <xsl:choose> + <xsl:when test="chapterinfo/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="chapterinfo/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="chapterinfo/abstract"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="chapter.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="chapter.titlepage.verso"> +</xsl:template> + +<xsl:template name="chapter.titlepage.separator"> +</xsl:template> + +<xsl:template name="chapter.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="chapter.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="chapter.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="chapter.titlepage.before.recto"/> + <xsl:call-template name="chapter.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="chapter.titlepage.before.verso"/> + <xsl:call-template name="chapter.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="chapter.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="chapter.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="chapter.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="chapter.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="chapter.titlepage.recto.style"> +<xsl:apply-templates select="." mode="chapter.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="appendix.titlepage.recto"> + <xsl:choose> + <xsl:when test="appendixinfo/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="appendixinfo/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="appendixinfo/abstract"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="appendix.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="appendix.titlepage.verso"> +</xsl:template> + +<xsl:template name="appendix.titlepage.separator"> +</xsl:template> + +<xsl:template name="appendix.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="appendix.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="appendix.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="appendix.titlepage.before.recto"/> + <xsl:call-template name="appendix.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="appendix.titlepage.before.verso"/> + <xsl:call-template name="appendix.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="appendix.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="appendix.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="appendix.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="appendix.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="appendix.titlepage.recto.style"> +<xsl:apply-templates select="." mode="appendix.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="section.titlepage.recto"> + <xsl:choose> + <xsl:when test="sectioninfo/title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sectioninfo/subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/corpauthor"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/authorgroup"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/author"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/othercredit"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/releaseinfo"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/copyright"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/legalnotice"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/pubdate"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/revision"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/revhistory"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="sectioninfo/abstract"/> + <xsl:apply-templates mode="section.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="section.titlepage.verso"> +</xsl:template> + +<xsl:template name="section.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="section.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="section.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="section.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="section.titlepage.before.recto"/> + <xsl:call-template name="section.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="section.titlepage.before.verso"/> + <xsl:call-template name="section.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="section.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="section.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="section.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="section.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="section.titlepage.recto.style"> +<xsl:apply-templates select="." mode="section.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect1.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect1info/title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect1info/subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/corpauthor"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/authorgroup"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/author"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/othercredit"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/releaseinfo"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/copyright"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/legalnotice"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/pubdate"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/revision"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/revhistory"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="sect1info/abstract"/> + <xsl:apply-templates mode="sect1.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect1.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect1.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect1.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect1.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect1.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect1.titlepage.before.recto"/> + <xsl:call-template name="sect1.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect1.titlepage.before.verso"/> + <xsl:call-template name="sect1.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect1.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect1.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect1.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect1.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect1.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect1.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect2.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect2info/title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect2info/subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/corpauthor"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/authorgroup"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/author"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/othercredit"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/releaseinfo"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/copyright"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/legalnotice"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/pubdate"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/revision"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/revhistory"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="sect2info/abstract"/> + <xsl:apply-templates mode="sect2.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect2.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect2.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect2.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect2.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect2.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect2.titlepage.before.recto"/> + <xsl:call-template name="sect2.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect2.titlepage.before.verso"/> + <xsl:call-template name="sect2.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect2.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect2.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect2.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect2.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect2.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect2.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect3.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect3info/title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect3info/subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/corpauthor"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/authorgroup"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/author"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/othercredit"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/releaseinfo"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/copyright"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/legalnotice"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/pubdate"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/revision"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/revhistory"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="sect3info/abstract"/> + <xsl:apply-templates mode="sect3.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect3.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect3.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect3.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect3.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect3.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect3.titlepage.before.recto"/> + <xsl:call-template name="sect3.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect3.titlepage.before.verso"/> + <xsl:call-template name="sect3.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect3.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect3.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect3.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect3.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect3.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect3.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect4.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect4info/title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect4info/subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/corpauthor"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/authorgroup"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/author"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/othercredit"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/releaseinfo"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/copyright"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/legalnotice"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/pubdate"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/revision"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/revhistory"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="sect4info/abstract"/> + <xsl:apply-templates mode="sect4.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect4.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect4.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect4.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect4.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect4.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect4.titlepage.before.recto"/> + <xsl:call-template name="sect4.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect4.titlepage.before.verso"/> + <xsl:call-template name="sect4.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect4.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect4.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect4.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect4.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect4.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect4.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="sect5.titlepage.recto"> + <xsl:choose> + <xsl:when test="sect5info/title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="sect5info/subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/corpauthor"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/authorgroup"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/author"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/othercredit"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/releaseinfo"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/copyright"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/legalnotice"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/pubdate"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/revision"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/revhistory"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="sect5info/abstract"/> + <xsl:apply-templates mode="sect5.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="sect5.titlepage.verso"> +</xsl:template> + +<xsl:template name="sect5.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="sect5.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="sect5.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="sect5.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="sect5.titlepage.before.recto"/> + <xsl:call-template name="sect5.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="sect5.titlepage.before.verso"/> + <xsl:call-template name="sect5.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="sect5.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="sect5.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="sect5.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="sect5.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="sect5.titlepage.recto.style"> +<xsl:apply-templates select="." mode="sect5.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="simplesect.titlepage.recto"> + <xsl:choose> + <xsl:when test="simplesectinfo/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/title"/> + </xsl:when> + <xsl:when test="docinfo/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/title"/> + </xsl:when> + <xsl:when test="info/title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/title"/> + </xsl:when> + <xsl:when test="title"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="title"/> + </xsl:when> + </xsl:choose> + + <xsl:choose> + <xsl:when test="simplesectinfo/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/corpauthor"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/authorgroup"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/author"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/othercredit"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/releaseinfo"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/copyright"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/legalnotice"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/pubdate"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/revision"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/revhistory"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="simplesectinfo/abstract"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="docinfo/abstract"/> + <xsl:apply-templates mode="simplesect.titlepage.recto.auto.mode" select="info/abstract"/> +</xsl:template> + +<xsl:template name="simplesect.titlepage.verso"> +</xsl:template> + +<xsl:template name="simplesect.titlepage.separator"><xsl:if test="count(parent::*)='0'"><hr/></xsl:if> +</xsl:template> + +<xsl:template name="simplesect.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="simplesect.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="simplesect.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="simplesect.titlepage.before.recto"/> + <xsl:call-template name="simplesect.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="simplesect.titlepage.before.verso"/> + <xsl:call-template name="simplesect.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="simplesect.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="simplesect.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="simplesect.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="title" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="subtitle" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="corpauthor" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="authorgroup" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="author" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="othercredit" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="releaseinfo" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="copyright" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="legalnotice" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="pubdate" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revision" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="revhistory" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template match="abstract" mode="simplesect.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="simplesect.titlepage.recto.style"> +<xsl:apply-templates select="." mode="simplesect.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="bibliography.titlepage.recto"> + <div xsl:use-attribute-sets="bibliography.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::bibliography[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="bibliographyinfo/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="bibliographyinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="bibliography.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="bibliography.titlepage.verso"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.separator"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="bibliography.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="bibliography.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="bibliography.titlepage.before.recto"/> + <xsl:call-template name="bibliography.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="bibliography.titlepage.before.verso"/> + <xsl:call-template name="bibliography.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="bibliography.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="bibliography.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="bibliography.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="bibliography.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="bibliography.titlepage.recto.style"> +<xsl:apply-templates select="." mode="bibliography.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="glossary.titlepage.recto"> + <div xsl:use-attribute-sets="glossary.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::glossary[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="glossaryinfo/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="glossaryinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="glossary.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="glossary.titlepage.verso"> +</xsl:template> + +<xsl:template name="glossary.titlepage.separator"> +</xsl:template> + +<xsl:template name="glossary.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="glossary.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="glossary.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="glossary.titlepage.before.recto"/> + <xsl:call-template name="glossary.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="glossary.titlepage.before.verso"/> + <xsl:call-template name="glossary.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="glossary.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="glossary.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="glossary.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="glossary.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="glossary.titlepage.recto.style"> +<xsl:apply-templates select="." mode="glossary.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="index.titlepage.recto"> + <div xsl:use-attribute-sets="index.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::index[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="indexinfo/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="indexinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="index.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="index.titlepage.verso"> +</xsl:template> + +<xsl:template name="index.titlepage.separator"> +</xsl:template> + +<xsl:template name="index.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="index.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="index.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="index.titlepage.before.recto"/> + <xsl:call-template name="index.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="index.titlepage.before.verso"/> + <xsl:call-template name="index.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="index.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="index.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="index.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="index.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="index.titlepage.recto.style"> +<xsl:apply-templates select="." mode="index.titlepage.recto.mode"/> +</div> +</xsl:template> + +<xsl:template name="setindex.titlepage.recto"> + <div xsl:use-attribute-sets="setindex.titlepage.recto.style"> +<xsl:call-template name="component.title"> +<xsl:with-param name="node" select="ancestor-or-self::setindex[1]"/> +</xsl:call-template></div> + <xsl:choose> + <xsl:when test="setindexinfo/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="setindexinfo/subtitle"/> + </xsl:when> + <xsl:when test="docinfo/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="docinfo/subtitle"/> + </xsl:when> + <xsl:when test="info/subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="info/subtitle"/> + </xsl:when> + <xsl:when test="subtitle"> + <xsl:apply-templates mode="setindex.titlepage.recto.auto.mode" select="subtitle"/> + </xsl:when> + </xsl:choose> + +</xsl:template> + +<xsl:template name="setindex.titlepage.verso"> +</xsl:template> + +<xsl:template name="setindex.titlepage.separator"> +</xsl:template> + +<xsl:template name="setindex.titlepage.before.recto"> +</xsl:template> + +<xsl:template name="setindex.titlepage.before.verso"> +</xsl:template> + +<xsl:template name="setindex.titlepage"> + <div class="titlepage"> + <xsl:variable name="recto.content"> + <xsl:call-template name="setindex.titlepage.before.recto"/> + <xsl:call-template name="setindex.titlepage.recto"/> + </xsl:variable> + <xsl:variable name="recto.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($recto.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($recto.content) != '') or ($recto.elements.count > 0)"> + <div><xsl:copy-of select="$recto.content"/></div> + </xsl:if> + <xsl:variable name="verso.content"> + <xsl:call-template name="setindex.titlepage.before.verso"/> + <xsl:call-template name="setindex.titlepage.verso"/> + </xsl:variable> + <xsl:variable name="verso.elements.count"> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:when test="contains(system-property('xsl:vendor'), 'Apache Software Foundation')"> + <!--Xalan quirk--><xsl:value-of select="count(exsl:node-set($verso.content)/*)"/></xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="(normalize-space($verso.content) != '') or ($verso.elements.count > 0)"> + <div><xsl:copy-of select="$verso.content"/></div> + </xsl:if> + <xsl:call-template name="setindex.titlepage.separator"/> + </div> +</xsl:template> + +<xsl:template match="*" mode="setindex.titlepage.recto.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="*" mode="setindex.titlepage.verso.mode"> + <!-- if an element isn't found in this mode, --> + <!-- try the generic titlepage.mode --> + <xsl:apply-templates select="." mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="subtitle" mode="setindex.titlepage.recto.auto.mode"> +<div xsl:use-attribute-sets="setindex.titlepage.recto.style"> +<xsl:apply-templates select="." mode="setindex.titlepage.recto.mode"/> +</div> +</xsl:template> + +</xsl:stylesheet> + diff --git a/docs/xsl-generic/html/titlepage.xsl b/docs/xsl-generic/html/titlepage.xsl new file mode 100644 index 00000000..e2445516 --- /dev/null +++ b/docs/xsl-generic/html/titlepage.xsl @@ -0,0 +1,1031 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: titlepage.xsl 7253 2007-08-18 16:49:39Z mzjn $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:attribute-set name="book.titlepage.recto.style"/> +<xsl:attribute-set name="book.titlepage.verso.style"/> + +<xsl:attribute-set name="article.titlepage.recto.style"/> +<xsl:attribute-set name="article.titlepage.verso.style"/> + +<xsl:attribute-set name="set.titlepage.recto.style"/> +<xsl:attribute-set name="set.titlepage.verso.style"/> + +<xsl:attribute-set name="part.titlepage.recto.style"/> +<xsl:attribute-set name="part.titlepage.verso.style"/> + +<xsl:attribute-set name="partintro.titlepage.recto.style"/> +<xsl:attribute-set name="partintro.titlepage.verso.style"/> + +<xsl:attribute-set name="reference.titlepage.recto.style"/> +<xsl:attribute-set name="reference.titlepage.verso.style"/> + +<xsl:attribute-set name="refentry.titlepage.recto.style"/> +<xsl:attribute-set name="refentry.titlepage.verso.style"/> + +<xsl:attribute-set name="dedication.titlepage.recto.style"/> +<xsl:attribute-set name="dedication.titlepage.verso.style"/> + +<xsl:attribute-set name="preface.titlepage.recto.style"/> +<xsl:attribute-set name="preface.titlepage.verso.style"/> + +<xsl:attribute-set name="chapter.titlepage.recto.style"/> +<xsl:attribute-set name="chapter.titlepage.verso.style"/> + +<xsl:attribute-set name="appendix.titlepage.recto.style"/> +<xsl:attribute-set name="appendix.titlepage.verso.style"/> + +<xsl:attribute-set name="bibliography.titlepage.recto.style"/> +<xsl:attribute-set name="bibliography.titlepage.verso.style"/> + +<xsl:attribute-set name="glossary.titlepage.recto.style"/> +<xsl:attribute-set name="glossary.titlepage.verso.style"/> + +<xsl:attribute-set name="index.titlepage.recto.style"/> +<xsl:attribute-set name="index.titlepage.verso.style"/> + +<xsl:attribute-set name="setindex.titlepage.recto.style"/> +<xsl:attribute-set name="setindex.titlepage.verso.style"/> + +<xsl:attribute-set name="section.titlepage.recto.style"/> +<xsl:attribute-set name="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect1.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect1.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect2.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect2.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect3.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect3.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect4.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect4.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="sect5.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="sect5.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="simplesect.titlepage.recto.style" + use-attribute-sets="section.titlepage.recto.style"/> +<xsl:attribute-set name="simplesect.titlepage.verso.style" + use-attribute-sets="section.titlepage.verso.style"/> + +<xsl:attribute-set name="table.of.contents.titlepage.recto.style"/> +<xsl:attribute-set name="table.of.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.tables.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.tables.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.figures.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.figures.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.equations.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.equations.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.examples.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.examples.contents.titlepage.verso.style"/> + +<xsl:attribute-set name="list.of.unknowns.titlepage.recto.style"/> +<xsl:attribute-set name="list.of.unknowns.contents.titlepage.verso.style"/> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="titlepage.mode"> + <!-- if an element isn't found in this mode, try the default mode --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="abbrev" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="abstract" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="anchor"/> + <xsl:if test="$abstract.notitle.enabled = 0"> + <xsl:call-template name="formal.object.heading"> + <xsl:with-param name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + <xsl:apply-templates mode="titlepage.mode"/> + <xsl:call-template name="process.footnotes"/> + </div> +</xsl:template> + +<xsl:template match="abstract/title" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="address" mode="titlepage.mode"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="paragraph"> + <xsl:with-param name="content"> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </div> + </xsl:when> + + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="paragraph"> + <xsl:with-param name="content"> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="affiliation" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="artpagenums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="author|editor" mode="titlepage.mode"> + <xsl:call-template name="credits.div"/> +</xsl:template> + +<xsl:template name="credits.div"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="self::editor[position()=1] and not($editedby.enabled = 0)"> + <h4 class="editedby"><xsl:call-template name="gentext.edited.by"/></h4> + </xsl:if> + <h3> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:choose> + <xsl:when test="orgname"> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="person.name"/> + </xsl:otherwise> + </xsl:choose> + </h3> + <xsl:if test="not($contrib.inline.enabled = 0)"> + <xsl:apply-templates mode="titlepage.mode" select="contrib"/> + </xsl:if> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + <xsl:apply-templates mode="titlepage.mode" select="email"/> + <xsl:if test="not($blurb.on.titlepage.enabled = 0)"> + <xsl:choose> + <xsl:when test="$contrib.inline.enabled = 0"> + <xsl:apply-templates mode="titlepage.mode" + select="contrib|authorblurb|personblurb"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode" + select="authorblurb|personblurb"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </div> +</xsl:template> + +<xsl:template match="authorblurb|personblurb" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="authorgroup" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="anchor"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="authorinitials" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="bibliomisc" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="bibliomset" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="collab" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="collabname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </span> +</xsl:template> + +<xsl:template match="confgroup" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="confdates" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="confsponsor" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="conftitle" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="confnum" mode="titlepage.mode"> + <!-- suppress --> +</xsl:template> + +<xsl:template match="contractnum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="contractsponsor" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="contrib" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="not($contrib.inline.enabled = 0)"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </span><xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p><xsl:apply-templates mode="titlepage.mode"/></p> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="copyright" mode="titlepage.mode"> + <p> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Copyright'"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="dingbat"> + <xsl:with-param name="dingbat">copyright</xsl:with-param> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="copyright.years"> + <xsl:with-param name="years" select="year"/> + <xsl:with-param name="print.ranges" select="$make.year.ranges"/> + <xsl:with-param name="single.year.ranges" + select="$make.single.year.ranges"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:apply-templates select="holder" mode="titlepage.mode"/> + </p> +</xsl:template> + +<xsl:template match="year" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="holder" mode="titlepage.mode"> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="position() < last()"> + <xsl:text>, </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="corpauthor" mode="titlepage.mode"> + <h3> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </h3> +</xsl:template> + +<xsl:template match="corpcredit" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="corpname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="date" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="edition" mode="titlepage.mode"> + <p> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <xsl:call-template name="gentext.space"/> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Edition'"/> + </xsl:call-template> + </p> +</xsl:template> + +<xsl:template match="email" mode="titlepage.mode"> + <!-- use the normal e-mail handling code --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="firstname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="graphic" mode="titlepage.mode"> + <!-- use the normal graphic handling code --> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="honorific" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="isbn" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="issn" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="biblioid" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="itermset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="invpartnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="issuenum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="jobtitle" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="keywordset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="legalnotice" mode="titlepage.mode"> + <xsl:variable name="id"><xsl:call-template name="object.id"/></xsl:variable> + + <xsl:choose> + <xsl:when test="$generate.legalnotice.link != 0"> + + <!-- Compute name of legalnotice file --> + <xsl:variable name="file"> + <xsl:call-template name="ln.or.rh.filename"/> + </xsl:variable> + + <xsl:variable name="filename"> + <xsl:call-template name="make-relative-filename"> + <xsl:with-param name="base.dir" select="$base.dir"/> + <xsl:with-param name="base.name" select="$file"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:variable> + + <a href="{$file}"> + <xsl:copy-of select="$title"/> + </a> + + <xsl:call-template name="write.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="quiet" select="$chunk.quietly"/> + <xsl:with-param name="content"> + <xsl:call-template name="user.preroot"/> + <html> + <head> + <xsl:call-template name="system.head.content"/> + <xsl:call-template name="head.content"/> + <xsl:call-template name="user.head.content"/> + </head> + <body> + <xsl:call-template name="body.attributes"/> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> + </body> + </html> + <xsl:value-of select="$chunk.append"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <a name="{$id}"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="legalnotice/title" mode="titlepage.mode"> + <p class="legalnotice-title"><b><xsl:apply-templates/></b></p> +</xsl:template> + +<xsl:template match="lineage" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="modespec" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="orgdiv" mode="titlepage.mode"> + <xsl:if test="preceding-sibling::*[1][self::orgname]"> + <xsl:text> </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="orgname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="othercredit" mode="titlepage.mode"> +<xsl:choose> + <xsl:when test="not($othercredit.like.author.enabled = 0)"> + <xsl:variable name="contrib" select="string(contrib)"/> + <xsl:choose> + <xsl:when test="contrib"> + <xsl:if test="not(preceding-sibling::othercredit[string(contrib)=$contrib])"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode" select="contrib"/> + <xsl:text>: </xsl:text> + <xsl:call-template name="person.name"/> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + <xsl:apply-templates select="following-sibling::othercredit[string(contrib)=$contrib]" mode="titlepage.othercredits"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:call-template name="person.name"/> + </xsl:with-param> + </xsl:call-template> + <xsl:apply-templates mode="titlepage.mode" select="affiliation"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="credits.div"/> + </xsl:otherwise> +</xsl:choose> +</xsl:template> + +<xsl:template match="othercredit" mode="titlepage.othercredits"> + <xsl:text>, </xsl:text> + <xsl:call-template name="person.name"/> +</xsl:template> + +<xsl:template match="othername" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pagenums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="printhistory" mode="titlepage.mode"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </div> +</xsl:template> + +<xsl:template match="productname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="productnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pubdate" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="publisher" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="publishername" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="pubsnumber" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="releaseinfo" mode="titlepage.mode"> + <xsl:call-template name="paragraph"> + <xsl:with-param name="class" select="local-name(.)"/> + <xsl:with-param name="content"> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="revhistory" mode="titlepage.mode"> + <xsl:variable name="numcols"> + <xsl:choose> + <xsl:when test=".//authorinitials|.//author">3</xsl:when> + <xsl:otherwise>2</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="id"><xsl:call-template name="object.id"/></xsl:variable> + + <xsl:variable name="title"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key">RevHistory</xsl:with-param> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="contents"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <table border="1" width="100%" summary="Revision history"> + <tr> + <th align="left" valign="top" colspan="{$numcols}"> + <b> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RevHistory'"/> + </xsl:call-template> + </b> + </th> + </tr> + <xsl:apply-templates mode="titlepage.mode"> + <xsl:with-param name="numcols" select="$numcols"/> + </xsl:apply-templates> + </table> + </div> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$generate.revhistory.link != 0"> + + <!-- Compute name of revhistory file --> + <xsl:variable name="file"> + <xsl:call-template name="ln.or.rh.filename"> + <xsl:with-param name="is.ln" select="false()"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="filename"> + <xsl:call-template name="make-relative-filename"> + <xsl:with-param name="base.dir" select="$base.dir"/> + <xsl:with-param name="base.name" select="$file"/> + </xsl:call-template> + </xsl:variable> + + <a href="{$file}"> + <xsl:copy-of select="$title"/> + </a> + + <xsl:call-template name="write.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="quiet" select="$chunk.quietly"/> + <xsl:with-param name="content"> + <xsl:call-template name="user.preroot"/> + <html> + <head> + <xsl:call-template name="system.head.content"/> + <xsl:call-template name="head.content"> + <xsl:with-param name="title"> + <xsl:value-of select="$title"/> + <xsl:if test="../../title"> + <xsl:value-of select="concat(' (', ../../title, ')')"/> + </xsl:if> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="user.head.content"/> + </head> + <body> + <xsl:call-template name="body.attributes"/> + <xsl:copy-of select="$contents"/> + </body> + </html> + <xsl:text> </xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="revhistory/revision" mode="titlepage.mode"> + <xsl:param name="numcols" select="'3'"/> + <xsl:variable name="revnumber" select="revnumber"/> + <xsl:variable name="revdate" select="date"/> + <xsl:variable name="revauthor" select="authorinitials|author"/> + <xsl:variable name="revremark" select="revremark|revdescription"/> + <tr> + <td align="left"> + <xsl:if test="$revnumber"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Revision'"/> + </xsl:call-template> + <xsl:call-template name="gentext.space"/> + <xsl:apply-templates select="$revnumber[1]" mode="titlepage.mode"/> + </xsl:if> + </td> + <td align="left"> + <xsl:apply-templates select="$revdate[1]" mode="titlepage.mode"/> + </td> + <xsl:choose> + <xsl:when test="$revauthor"> + <td align="left"> + <xsl:for-each select="$revauthor"> + <xsl:apply-templates select="." mode="titlepage.mode"/> + <xsl:if test="position() != last()"> + <xsl:text>, </xsl:text> + </xsl:if> + </xsl:for-each> + </td> + </xsl:when> + <xsl:when test="$numcols > 2"> + <td> </td> + </xsl:when> + <xsl:otherwise></xsl:otherwise> + </xsl:choose> + </tr> + <xsl:if test="$revremark"> + <tr> + <td align="left" colspan="{$numcols}"> + <xsl:apply-templates select="$revremark[1]" mode="titlepage.mode"/> + </td> + </tr> + </xsl:if> +</xsl:template> + +<xsl:template match="revision/revnumber" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/date" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/authorinitials" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/author" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/revremark" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="revision/revdescription" mode="titlepage.mode"> + <xsl:apply-templates mode="titlepage.mode"/> +</xsl:template> + +<xsl:template match="seriesvolnums" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="shortaffil" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="subjectset" mode="titlepage.mode"> +</xsl:template> + +<xsl:template match="subtitle" mode="titlepage.mode"> + <h2> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + </h2> +</xsl:template> + +<xsl:template match="surname" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<xsl:template match="title" mode="titlepage.mode"> + <xsl:variable name="id"> + <xsl:choose> + <!-- if title is in an *info wrapper, get the grandparent --> + <xsl:when test="contains(local-name(..), 'info')"> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="../.."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select=".."/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <h1> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="$generate.id.attributes = 0"> + <a name="{$id}"/> + </xsl:if> + <xsl:choose> + <xsl:when test="$show.revisionflag != 0 and @revisionflag"> + <span class="{@revisionflag}"> + <xsl:apply-templates mode="titlepage.mode"/> + </span> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="titlepage.mode"/> + </xsl:otherwise> + </xsl:choose> + </h1> +</xsl:template> + +<xsl:template match="titleabbrev" mode="titlepage.mode"> + <!-- nop; title abbreviations don't belong on the title page! --> +</xsl:template> + +<xsl:template match="volumenum" mode="titlepage.mode"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="titlepage.mode"/> + <br/> + </span> +</xsl:template> + +<!-- This template computes the filename for legalnotice and revhistory chunks --> +<xsl:template name="ln.or.rh.filename"> + <xsl:param name="node" select="."/> + <xsl:param name="is.ln" select="true()"/> + + <xsl:variable name="dbhtml-filename"> + <xsl:call-template name="pi.dbhtml_filename"> + <xsl:with-param name="node" select="$node"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <!-- 1. If there is a dbhtml_filename PI, use that --> + <xsl:when test="$dbhtml-filename != ''"> + <xsl:value-of select="$dbhtml-filename"/> + </xsl:when> + <xsl:when test="($node/@id or $node/@xml:id) and not($use.id.as.filename = 0)"> + <!-- * 2. If this legalnotice/revhistory has an ID, then go ahead and use --> + <!-- * just the value of that ID as the basename for the file --> + <!-- * (that is, without prepending an "ln-" or "rh-" to it) --> + <xsl:value-of select="($node/@id|$node/@xml:id)[1]"/> + <xsl:value-of select="$html.ext"/> + </xsl:when> + <xsl:when test="not ($node/@id or $node/@xml:id) or $use.id.as.filename = 0"> + <!-- * 3. Otherwise, if this legalnotice/revhistory does not have an ID, or --> + <!-- * if $use.id.as.filename = 0 --> + <!-- * then we generate an ID... --> + <xsl:variable name="id"> + <xsl:value-of select="generate-id($node)"/> + </xsl:variable> + <!-- * ...and then we take that generated ID, prepend a --> + <!-- * prefix to it, and use that as the basename for the file --> + <xsl:choose> + <xsl:when test="$is.ln"> + <xsl:value-of select="concat('ln-',$id,$html.ext)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="concat('rh-',$id,$html.ext)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/toc.xsl b/docs/xsl-generic/html/toc.xsl new file mode 100644 index 00000000..bf1e62c7 --- /dev/null +++ b/docs/xsl-generic/html/toc.xsl @@ -0,0 +1,173 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: toc.xsl 6910 2007-06-28 23:23:30Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="toc"> + <xsl:choose> + <xsl:when test="*"> + <xsl:if test="$process.source.toc != 0"> + <!-- if the toc isn't empty, process it --> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates/> + </xsl:element> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:if test="$process.empty.source.toc != 0"> + <xsl:choose> + <xsl:when test="parent::section + or parent::sect1 + or parent::sect2 + or parent::sect3 + or parent::sect4 + or parent::sect5"> + <xsl:apply-templates select="parent::*" + mode="toc.for.section"/> + </xsl:when> + <xsl:when test="parent::article"> + <xsl:apply-templates select="parent::*" + mode="toc.for.component"/> + </xsl:when> + <xsl:when test="parent::book + or parent::part"> + <xsl:apply-templates select="parent::*" + mode="toc.for.division"/> + </xsl:when> + <xsl:when test="parent::set"> + <xsl:apply-templates select="parent::*" + mode="toc.for.set"/> + </xsl:when> + <!-- there aren't any other contexts that allow toc --> + <xsl:otherwise> + <xsl:message> + <xsl:text>I don't know how to make a TOC in this context!</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocpart|tocchap + |toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> + <xsl:variable name="sub-toc"> + <xsl:if test="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"> + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dd> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> + </xsl:element> + </dd> + </xsl:when> + <xsl:otherwise> + <xsl:element name="{$toc.list.type}"> + <xsl:apply-templates select="tocchap|toclevel1|toclevel2|toclevel3|toclevel4|toclevel5"/> + </xsl:element> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:variable> + + <xsl:apply-templates select="tocentry[position() != last()]"/> + + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dt> + <xsl:apply-templates select="tocentry[position() = last()]"/> + </dt> + <xsl:copy-of select="$sub-toc"/> + </xsl:when> + <xsl:otherwise> + <li> + <xsl:apply-templates select="tocentry[position() = last()]"/> + <xsl:copy-of select="$sub-toc"/> + </li> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocentry|tocfront|tocback"> + <xsl:choose> + <xsl:when test="$toc.list.type = 'dl'"> + <dt> + <xsl:call-template name="tocentry-content"/> + </dt> + </xsl:when> + <xsl:otherwise> + <li> + <xsl:call-template name="tocentry-content"/> + </li> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="tocentry[position() = last()]" priority="2"> + <xsl:call-template name="tocentry-content"/> +</xsl:template> + +<xsl:template name="tocentry-content"> + <xsl:variable name="targets" select="key('id',@linkend)"/> + <xsl:variable name="target" select="$targets[1]"/> + + <xsl:choose> + <xsl:when test="@linkend"> + <xsl:call-template name="check.id.unique"> + <xsl:with-param name="linkend" select="@linkend"/> + </xsl:call-template> + <a> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:attribute> + <xsl:apply-templates/> + </a> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="toc.for.section"> + <xsl:call-template name="section.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.component"> + <xsl:call-template name="component.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.section"> + <xsl:call-template name="section.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.division"> + <xsl:call-template name="division.toc"/> +</xsl:template> + +<xsl:template match="*" mode="toc.for.set"> + <xsl:call-template name="set.toc"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="lot|lotentry"> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/verbatim.xsl b/docs/xsl-generic/html/verbatim.xsl new file mode 100644 index 00000000..13ad10ef --- /dev/null +++ b/docs/xsl-generic/html/verbatim.xsl @@ -0,0 +1,376 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:sverb="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.Verbatim" + xmlns:xverb="xalan://com.nwalsh.xalan.Verbatim" + xmlns:lxslt="http://xml.apache.org/xslt" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="sverb xverb lxslt exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: verbatim.xsl 6946 2007-07-04 10:21:57Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:include href="../highlighting/common.xsl"/> +<xsl:include href="highlight.xsl"/> + +<lxslt:component prefix="xverb" + functions="numberLines"/> + +<xsl:template match="programlisting|screen|synopsis"> + <xsl:param name="suppress-numbers" select="'0'"/> + <xsl:variable name="id"> + <xsl:call-template name="object.id"/> + </xsl:variable> + + <xsl:call-template name="anchor"/> + + <xsl:if test="$shade.verbatim != 0"> + <xsl:message> + <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> + <xsl:text>Use CSS instead,</xsl:text> + </xsl:message> + <xsl:message> + <xsl:text>for example: pre.</xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text> { background-color: #E0E0E0; }</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <xsl:variable name="rtf"> + <xsl:call-template name="apply-highlighting"/> + </xsl:variable> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </pre> + </xsl:when> + <xsl:otherwise> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="apply-highlighting"/> + </pre> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="literallayout"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates/> + </xsl:variable> + + <xsl:if test="$shade.verbatim != 0 and @class='monospaced'"> + <xsl:message> + <xsl:text>The shade.verbatim parameter is deprecated. </xsl:text> + <xsl:text>Use CSS instead,</xsl:text> + </xsl:message> + <xsl:message> + <xsl:text>for example: pre.</xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text> { background-color: #E0E0E0; }</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <xsl:choose> + <xsl:when test="@class='monospaced'"> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </pre> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="@class='monospaced'"> + <pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$rtf"/> + </pre> + </xsl:when> + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="address"> + <xsl:param name="suppress-numbers" select="'0'"/> + + <xsl:variable name="rtf"> + <xsl:apply-templates/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$suppress-numbers = '0' + and @linenumbering = 'numbered' + and $use.extensions != '0' + and $linenumbering.extension != '0'"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="number.rtf.lines"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:when> + + <xsl:otherwise> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:call-template name="make-verbatim"> + <xsl:with-param name="rtf" select="$rtf"/> + </xsl:call-template> + </p> + </div> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="number.rtf.lines"> + <xsl:param name="rtf" select="''"/> + <xsl:param name="pi.context" select="."/> + + <!-- Save the global values --> + <xsl:variable name="global.linenumbering.everyNth" + select="$linenumbering.everyNth"/> + + <xsl:variable name="global.linenumbering.separator" + select="$linenumbering.separator"/> + + <xsl:variable name="global.linenumbering.width" + select="$linenumbering.width"/> + + <!-- Extract the <?dbhtml linenumbering.*?> PI values --> + <xsl:variable name="pi.linenumbering.everyNth"> + <xsl:call-template name="pi.dbhtml_linenumbering.everyNth"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="pi.linenumbering.separator"> + <xsl:call-template name="pi.dbhtml_linenumbering.separator"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="pi.linenumbering.width"> + <xsl:call-template name="pi.dbhtml_linenumbering.width"> + <xsl:with-param name="node" select="$pi.context"/> + </xsl:call-template> + </xsl:variable> + + <!-- Construct the 'in-context' values --> + <xsl:variable name="linenumbering.everyNth"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.everyNth != ''"> + <xsl:value-of select="$pi.linenumbering.everyNth"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.everyNth"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.separator"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.separator != ''"> + <xsl:value-of select="$pi.linenumbering.separator"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.separator"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.width"> + <xsl:choose> + <xsl:when test="$pi.linenumbering.width != ''"> + <xsl:value-of select="$pi.linenumbering.width"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$global.linenumbering.width"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="linenumbering.startinglinenumber"> + <xsl:choose> + <xsl:when test="$pi.context/@startinglinenumber"> + <xsl:value-of select="$pi.context/@startinglinenumber"/> + </xsl:when> + <xsl:when test="$pi.context/@continuation='continues'"> + <xsl:variable name="lastLine"> + <xsl:choose> + <xsl:when test="$pi.context/self::programlisting"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::programlisting[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::screen"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::screen[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::literallayout"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::literallayout[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::address"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::address[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:when test="$pi.context/self::synopsis"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" + select="preceding::synopsis[@linenumbering='numbered']"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unexpected verbatim environment: </xsl:text> + <xsl:value-of select="local-name($pi.context)"/> + </xsl:message> + <xsl:value-of select="0"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:value-of select="$lastLine + 1"/> + </xsl:when> + <xsl:otherwise>1</xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('sverb:numberLines')"> + <xsl:copy-of select="sverb:numberLines($rtf)"/> + </xsl:when> + <xsl:when test="function-available('xverb:numberLines')"> + <xsl:copy-of select="xverb:numberLines($rtf)"/> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>No numberLines function available.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="make-verbatim"> + <xsl:param name="rtf"/> + + <!-- I want to make this RTF verbatim. There are two possibilities: either + I have access to the exsl:node-set extension function and I can "do it right" + or I have to rely on CSS. --> + + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:apply-templates select="exsl:node-set($rtf)" mode="make.verbatim.mode"/> + </xsl:when> + <xsl:otherwise> + <span style="white-space: pre;"> + <xsl:copy-of select="$rtf"/> + </span> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ======================================================================== --> + +<xsl:template name="lastLineNumber"> + <xsl:param name="listings"/> + <xsl:param name="number" select="0"/> + + <xsl:variable name="lines"> + <xsl:call-template name="countLines"> + <xsl:with-param name="listing" select="string($listings[1])"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <xsl:when test="not($listings)"> + <xsl:value-of select="$number"/> + </xsl:when> + <xsl:when test="$listings[1]/@startinglinenumber"> + <xsl:value-of select="$number + $listings[1]/@startinglinenumber + $lines - 1"/> + </xsl:when> + <xsl:when test="$listings[1]/@continuation='continues'"> + <xsl:call-template name="lastLineNumber"> + <xsl:with-param name="listings" select="listings[position() > 1]"/> + <xsl:with-param name="number" select="$number + $lines"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$lines"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="countLines"> + <xsl:param name="listing"/> + <xsl:param name="count" select="1"/> + + <xsl:choose> + <xsl:when test="contains($listing, ' ')"> + <xsl:call-template name="countLines"> + <xsl:with-param name="listing" select="substring-after($listing, ' ')"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/html/xref.xsl b/docs/xsl-generic/html/xref.xsl new file mode 100644 index 00000000..7c8dd68d --- /dev/null +++ b/docs/xsl-generic/html/xref.xsl @@ -0,0 +1,1348 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:suwl="http://nwalsh.com/xslt/ext/com.nwalsh.saxon.UnwrapLinks" + xmlns:exsl="http://exslt.org/common" + xmlns:xlink='http://www.w3.org/1999/xlink' + exclude-result-prefixes="suwl exsl xlink" + version='1.0'> + +<!-- ******************************************************************** + $Id: xref.xsl 7107 2007-07-22 10:22:06Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- Use internal variable for olink xlink role for consistency --> +<xsl:variable + name="xolink.role">http://docbook.org/xlink/role/olink</xsl:variable> + +<!-- ==================================================================== --> + +<xsl:template match="anchor"> + <xsl:call-template name="anchor"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="xref" name="xref"> + <xsl:param name="xhref" select="@xlink:href"/> + <!-- is the @xlink:href a local idref link? --> + <xsl:param name="xlink.idref"> + <xsl:if test="starts-with($xhref,'#') + and (not(contains($xhref,'(')) + or starts-with($xhref, '#xpointer(id('))"> + <xsl:call-template name="xpointer.idref"> + <xsl:with-param name="xpointer" select="$xhref"/> + </xsl:call-template> + </xsl:if> + </xsl:param> + <xsl:param name="xlink.targets" select="key('id',$xlink.idref)"/> + <xsl:param name="linkend.targets" select="key('id',@linkend)"/> + <xsl:param name="target" select="($xlink.targets | $linkend.targets)[1]"/> + + <xsl:variable name="xrefstyle"> + <xsl:choose> + <xsl:when test="@role and not(@xrefstyle) + and $use.role.as.xrefstyle != 0"> + <xsl:value-of select="@role"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@xrefstyle"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="anchor"/> + + <xsl:variable name="content"> + <xsl:choose> + + <xsl:when test="@endterm"> + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$target/@xreflabel"> + <xsl:call-template name="xref.xreflabel"> + <xsl:with-param name="target" select="$target"/> + </xsl:call-template> + </xsl:when> + + <xsl:when test="$target"> + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-prefix"/> + </xsl:if> + + <xsl:apply-templates select="$target" mode="xref-to"> + <xsl:with-param name="referrer" select="."/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + </xsl:apply-templates> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-suffix"/> + </xsl:if> + </xsl:when> + + <xsl:otherwise> + <xsl:message> + <xsl:text>ERROR: xref linking to </xsl:text> + <xsl:value-of select="@linkend|@xlink:href"/> + <xsl:text> has no generated link text.</xsl:text> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="simple.xlink"> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + +</xsl:template> + +<!-- ==================================================================== --> + +<!-- biblioref handled largely like an xref --> +<!-- To be done: add support for begin, end, and units attributes --> +<xsl:template match="biblioref"> + <xsl:variable name="targets" select="key('id',@linkend)"/> + <xsl:variable name="target" select="$targets[1]"/> + <xsl:variable name="refelem" select="local-name($target)"/> + + <xsl:call-template name="check.id.unique"> + <xsl:with-param name="linkend" select="@linkend"/> + </xsl:call-template> + + <xsl:call-template name="anchor"/> + + <xsl:choose> + <xsl:when test="count($target) = 0"> + <xsl:message> + <xsl:text>XRef to nonexistent id: </xsl:text> + <xsl:value-of select="@linkend"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + + <xsl:when test="@endterm"> + <xsl:variable name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>???</xsl:text> + </a> + </xsl:when> + <xsl:otherwise> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </a> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + + <xsl:when test="$target/@xreflabel"> + <a> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:attribute name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:attribute> + <xsl:call-template name="xref.xreflabel"> + <xsl:with-param name="target" select="$target"/> + </xsl:call-template> + </a> + </xsl:when> + + <xsl:otherwise> + <xsl:variable name="href"> + <xsl:call-template name="href.target"> + <xsl:with-param name="object" select="$target"/> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-prefix"/> + </xsl:if> + + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="$target/title or $target/*/title"> + <xsl:attribute name="title"> + <xsl:apply-templates select="$target" mode="xref-title"/> + </xsl:attribute> + </xsl:if> + <xsl:apply-templates select="$target" mode="xref-to"> + <xsl:with-param name="referrer" select="."/> + <xsl:with-param name="xrefstyle"> + <xsl:choose> + <xsl:when test="@role and not(@xrefstyle) and $use.role.as.xrefstyle != 0"> + <xsl:value-of select="@role"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@xrefstyle"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:apply-templates> + </a> + + <xsl:if test="not(parent::citation)"> + <xsl:apply-templates select="$target" mode="xref-to-suffix"/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="endterm"> + <!-- Process the children of the endterm element --> + <xsl:variable name="endterm"> + <xsl:apply-templates select="child::node()"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:apply-templates select="exsl:node-set($endterm)" mode="remove-ids"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$endterm"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="remove-ids"> + <xsl:choose> + <!-- handle html or xhtml --> + <xsl:when test="local-name(.) = 'a' + and (namespace-uri(.) = '' + or namespace-uri(.) = 'http://www.w3.org/1999/xhtml')"> + <xsl:choose> + <xsl:when test="(@name and count(@*) = 1) + or (@id and count(@*) = 1) + or (@xml:id and count(@*) = 1) + or (@xml:id and @name and count(@*) = 2) + or (@id and @name and count(@*) = 2)"> + <xsl:message>suppress anchor</xsl:message> + <!-- suppress the whole thing --> + </xsl:when> + <xsl:otherwise> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:choose> + <xsl:when test="local-name(.) != 'name' and local-name(.) != 'id'"> + <xsl:copy/> + </xsl:when> + <xsl:otherwise> + <xsl:message>removing <xsl:value-of + select="local-name(.)"/></xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:copy> + <xsl:apply-templates mode="remove-ids"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:copy> + <xsl:for-each select="@*"> + <xsl:choose> + <xsl:when test="local-name(.) != 'id'"> + <xsl:copy/> + </xsl:when> + <xsl:otherwise> + <xsl:message>removing <xsl:value-of + select="local-name(.)"/></xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:apply-templates mode="remove-ids"/> + </xsl:copy> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="xref-to-prefix"/> +<xsl:template match="*" mode="xref-to-suffix"/> + +<xsl:template match="*" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:if test="$verbose"> + <xsl:message> + <xsl:text>Don't know what gentext to create for xref to: "</xsl:text> + <xsl:value-of select="name(.)"/> + <xsl:text>", ("</xsl:text> + <xsl:value-of select="(@id|@xml:id)[1]"/> + <xsl:text>")</xsl:text> + </xsl:message> + </xsl:if> + <xsl:text>???</xsl:text> +</xsl:template> + +<xsl:template match="title" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- if you xref to a title, xref to the parent... --> + <xsl:choose> + <!-- FIXME: how reliable is this? --> + <xsl:when test="contains(local-name(parent::*), 'info')"> + <xsl:apply-templates select="parent::*[2]" mode="xref-to"> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="parent::*" mode="xref-to"> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="abstract|authorblurb|personblurb|bibliodiv|bibliomset + |biblioset|blockquote|calloutlist|caution|colophon + |constraintdef|formalpara|glossdiv|important|indexdiv + |itemizedlist|legalnotice|lot|msg|msgexplan|msgmain + |msgrel|msgset|msgsub|note|orderedlist|partintro + |productionset|qandadiv|refsynopsisdiv|segmentedlist + |set|setindex|sidebar|tip|toc|variablelist|warning" + mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- catch-all for things with (possibly optional) titles --> + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="author|editor|othercredit|personname" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="person.name"/> +</xsl:template> + +<xsl:template match="authorgroup" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="person.name.list"/> +</xsl:template> + +<xsl:template match="figure|example|table|equation" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="procedure" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="task" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="cmdsynopsis" mode="xref-to"> + <xsl:apply-templates select="(.//command)[1]" mode="xref"/> +</xsl:template> + +<xsl:template match="funcsynopsis" mode="xref-to"> + <xsl:apply-templates select="(.//function)[1]" mode="xref"/> +</xsl:template> + +<xsl:template match="dedication|preface|chapter|appendix|article" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="bibliography" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to-prefix"> + <xsl:text>[</xsl:text> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to-suffix"> + <xsl:text>]</xsl:text> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <!-- handles both biblioentry and bibliomixed --> + <xsl:choose> + <xsl:when test="string(.) = ''"> + <xsl:variable name="bib" select="document($bibliography.collection,.)"/> + <xsl:variable name="id" select="(@id|@xml:id)[1]"/> + <xsl:variable name="entry" select="$bib/bibliography/ + *[@id=$id or @xml:id=$id][1]"/> + <xsl:choose> + <xsl:when test="$entry"> + <xsl:choose> + <xsl:when test="$bibliography.numbered != 0"> + <xsl:number from="bibliography" count="biblioentry|bibliomixed" + level="any" format="1"/> + </xsl:when> + <xsl:when test="local-name($entry/*[1]) = 'abbrev'"> + <xsl:apply-templates select="$entry/*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>No bibliography entry: </xsl:text> + <xsl:value-of select="$id"/> + <xsl:text> found in </xsl:text> + <xsl:value-of select="$bibliography.collection"/> + </xsl:message> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="$bibliography.numbered != 0"> + <xsl:number from="bibliography" count="biblioentry|bibliomixed" + level="any" format="1"/> + </xsl:when> + <xsl:when test="local-name(*[1]) = 'abbrev'"> + <xsl:apply-templates select="*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="glossary" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="glossentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + <xsl:choose> + <xsl:when test="$glossentry.show.acronym = 'primary'"> + <xsl:choose> + <xsl:when test="acronym|abbrev"> + <xsl:apply-templates select="(acronym|abbrev)[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="glossterm[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="glossterm[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="glossterm" mode="xref-to"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="index" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="listitem" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="section|simplesect + |sect1|sect2|sect3|sect4|sect5 + |refsect1|refsect2|refsect3|refsection" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + <!-- FIXME: What about "in Chapter X"? --> +</xsl:template> + +<xsl:template match="bridgehead" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> + <!-- FIXME: What about "in Chapter X"? --> +</xsl:template> + +<xsl:template match="qandaset" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="qandadiv" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="qandaentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="question[1]" mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="question|answer" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="part|reference" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="refentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:choose> + <xsl:when test="refmeta/refentrytitle"> + <xsl:apply-templates select="refmeta/refentrytitle"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="refnamediv/refname[1]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates select="refmeta/manvolnum"/> +</xsl:template> + +<xsl:template match="refnamediv" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="refname[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="refname" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates mode="xref-to"/> +</xsl:template> + +<xsl:template match="step" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="varlistentry" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="term[1]" mode="xref-to"> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="varlistentry/term" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <!-- to avoid the comma that will be generated if there are several terms --> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="co" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:apply-templates select="." mode="callout-bug"/> +</xsl:template> + +<xsl:template match="area|areaset" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + + <xsl:call-template name="callout-bug"> + <xsl:with-param name="conum"> + <xsl:apply-templates select="." mode="conumber"/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template match="book" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:apply-templates select="." mode="object.xref.markup"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<xsl:template match="para" mode="xref-to"> + <xsl:param name="referrer"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="verbose" select="1"/> + + <xsl:variable name="context" select="(ancestor::simplesect + |ancestor::section + |ancestor::sect1 + |ancestor::sect2 + |ancestor::sect3 + |ancestor::sect4 + |ancestor::sect5 + |ancestor::refsection + |ancestor::refsect1 + |ancestor::refsect2 + |ancestor::refsect3 + |ancestor::chapter + |ancestor::appendix + |ancestor::preface + |ancestor::partintro + |ancestor::dedication + |ancestor::colophon + |ancestor::bibliography + |ancestor::index + |ancestor::glossary + |ancestor::glossentry + |ancestor::listitem + |ancestor::varlistentry)[last()]"/> + + <xsl:apply-templates select="$context" mode="xref-to"> + <xsl:with-param name="purpose" select="'xref'"/> + <xsl:with-param name="xrefstyle" select="$xrefstyle"/> + <xsl:with-param name="referrer" select="$referrer"/> + <xsl:with-param name="verbose" select="$verbose"/> + </xsl:apply-templates> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="object.title.markup"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="author" mode="xref-title"> + <xsl:variable name="title"> + <xsl:call-template name="person.name"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="authorgroup" mode="xref-title"> + <xsl:variable name="title"> + <xsl:call-template name="person.name.list"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="cmdsynopsis" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="(.//command)[1]" mode="xref"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="funcsynopsis" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="(.//function)[1]" mode="xref"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="biblioentry|bibliomixed" mode="xref-title"> + <!-- handles both biblioentry and bibliomixed --> + <xsl:variable name="title"> + <xsl:text>[</xsl:text> + <xsl:choose> + <xsl:when test="local-name(*[1]) = 'abbrev'"> + <xsl:apply-templates select="*[1]"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>]</xsl:text> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<xsl:template match="step" mode="xref-title"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="step[not(./title)]" mode="title.markup"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Step'"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:apply-templates select="." mode="number"/> +</xsl:template> + +<xsl:template match="co" mode="xref-title"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="callout-bug"/> + </xsl:variable> + + <xsl:value-of select="$title"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="link" name="link"> + <xsl:param name="linkend" select="@linkend"/> + <xsl:param name="a.target"/> + <xsl:param name="xhref" select="@xlink:href"/> + + <xsl:variable name="content"> + <xsl:call-template name="anchor"/> + <xsl:choose> + <xsl:when test="count(child::node()) > 0"> + <!-- If it has content, use it --> + <xsl:apply-templates/> + </xsl:when> + <!-- else look for an endterm --> + <xsl:when test="@endterm"> + <xsl:variable name="etargets" select="key('id',@endterm)"/> + <xsl:variable name="etarget" select="$etargets[1]"/> + <xsl:choose> + <xsl:when test="count($etarget) = 0"> + <xsl:message> + <xsl:value-of select="count($etargets)"/> + <xsl:text>Endterm points to nonexistent ID: </xsl:text> + <xsl:value-of select="@endterm"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$etarget" mode="endterm"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <!-- Use the xlink:href if no other text --> + <xsl:when test="@xlink:href"> + <xsl:value-of select="@xlink:href"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Link element has no content and no Endterm. </xsl:text> + <xsl:text>Nothing to show in the link to </xsl:text> + <xsl:value-of select="(@xlink:href|@linkend)[1]"/> + </xsl:message> + <xsl:text>???</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:call-template name="simple.xlink"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="linkend" select="$linkend"/> + <xsl:with-param name="content" select="$content"/> + <xsl:with-param name="a.target" select="$a.target"/> + <xsl:with-param name="xhref" select="$xhref"/> + </xsl:call-template> + +</xsl:template> + +<xsl:template match="ulink" name="ulink"> + <xsl:param name="url" select="@url"/> + <xsl:variable name="link"> + <a> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="@id or @xml:id"> + <xsl:attribute name="name"> + <xsl:value-of select="(@id|@xml:id)[1]"/> + </xsl:attribute> + </xsl:if> + <xsl:attribute name="href"><xsl:value-of select="$url"/></xsl:attribute> + <xsl:if test="$ulink.target != ''"> + <xsl:attribute name="target"> + <xsl:value-of select="$ulink.target"/> + </xsl:attribute> + </xsl:if> + <xsl:choose> + <xsl:when test="count(child::node())=0"> + <xsl:value-of select="$url"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </a> + </xsl:variable> + + <xsl:choose> + <xsl:when test="function-available('suwl:unwrapLinks')"> + <xsl:copy-of select="suwl:unwrapLinks($link)"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$link"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="olink" name="olink"> + <!-- olink content may be passed in from xlink olink --> + <xsl:param name="content" select="NOTANELEMENT"/> + + <xsl:call-template name="anchor"/> + + <xsl:variable name="localinfo" select="@localinfo"/> + + <xsl:choose> + <!-- olinks resolved by stylesheet and target database --> + <xsl:when test="@targetdoc or @targetptr or + (@xlink:role=$xolink.role and + contains(@xlink:href, '#') )" > + + <xsl:variable name="targetdoc.att"> + <xsl:choose> + <xsl:when test="@targetdoc != ''"> + <xsl:value-of select="@targetdoc"/> + </xsl:when> + <xsl:when test="@xlink:role=$xolink.role and + contains(@xlink:href, '#')" > + <xsl:value-of select="substring-before(@xlink:href, '#')"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="targetptr.att"> + <xsl:choose> + <xsl:when test="@targetptr != ''"> + <xsl:value-of select="@targetptr"/> + </xsl:when> + <xsl:when test="@xlink:role=$xolink.role and + contains(@xlink:href, '#')" > + <xsl:value-of select="substring-after(@xlink:href, '#')"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="olink.lang"> + <xsl:call-template name="l10n.language"> + <xsl:with-param name="xref-context" select="true()"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="target.database.filename"> + <xsl:call-template name="select.target.database"> + <xsl:with-param name="targetdoc.att" select="$targetdoc.att"/> + <xsl:with-param name="targetptr.att" select="$targetptr.att"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="target.database" + select="document($target.database.filename,/)"/> + + <xsl:if test="$olink.debug != 0"> + <xsl:message> + <xsl:text>Olink debug: root element of target.database '</xsl:text> + <xsl:value-of select="$target.database.filename"/> + <xsl:text>' is '</xsl:text> + <xsl:value-of select="local-name($target.database/*[1])"/> + <xsl:text>'.</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="olink.key"> + <xsl:call-template name="select.olink.key"> + <xsl:with-param name="targetdoc.att" select="$targetdoc.att"/> + <xsl:with-param name="targetptr.att" select="$targetptr.att"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:variable> + + <xsl:if test="string-length($olink.key) = 0"> + <xsl:message> + <xsl:text>Error: unresolved olink: </xsl:text> + <xsl:text>targetdoc/targetptr = '</xsl:text> + <xsl:value-of select="$targetdoc.att"/> + <xsl:text>/</xsl:text> + <xsl:value-of select="$targetptr.att"/> + <xsl:text>'.</xsl:text> + </xsl:message> + </xsl:if> + + <xsl:variable name="href"> + <xsl:call-template name="make.olink.href"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="hottext"> + <xsl:choose> + <xsl:when test="$content"> + <xsl:copy-of select="$content"/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="olink.hottext"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + <xsl:with-param name="target.database" select="$target.database"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="olink.docname.citation"> + <xsl:call-template name="olink.document.citation"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="olink.page.citation"> + <xsl:call-template name="olink.page.citation"> + <xsl:with-param name="olink.key" select="$olink.key"/> + <xsl:with-param name="target.database" select="$target.database"/> + <xsl:with-param name="olink.lang" select="$olink.lang"/> + </xsl:call-template> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$href != ''"> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$hottext"/> + </a> + <xsl:copy-of select="$olink.page.citation"/> + <xsl:copy-of select="$olink.docname.citation"/> + </xsl:when> + <xsl:otherwise> + <span class="olink"><xsl:copy-of select="$hottext"/></span> + <xsl:copy-of select="$olink.page.citation"/> + <xsl:copy-of select="$olink.docname.citation"/> + </xsl:otherwise> + </xsl:choose> + + </xsl:when> + + <!-- Or use old olink mechanism --> + <xsl:otherwise> + <xsl:variable name="href"> + <xsl:choose> + <xsl:when test="@linkmode"> + <!-- use the linkmode to get the base URI, use localinfo as fragid --> + <xsl:variable name="modespec" select="key('id',@linkmode)"/> + <xsl:if test="count($modespec) != 1 + or local-name($modespec) != 'modespec'"> + <xsl:message>Warning: olink linkmode pointer is wrong.</xsl:message> + </xsl:if> + <xsl:value-of select="$modespec"/> + <xsl:if test="@localinfo"> + <xsl:text>#</xsl:text> + <xsl:value-of select="@localinfo"/> + </xsl:if> + </xsl:when> + <xsl:when test="@type = 'href'"> + <xsl:call-template name="olink.outline"> + <xsl:with-param name="outline.base.uri" + select="unparsed-entity-uri(@targetdocent)"/> + <xsl:with-param name="localinfo" select="@localinfo"/> + <xsl:with-param name="return" select="'href'"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$olink.resolver"/> + <xsl:text>?</xsl:text> + <xsl:value-of select="$olink.sysid"/> + <xsl:value-of select="unparsed-entity-uri(@targetdocent)"/> + <!-- XSL gives no access to the public identifier (grumble...) --> + <xsl:if test="@localinfo"> + <xsl:text>&</xsl:text> + <xsl:value-of select="$olink.fragid"/> + <xsl:value-of select="@localinfo"/> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$href != ''"> + <a href="{$href}"> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:call-template name="olink.hottext"/> + </a> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="olink.hottext"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="pagenumber.markup"> + <!-- no-op in HTML --> +</xsl:template> + + +<xsl:template name="olink.outline"> + <xsl:param name="outline.base.uri"/> + <xsl:param name="localinfo"/> + <xsl:param name="return" select="href"/> + + <xsl:variable name="outline-file" + select="concat($outline.base.uri, + $olink.outline.ext)"/> + + <xsl:variable name="outline" select="document($outline-file,.)/div"/> + + <xsl:variable name="node-href"> + <xsl:choose> + <xsl:when test="$localinfo != ''"> + <xsl:variable name="node" select="$outline// + *[@id=$localinfo or @xml:id=$localinfo]"/> + <xsl:value-of select="$node/@href"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$outline/@href"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="node-xref"> + <xsl:choose> + <xsl:when test="$localinfo != ''"> + <xsl:variable name="node" select="$outline// + *[@id=$localinfo or @xml:id=$localinfo]"/> + <xsl:copy-of select="$node/xref"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$outline/xref"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$return = 'href'"> + <xsl:value-of select="$node-href"/> + </xsl:when> + <xsl:when test="$return = 'xref'"> + <xsl:value-of select="$node-xref"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$node-xref"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="xref.xreflabel"> + <!-- called to process an xreflabel...you might use this to make --> + <!-- xreflabels come out in the right font for different targets, --> + <!-- for example. --> + <xsl:param name="target" select="."/> + <xsl:value-of select="$target/@xreflabel"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="title" mode="xref"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="command" mode="xref"> + <xsl:call-template name="inline.boldseq"/> +</xsl:template> + +<xsl:template match="function" mode="xref"> + <xsl:call-template name="inline.monoseq"/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*" mode="insert.title.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="title"/> + + <xsl:choose> + <!-- FIXME: what about the case where titleabbrev is inside the info? --> + <xsl:when test="$purpose = 'xref' and titleabbrev"> + <xsl:apply-templates select="." mode="titleabbrev.markup"/> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$title"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="chapter|appendix" mode="insert.title.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="title"/> + + <xsl:choose> + <xsl:when test="$purpose = 'xref'"> + <i> + <xsl:copy-of select="$title"/> + </i> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$title"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="*" mode="insert.subtitle.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="subtitle"/> + + <xsl:copy-of select="$subtitle"/> +</xsl:template> + +<xsl:template match="*" mode="insert.label.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="label"/> + + <xsl:copy-of select="$label"/> +</xsl:template> + +<xsl:template match="*" mode="insert.pagenumber.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="pagenumber"/> + + <xsl:copy-of select="$pagenumber"/> +</xsl:template> + +<xsl:template match="*" mode="insert.direction.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="direction"/> + + <xsl:copy-of select="$direction"/> +</xsl:template> + +<xsl:template match="*" mode="insert.olink.docname.markup"> + <xsl:param name="purpose"/> + <xsl:param name="xrefstyle"/> + <xsl:param name="docname"/> + + <span class="olinkdocname"> + <xsl:copy-of select="$docname"/> + </span> + +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/lib/lib.xsl b/docs/xsl-generic/lib/lib.xsl new file mode 100644 index 00000000..5eee4862 --- /dev/null +++ b/docs/xsl-generic/lib/lib.xsl @@ -0,0 +1,480 @@ +<?xml version="1.0" encoding="ASCII"?> +<!-- ******************************************************************** + $Id: lib.xweb 7102 2007-07-20 15:35:24Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + This module implements DTD-independent functions + + ******************************************************************** --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<xsl:template name="dot.count"> + <!-- Returns the number of "." characters in a string --> + <xsl:param name="string"/> + <xsl:param name="count" select="0"/> + <xsl:choose> + <xsl:when test="contains($string, '.')"> + <xsl:call-template name="dot.count"> + <xsl:with-param name="string" select="substring-after($string, '.')"/> + <xsl:with-param name="count" select="$count+1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="copy-string"> + <!-- returns 'count' copies of 'string' --> + <xsl:param name="string"/> + <xsl:param name="count" select="0"/> + <xsl:param name="result"/> + + <xsl:choose> + <xsl:when test="$count>0"> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="count" select="$count - 1"/> + <xsl:with-param name="result"> + <xsl:value-of select="$result"/> + <xsl:value-of select="$string"/> + </xsl:with-param> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$result"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="string.subst"> + <xsl:param name="string"/> + <xsl:param name="target"/> + <xsl:param name="replacement"/> + + <xsl:choose> + <xsl:when test="contains($string, $target)"> + <xsl:variable name="rest"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="substring-after($string, $target)"/> + <xsl:with-param name="target" select="$target"/> + <xsl:with-param name="replacement" select="$replacement"/> + </xsl:call-template> + </xsl:variable> + <xsl:value-of select="concat(substring-before($string, $target), $replacement, $rest)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$string"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="xpointer.idref"> + <xsl:param name="xpointer">http://...</xsl:param> + <xsl:choose> + <xsl:when test="starts-with($xpointer, '#xpointer(id(')"> + <xsl:variable name="rest" select="substring-after($xpointer, '#xpointer(id(')"/> + <xsl:variable name="quote" select="substring($rest, 1, 1)"/> + <xsl:value-of select="substring-before(substring-after($xpointer, $quote), $quote)"/> + </xsl:when> + <xsl:when test="starts-with($xpointer, '#')"> + <xsl:value-of select="substring-after($xpointer, '#')"/> + </xsl:when> + <!-- otherwise it's a pointer to some other document --> + </xsl:choose> +</xsl:template> +<xsl:template name="length-magnitude"> + <xsl:param name="length" select="'0pt'"/> + + <xsl:choose> + <xsl:when test="string-length($length) = 0"/> + <xsl:when test="substring($length,1,1) = '0' or substring($length,1,1) = '1' or substring($length,1,1) = '2' or substring($length,1,1) = '3' or substring($length,1,1) = '4' or substring($length,1,1) = '5' or substring($length,1,1) = '6' or substring($length,1,1) = '7' or substring($length,1,1) = '8' or substring($length,1,1) = '9' or substring($length,1,1) = '.'"> + <xsl:value-of select="substring($length,1,1)"/> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="substring($length,2)"/> + </xsl:call-template> + </xsl:when> + </xsl:choose> +</xsl:template> +<xsl:template name="length-units"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="default.units" select="'px'"/> + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$units = ''"> + <xsl:value-of select="$default.units"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$units"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="length-spec"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="default.units" select="'px'"/> + + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:value-of select="$magnitude"/> + <xsl:choose> + <xsl:when test="$units='cm' or $units='mm' or $units='in' or $units='pt' or $units='pc' or $units='px' or $units='em'"> + <xsl:value-of select="$units"/> + </xsl:when> + <xsl:when test="$units = ''"> + <xsl:value-of select="$default.units"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized unit of measure: </xsl:text> + <xsl:value-of select="$units"/> + <xsl:text>.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="length-in-points"> + <xsl:param name="length" select="'0pt'"/> + <xsl:param name="em.size" select="10"/> + <xsl:param name="pixels.per.inch" select="90"/> + + <xsl:variable name="magnitude"> + <xsl:call-template name="length-magnitude"> + <xsl:with-param name="length" select="$length"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="units"> + <xsl:value-of select="substring($length, string-length($magnitude)+1)"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$units = 'pt'"> + <xsl:value-of select="$magnitude"/> + </xsl:when> + <xsl:when test="$units = 'cm'"> + <xsl:value-of select="$magnitude div 2.54 * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'mm'"> + <xsl:value-of select="$magnitude div 25.4 * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'in'"> + <xsl:value-of select="$magnitude * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'pc'"> + <xsl:value-of select="$magnitude * 12.0"/> + </xsl:when> + <xsl:when test="$units = 'px'"> + <xsl:value-of select="$magnitude div $pixels.per.inch * 72.0"/> + </xsl:when> + <xsl:when test="$units = 'em'"> + <xsl:value-of select="$magnitude * $em.size"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized unit of measure: </xsl:text> + <xsl:value-of select="$units"/> + <xsl:text>.</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="pi-attribute"> + <xsl:param name="pis" select="processing-instruction('BOGUS_PI')"/> + <xsl:param name="attribute">filename</xsl:param> + <xsl:param name="count">1</xsl:param> + + <xsl:choose> + <xsl:when test="$count>count($pis)"> + <!-- not found --> + </xsl:when> + <xsl:otherwise> + <xsl:variable name="pi"> + <xsl:value-of select="$pis[$count]"/> + </xsl:variable> + <xsl:variable name="pivalue"> + <xsl:value-of select="concat(' ', normalize-space($pi))"/> + </xsl:variable> + <xsl:choose> + <xsl:when test="contains($pivalue,concat(' ', $attribute, '='))"> + <xsl:variable name="rest" select="substring-after($pivalue,concat(' ', $attribute,'='))"/> + <xsl:variable name="quote" select="substring($rest,1,1)"/> + <xsl:value-of select="substring-before(substring($rest,2),$quote)"/> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="pi-attribute"> + <xsl:with-param name="pis" select="$pis"/> + <xsl:with-param name="attribute" select="$attribute"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="lookup.key"> + <xsl:param name="key" select="''"/> + <xsl:param name="table" select="''"/> + + <xsl:if test="contains($table, ' ')"> + <xsl:choose> + <xsl:when test="substring-before($table, ' ') = $key"> + <xsl:variable name="rest" select="substring-after($table, ' ')"/> + <xsl:choose> + <xsl:when test="contains($rest, ' ')"> + <xsl:value-of select="substring-before($rest, ' ')"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$rest"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="lookup.key"> + <xsl:with-param name="key" select="$key"/> + <xsl:with-param name="table" select="substring-after(substring-after($table,' '), ' ')"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:if> +</xsl:template> +<xsl:template name="xpath.location"> + <xsl:param name="node" select="."/> + <xsl:param name="path" select="''"/> + + <xsl:variable name="next.path"> + <xsl:value-of select="local-name($node)"/> + <xsl:if test="$path != ''">/</xsl:if> + <xsl:value-of select="$path"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$node/parent::*"> + <xsl:call-template name="xpath.location"> + <xsl:with-param name="node" select="$node/parent::*"/> + <xsl:with-param name="path" select="$next.path"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:text>/</xsl:text> + <xsl:value-of select="$next.path"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="comment-escape-string"> + <xsl:param name="string" select="''"/> + + <xsl:if test="starts-with($string, '-')"> + <xsl:text> </xsl:text> + </xsl:if> + + <xsl:call-template name="comment-escape-string.recursive"> + <xsl:with-param name="string" select="$string"/> + </xsl:call-template> + + <xsl:if test="substring($string, string-length($string), 1) = '-'"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> +<xsl:template name="comment-escape-string.recursive"> + <xsl:param name="string" select="''"/> + <xsl:choose> + <xsl:when test="contains($string, '--')"> + <xsl:value-of select="substring-before($string, '--')"/> + <xsl:value-of select="'- -'"/> + <xsl:call-template name="comment-escape-string.recursive"> + <xsl:with-param name="string" select="substring-after($string, '--')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$string"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + <xsl:template name="str.tokenize.keep.delimiters"> + <xsl:param name="string" select="''"/> + <xsl:param name="delimiters" select="' '"/> + <xsl:choose> + <xsl:when test="not($string)"/> + <xsl:when test="not($delimiters)"> + <xsl:call-template name="str.tokenize.keep.delimiters-characters"> + <xsl:with-param name="string" select="$string"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="delimiters" select="$delimiters"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template name="str.tokenize.keep.delimiters-characters"> + <xsl:param name="string"/> + <xsl:if test="$string"> + <ssb:token xmlns:ssb="http://sideshowbarker.net/ns"><xsl:value-of select="substring($string, 1, 1)"/></ssb:token> + <xsl:call-template name="str.tokenize.keep.delimiters-characters"> + <xsl:with-param name="string" select="substring($string, 2)"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + <xsl:template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:param name="string"/> + <xsl:param name="delimiters"/> + <xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/> + <xsl:choose> + <xsl:when test="not($delimiter)"> + <ssb:token xmlns:ssb="http://sideshowbarker.net/ns"><xsl:value-of select="$string"/></ssb:token> + </xsl:when> + <xsl:when test="contains($string, $delimiter)"> + <xsl:if test="not(starts-with($string, $delimiter))"> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="substring-before($string, $delimiter)"/> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)"/> + </xsl:call-template> + </xsl:if> + <!-- output each delimiter --> + <xsl:value-of select="$delimiter"/> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="substring-after($string, $delimiter)"/> + <xsl:with-param name="delimiters" select="$delimiters"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="str.tokenize.keep.delimiters-delimiters"> + <xsl:with-param name="string" select="$string"/> + <xsl:with-param name="delimiters" select="substring($delimiters, 2)"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + <xsl:template name="apply-string-subst-map"> + <xsl:param name="content"/> + <xsl:param name="map.contents"/> + <xsl:variable name="replaced_text"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="$map.contents[1]/@oldstring"/> + <xsl:with-param name="replacement" select="$map.contents[1]/@newstring"/> + </xsl:call-template> + </xsl:variable> + <xsl:choose> + <xsl:when test="$map.contents[2]"> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$replaced_text"/> + <xsl:with-param name="map.contents" select="$map.contents[position() > 1]"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$replaced_text"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +<xsl:template name="count.uri.path.depth"> + <xsl:param name="filename" select="''"/> + <xsl:param name="count" select="0"/> + + <xsl:choose> + <xsl:when test="contains($filename, '/')"> + <xsl:call-template name="count.uri.path.depth"> + <xsl:with-param name="filename" select="substring-after($filename, '/')"/> + <xsl:with-param name="count" select="$count + 1"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$count"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> +<xsl:template name="trim.common.uri.paths"> + <xsl:param name="uriA" select="''"/> + <xsl:param name="uriB" select="''"/> + <xsl:param name="return" select="'A'"/> + + <xsl:choose> + <xsl:when test="contains($uriA, '/') and contains($uriB, '/') and substring-before($uriA, '/') = substring-before($uriB, '/')"> + <xsl:call-template name="trim.common.uri.paths"> + <xsl:with-param name="uriA" select="substring-after($uriA, '/')"/> + <xsl:with-param name="uriB" select="substring-after($uriB, '/')"/> + <xsl:with-param name="return" select="$return"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:choose> + <xsl:when test="$return = 'A'"> + <xsl:value-of select="$uriA"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$uriB"/> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + + <xsl:template name="trim.text"> + <xsl:param name="contents" select="."/> + <xsl:variable name="contents-left-trimmed"> + <xsl:call-template name="trim-left"> + <xsl:with-param name="contents" select="$contents"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="contents-trimmed"> + <xsl:call-template name="trim-right"> + <xsl:with-param name="contents" select="$contents-left-trimmed"/> + </xsl:call-template> + </xsl:variable> + <xsl:value-of select="$contents-trimmed"/> + </xsl:template> + + <xsl:template name="trim-left"> + <xsl:param name="contents"/> + <xsl:choose> + <xsl:when test="starts-with($contents,' ') or starts-with($contents,' ') or starts-with($contents,' ') or starts-with($contents,' ')"> + <xsl:call-template name="trim-left"> + <xsl:with-param name="contents" select="substring($contents, 2)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template name="trim-right"> + <xsl:param name="contents"/> + <xsl:variable name="last-char"> + <xsl:value-of select="substring($contents, string-length($contents), 1)"/> + </xsl:variable> + <xsl:choose> + <xsl:when test="($last-char = ' ') or ($last-char = ' ') or ($last-char = ' ') or ($last-char = ' ')"> + <xsl:call-template name="trim-right"> + <xsl:with-param name="contents" select="substring($contents, 1, string-length($contents) - 1)"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/block.xsl b/docs/xsl-generic/manpages/block.xsl new file mode 100644 index 00000000..efee63be --- /dev/null +++ b/docs/xsl-generic/manpages/block.xsl @@ -0,0 +1,296 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: block.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="caution|important|note|tip|warning"> + <xsl:call-template name="nested-section-title"/> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="formalpara"> + <xsl:variable name="title.wrapper"> + <xsl:value-of select="normalize-space(title[1])"/> + </xsl:variable> + <xsl:text>.PP </xsl:text> + <!-- * don't put linebreak after head; instead render it as a "run in" --> + <!-- * head, that is, inline, with a period and space following it --> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title.wrapper)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text>. </xsl:text> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="formalpara/para"> + <xsl:call-template name="mixed-block"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="para"> + <!-- * FIXME: Need to extract the ancestor::footnote, etc. checking and --> + <!-- * move to named template so that we can call it from templates for --> + <!-- * other block elements also --> + <xsl:choose> + <!-- * If a para is a descendant of a footnote, etc., then indent it --> + <!-- * (unless it is the first child, in which case don't generate --> + <!-- * anything at all to mark its start). --> + <!-- * FIXME: *blurb checking should not be munged in here the way --> + <!-- * it currently is; this probably breaks blurb indenting. --> + <xsl:when test="ancestor::footnote or + ancestor::annotation or + ancestor::authorblurb or + ancestor::personblurb"> + <xsl:if test="preceding-sibling::*[not(name() ='')]"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS 4n</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:text>.PP</xsl:text> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:call-template name="mixed-block"/> + <xsl:if test="ancestor::footnote or + ancestor::annotation or + ancestor::authorblurb or + ancestor::personblurb"> + <xsl:if test="preceding-sibling::*[not(name() ='')]"> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:text> </xsl:text> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * Yes, address, synopsis, and funcsynopsisinfo are verbatim environments. --> +<xsl:template match="literallayout|programlisting|screen| + address|synopsis|funcsynopsisinfo"> + <xsl:param name="indent"> + <!-- * Only indent this verbatim if $man.indent.verbatims is --> + <!-- * non-zero and it is not a child of a *synopsis element --> + <xsl:if test="not($man.indent.verbatims = 0) and + not(substring(local-name(..), + string-length(local-name(..))-7) = 'synopsis')"> + <xsl:text>Yes</xsl:text> + </xsl:if> + </xsl:param> + + <xsl:choose> + <!-- * Check to see if this verbatim item is within a parent element that --> + <!-- * allows mixed content. --> + <!-- * --> + <!-- * If it is within a mixed-content parent, then a line space is --> + <!-- * already added before it by the mixed-block template, so we don't --> + <!-- * need to add one here. --> + <!-- * --> + <!-- * If it is not within a mixed-content parent, then we need to add a --> + <!-- * line space before it. --> + <xsl:when test="parent::caption|parent::entry|parent::para| + parent::td|parent::th" /> <!-- do nothing --> + <xsl:otherwise> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$indent = 'Yes'"> + <!-- * start indented section --> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($man.indent.width = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$man.indent.width"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:choose> + <xsl:when test="self::funcsynopsisinfo"> + <!-- * All Funcsynopsisinfo content is by default rendered in bold, --> + <!-- * because the man(7) man page says this: --> + <!-- * --> + <!-- * For functions, the arguments are always specified using --> + <!-- * italics, even in the SYNOPSIS section, where the rest of --> + <!-- * the function is specified in bold --> + <!-- * --> + <!-- * Look through the contents of the man/man2 and man3 directories --> + <!-- * on your system, and you'll see that most existing pages do follow --> + <!-- * this "bold everything in function synopsis" rule. --> + <!-- * --> + <!-- * Users who don't want the bold output can choose to adjust the --> + <!-- * man.font.funcsynopsisinfo parameter on their own. So even if you --> + <!-- * don't personally like the way it looks, please don't change the --> + <!-- * default to be non-bold - because it's a convention that's --> + <!-- * followed is the vast majority of existing man pages that document --> + <!-- * functions, and we need to follow it by default, like it or no. --> + <xsl:text>.ft </xsl:text> + <xsl:value-of select="$man.font.funcsynopsisinfo"/> + <xsl:text> </xsl:text> + <xsl:text>.nf </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.fi </xsl:text> + <xsl:text>.ft </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Other verbatims do not need to get bolded --> + <xsl:text>.nf </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.fi </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$indent = 'Yes'"> + <!-- * end indented section --> + <xsl:text>.RE </xsl:text> + </xsl:if> + <!-- * if first following sibling node of this verbatim --> + <!-- * environment is a text node, output a line of space before it --> + <xsl:if test="following-sibling::node()[1][name(.) = '']"> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="table|informaltable"> + <xsl:apply-templates select="." mode="to.tbl"> + <!--* we call the to.tbl mode with the "source" param so that we can --> + <!--* preserve the context information and pass it down to the --> + <!--* named templates that do the actual table processing --> + <xsl:with-param name="source" select="ancestor::refentry/refnamediv[1]/refname[1]"/> + </xsl:apply-templates> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="informalexample"> + <xsl:apply-templates/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="figure|example"> + <xsl:variable name="param.placement" + select="substring-after(normalize-space($formal.title.placement), + concat(local-name(.), ' '))"/> + + <xsl:variable name="placement"> + <xsl:choose> + <xsl:when test="contains($param.placement, ' ')"> + <xsl:value-of select="substring-before($param.placement, ' ')"/> + </xsl:when> + <xsl:when test="$param.placement = ''">before</xsl:when> + <xsl:otherwise> + <xsl:value-of select="$param.placement"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:text>.PP </xsl:text> + <xsl:call-template name="formal.object"> + <xsl:with-param name="placement" select="$placement"/> + </xsl:call-template> + <xsl:text> </xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="mediaobject"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="imageobject"> + <xsl:text>[IMAGE]</xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="textobject[parent::inlinemediaobject]"> + <xsl:text>[</xsl:text> + <xsl:value-of select="."/> + <xsl:text>]</xsl:text> +</xsl:template> + +<xsl:template match="textobject"> + <xsl:apply-templates/> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="formal.object"> + <xsl:param name="placement" select="'before'"/> + <xsl:param name="class" select="local-name(.)"/> + + <xsl:choose> + <xsl:when test="$placement = 'before'"> + <xsl:call-template name="formal.object.heading"/> + <xsl:apply-templates/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates/> + <xsl:call-template name="formal.object.heading"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="formal.object.heading"> + <xsl:param name="object" select="."/> + <xsl:param name="title"> + <xsl:apply-templates select="$object" mode="object.title.markup.textonly"/> + </xsl:param> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + + <xsl:text> </xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * suppress abstract --> +<xsl:template match="abstract"/> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/charmap.groff.xsl b/docs/xsl-generic/manpages/charmap.groff.xsl new file mode 100644 index 00000000..7587659f --- /dev/null +++ b/docs/xsl-generic/manpages/charmap.groff.xsl @@ -0,0 +1,5985 @@ +<?xml version="1.0" encoding="US-ASCII"?> +<xsl:stylesheet version="2.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:u="http://docbook.sf.net/xmlns/unichar/1.0" + exclude-result-prefixes="u"> + +<!-- ******************************************************************** + $Id: charmap.groff.xsl 6528 2007-01-19 08:54:04Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:character-map name="groff"> + + <!-- * *************************************************************** --> + <!-- * Commentary --> + <!-- * *************************************************************** --> + <!-- * --> + <!-- * This file maps a selection of Unicode symbols and special --> + <!-- * characters (about 800) to corresponding groff escape sequences.--> + <!-- * --> + <!-- * Although the format of this file follows the "character map" --> + <!-- * format described in the XSLT 2.0 specification[1], the file can --> + <!-- * also be used with an appropriate XSLT 1.0 stylesheet and any --> + <!-- * XSLT 1.0 processor. --> + <!-- * --> + <!-- * [1] http://www.w3.org/TR/xslt20/#character-maps --> + <!-- * --> + <!-- * In order to make the character map more readable, and to make --> + <!-- * it possible to create subsets of it at run time, it uses the --> + <!-- * following "extension attributes" (in the "unichar" namespace): --> + <!-- * --> + <!-- * - u:name = ISO character name (e.g., "OHM SIGN") --> + <!-- * - u:entity = ISO entity name (e.g., "ohm") --> + <!-- * - u:block = Unicode block name (e.g., "Letterlike Symbols") --> + <!-- * - u:class = character class (e.g., "bullets") --> + <!-- * --> + <!-- * Use of such extension attributes is permitted by the XSLT 2.0 --> + <!-- * spec; see the "Extension Attributes" section[2]. --> + <!-- * --> + <!-- * [2] http://www.w3.org/TR/xslt20/#extension-attributes --> + <!-- * --> + <!-- * *************************************************************** --> + <!-- * Acknowledgements --> + <!-- * *************************************************************** --> + <!-- * The following references were consulted when selecting roff --> + <!-- * mappings and character information: --> + <!-- * --> + <!-- * - groff_char(7) man page[3] --> + <!-- * - groff info file[4]; in particular, the "Page Motions" node[5] --> + <!-- * - tables in "Character Sets" chapter of "XML In a Nutshell"[6] --> + <!-- * - Zvon Character Search[7] --> + <!-- * --> + <!-- * [3] http://www.linux.se/showMan.php?TITLE=groff_char&SECTION=7 --> + <!-- * [4] http://www.fifi.org/cgi-bin/info2www?(groff) --> + <!-- * [5] http://www.fifi.org/cgi-bin/info2www?(groff)Page+Motions --> + <!-- * [6] http://www.ibiblio.org/xml/books/xian2/ --> + <!-- * [7] http://zvon.org/other/charSearch/PHP/search.php --> + <!-- * --> + <!-- * The initial version of this file (before the "string" mappings --> + <!-- * were added) was generated by taking the "unichars.el" file from --> + <!-- * Norm Walsh's "xmlunicode.el"[8] elisp distro, and running a --> + <!-- * script on it to convert it to XML. --> + <!-- * --> + <!-- * [8] http://nwalsh.com/emacs/xmlchars/ --> + <!-- * --> + <!-- * The idea for implementing a character map in the DocBook Project --> + <!-- * manpages system was inspired by Steve Cheng's docbook2x[9]; --> + <!-- * in particular, its "utf8trans" utility and character-map system. --> + <!-- * --> + <!-- * [9] http://docbook2x.sourceforge.net/ --> + <!-- * --> + <!-- * ################################################################# --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Latin-1/ISO-8859-1 --> + <!-- * x00a0 to x00ff --> + <!-- * ***************************************************************** --> + + <!-- * A no-break space can be written two ways in roff; the difference, --> + <!-- * according to the "Page Motions" node in the groff info page, is: --> + <!-- * --> + <!-- * "\ " = --> + <!-- * An unbreakable and unpaddable (i.e. not expanded during filling) --> + <!-- * space. --> + <!-- * --> + <!-- * "\~" = --> + <!-- * An unbreakable space that stretches like a normal --> + <!-- * inter-word space when a line is adjusted." --> + <!-- * --> + <!-- * Unfortunately, roff seems to do some weird things with long --> + <!-- * lines that only have words separated by "\~" spaces, so it's --> + <!-- * safer just to stick with the "\ " space --> + <xsl:output-character + character=" " + u:name="NO-BREAK SPACE" + u:entity="nbsp" + string="\ " + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¡" + u:name="INVERTED EXCLAMATION MARK" + u:entity="iexcl" + string="\(r!" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¢" + u:name="CENT SIGN" + u:entity="cent" + string="\(ct" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="£" + u:name="POUND SIGN" + u:entity="pound" + string="\(Po" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¤" + u:name="CURRENCY SIGN" + u:entity="curren" + string="\(Cs" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¥" + u:name="YEN SIGN" + u:entity="yen" + string="\(Ye" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¦" + u:name="BROKEN BAR" + u:entity="brvbar" + string="\(bb" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="§" + u:name="SECTION SIGN" + u:entity="sect" + string="\(sc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¨" + u:name="DIAERESIS" + u:entity="Dot" + string="\(ad" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="©" + u:name="COPYRIGHT SIGN" + u:entity="copy" + string="\(co" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="ª" + u:name="FEMININE ORDINAL INDICATOR" + u:entity="ordf" + string="\(Of" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="«" + u:name="LEFT-POINTING DOUBLE ANGLE QUOTATION MARK" + u:entity="laquo" + string="\(Fo" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¬" + u:name="NOT SIGN" + u:entity="not" + string="\(no" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <!-- * groff_char(7) man page sayxsl: "the soft hyphen control character --> + <!-- * (prints as itself). groff never use this character for output --> + <!-- * (thus it is omitted in the table below); the input character 173 --> + <!-- * is onto \%." --> + <xsl:output-character + character="­" + u:name="SOFT HYPHEN" + u:entity="shy" + string="\%" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="®" + u:name="REGISTERED SIGN" + u:entity="reg" + string="\(rg" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¯" + u:name="MACRON" + u:entity="macr" + string="\(a-" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="°" + u:name="DEGREE SIGN" + u:entity="deg" + string="\(de" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="±" + u:name="PLUS-MINUS SIGN" + u:entity="plusmn" + string="\(+-" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="²" + u:name="SUPERSCRIPT TWO" + u:entity="sup2" + string="\(S2" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="³" + u:name="SUPERSCRIPT THREE" + u:entity="sup3" + string="\(S3" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="´" + u:name="ACUTE ACCENT" + u:entity="acute" + string="\(aa" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="µ" + u:name="MICRO SIGN" + u:entity="micro" + string="\(mc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¶" + u:name="PILCROW SIGN" + u:entity="para" + string="\(ps" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <!-- * according to groff_char(7), I think the escape string \(pc --> + <!-- * "periodcentered" could also be used for middot; not sure which --> + <!-- * is better, but "md" mnemonic is a better fit :-) --> + <xsl:output-character + character="·" + u:name="MIDDLE DOT" + u:entity="middot" + string="\(md" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¸" + u:name="CEDILLA" + u:entity="cedil" + string="\(ac" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¹" + u:name="SUPERSCRIPT ONE" + u:entity="sup1" + string="\(S1" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="º" + u:name="MASCULINE ORDINAL INDICATOR" + u:entity="ordm" + string="\(Om" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="»" + u:name="RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK" + u:entity="raquo" + string="\(Fc" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¼" + u:name="VULGAR FRACTION ONE QUARTER" + u:entity="frac14" + string="\(14" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="½" + u:name="VULGAR FRACTION ONE HALF" + u:entity="frac12" + string="\(12" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¾" + u:name="VULGAR FRACTION THREE QUARTERS" + u:entity="frac34" + string="\(34" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="¿" + u:name="INVERTED QUESTION MARK" + u:entity="iquest" + string="\(r?" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="À" + u:name="LATIN CAPITAL LETTER A WITH GRAVE" + u:entity="Agrave" + string="\(`A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Á" + u:name="LATIN CAPITAL LETTER A WITH ACUTE" + u:entity="Aacute" + string="\('A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Â" + u:name="LATIN CAPITAL LETTER A WITH CIRCUMFLEX" + u:entity="Acirc" + string="\(^A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ã" + u:name="LATIN CAPITAL LETTER A WITH TILDE" + u:entity="Atilde" + string="\(~A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ä" + u:name="LATIN CAPITAL LETTER A WITH DIAERESIS" + u:entity="Auml" + string="\(:A" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Å" + u:name="LATIN CAPITAL LETTER A WITH RING ABOVE" + u:entity="Aring" + string="\(oA" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Æ" + u:name="LATIN CAPITAL LETTER AE" + u:entity="AElig" + string="\(AE" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ç" + u:name="LATIN CAPITAL LETTER C WITH CEDILLA" + u:entity="Ccedil" + string="\(,C" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="È" + u:name="LATIN CAPITAL LETTER E WITH GRAVE" + u:entity="Egrave" + string="\(`E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="É" + u:name="LATIN CAPITAL LETTER E WITH ACUTE" + u:entity="Eacute" + string="\('E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ê" + u:name="LATIN CAPITAL LETTER E WITH CIRCUMFLEX" + u:entity="Ecirc" + string="\(^E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ë" + u:name="LATIN CAPITAL LETTER E WITH DIAERESIS" + u:entity="Euml" + string="\(:E" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ì" + u:name="LATIN CAPITAL LETTER I WITH GRAVE" + u:entity="Igrave" + string="\(`I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Í" + u:name="LATIN CAPITAL LETTER I WITH ACUTE" + u:entity="Iacute" + string="\('I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Î" + u:name="LATIN CAPITAL LETTER I WITH CIRCUMFLEX" + u:entity="Icirc" + string="\(^I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ï" + u:name="LATIN CAPITAL LETTER I WITH DIAERESIS" + u:entity="Iuml" + string="\(:I" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ð" + u:name="LATIN CAPITAL LETTER ETH" + u:entity="ETH" + string="\(-D" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ñ" + u:name="LATIN CAPITAL LETTER N WITH TILDE" + u:entity="Ntilde" + string="\(~N" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ò" + u:name="LATIN CAPITAL LETTER O WITH GRAVE" + u:entity="Ograve" + string="\(`O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ó" + u:name="LATIN CAPITAL LETTER O WITH ACUTE" + u:entity="Oacute" + string="\('O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ô" + u:name="LATIN CAPITAL LETTER O WITH CIRCUMFLEX" + u:entity="Ocirc" + string="\(^O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Õ" + u:name="LATIN CAPITAL LETTER O WITH TILDE" + u:entity="Otilde" + string="\(~O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ö" + u:name="LATIN CAPITAL LETTER O WITH DIAERESIS" + u:entity="Ouml" + string="\(:O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="×" + u:name="MULTIPLICATION SIGN" + u:entity="times" + string="\(mu" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="Ø" + u:name="LATIN CAPITAL LETTER O WITH STROKE" + u:entity="Oslash" + string="\(/O" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ù" + u:name="LATIN CAPITAL LETTER U WITH GRAVE" + u:entity="Ugrave" + string="\(`U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ú" + u:name="LATIN CAPITAL LETTER U WITH ACUTE" + u:entity="Uacute" + string="\('U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Û" + u:name="LATIN CAPITAL LETTER U WITH CIRCUMFLEX" + u:entity="Ucirc" + string="\(^U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ü" + u:name="LATIN CAPITAL LETTER U WITH DIAERESIS" + u:entity="Uuml" + string="\(:U" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Ý" + u:name="LATIN CAPITAL LETTER Y WITH ACUTE" + u:entity="Yacute" + string="\('Y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="Þ" + u:name="LATIN CAPITAL LETTER THORN" + u:entity="THORN" + string="\(TP" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ß" + u:name="LATIN SMALL LETTER SHARP S" + u:entity="szlig" + string="\(ss" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="à" + u:name="LATIN SMALL LETTER A WITH GRAVE" + u:entity="agrave" + string="\(`a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="á" + u:name="LATIN SMALL LETTER A WITH ACUTE" + u:entity="aacute" + string="\('a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="â" + u:name="LATIN SMALL LETTER A WITH CIRCUMFLEX" + u:entity="acirc" + string="\(^a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ã" + u:name="LATIN SMALL LETTER A WITH TILDE" + u:entity="atilde" + string="\(~a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ä" + u:name="LATIN SMALL LETTER A WITH DIAERESIS" + u:entity="auml" + string="\(:a" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="å" + u:name="LATIN SMALL LETTER A WITH RING ABOVE" + u:entity="aring" + string="\(oa" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="æ" + u:name="LATIN SMALL LETTER AE" + u:entity="aelig" + string="\(ae" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ç" + u:name="LATIN SMALL LETTER C WITH CEDILLA" + u:entity="ccedil" + string="\(,c" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="è" + u:name="LATIN SMALL LETTER E WITH GRAVE" + u:entity="egrave" + string="\(`e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="é" + u:name="LATIN SMALL LETTER E WITH ACUTE" + u:entity="eacute" + string="\('e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ê" + u:name="LATIN SMALL LETTER E WITH CIRCUMFLEX" + u:entity="ecirc" + string="\(^e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ë" + u:name="LATIN SMALL LETTER E WITH DIAERESIS" + u:entity="euml" + string="\(:e" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ì" + u:name="LATIN SMALL LETTER I WITH GRAVE" + u:entity="igrave" + string="\(`i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="í" + u:name="LATIN SMALL LETTER I WITH ACUTE" + u:entity="iacute" + string="\('i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="î" + u:name="LATIN SMALL LETTER I WITH CIRCUMFLEX" + u:entity="icirc" + string="\(^i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ï" + u:name="LATIN SMALL LETTER I WITH DIAERESIS" + u:entity="iuml" + string="\(:i" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ð" + u:name="LATIN SMALL LETTER ETH" + u:entity="eth" + string="\(Sd" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ñ" + u:name="LATIN SMALL LETTER N WITH TILDE" + u:entity="ntilde" + string="\(~n" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ò" + u:name="LATIN SMALL LETTER O WITH GRAVE" + u:entity="ograve" + string="\(`o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ó" + u:name="LATIN SMALL LETTER O WITH ACUTE" + u:entity="oacute" + string="\('o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ô" + u:name="LATIN SMALL LETTER O WITH CIRCUMFLEX" + u:entity="ocirc" + string="\(^o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="õ" + u:name="LATIN SMALL LETTER O WITH TILDE" + u:entity="otilde" + string="\(~o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ö" + u:name="LATIN SMALL LETTER O WITH DIAERESIS" + u:entity="ouml" + string="\(:o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="÷" + u:name="DIVISION SIGN" + u:entity="divide" + string="\(di" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="symbols" + /> + <xsl:output-character + character="ø" + u:name="LATIN SMALL LETTER O WITH STROKE" + u:entity="oslash" + string="\(/o" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ù" + u:name="LATIN SMALL LETTER U WITH GRAVE" + u:entity="ugrave" + string="\(`u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ú" + u:name="LATIN SMALL LETTER U WITH ACUTE" + u:entity="uacute" + string="\('u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="û" + u:name="LATIN SMALL LETTER U WITH CIRCUMFLEX" + u:entity="ucirc" + string="\(^u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ü" + u:name="LATIN SMALL LETTER U WITH DIAERESIS" + u:entity="uuml" + string="\(:u" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ý" + u:name="LATIN SMALL LETTER Y WITH ACUTE" + u:entity="yacute" + string="\('y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="þ" + u:name="LATIN SMALL LETTER THORN" + u:entity="thorn" + string="\(Tp" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <xsl:output-character + character="ÿ" + u:name="LATIN SMALL LETTER Y WITH DIAERESIS" + u:entity="yuml" + string="\(:y" + u:block="C1 Controls And Latin-1 Supplement (Latin-1 Supplement)" + u:class="letters" + /> + <!-- * **************************************************************** --> + <!-- * End: Latin-1/ISO-8859-1 --> + <!-- * **************************************************************** --> + + <!-- * **************************************************************** --> + <!-- * Begin: --> + <!-- * --> + <!-- * - x0100 to x017f (Latin Extended-A) --> + <!-- * - x0180 to x023f (Latin Extended-B) --> + <!-- * - x0250 to x02ad (IPA Extensions) --> + <!-- * - x02b0 to x02ee (Spacing Modifier Letters) --> + <!-- * - x0300 to x036f (Combining Diacritical Marks) --> + <!-- * --> + <!-- * Other than the following exceptions, characters in these --> + <!-- * blocks don't have any roff equivalents --> + <!-- * **************************************************************** --> + + <xsl:output-character + character="ı" + u:name="LATIN SMALL LETTER DOTLESS I" + u:entity="inodot" + string="\(.i" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="IJ" + u:name="LATIN CAPITAL LIGATURE IJ" + u:entity="IJlig" + string="\(IJ" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ij" + u:name="LATIN SMALL LIGATURE IJ" + u:entity="ijlig" + string="\(ij" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="Ł" + u:name="LATIN CAPITAL LETTER L WITH STROKE" + u:entity="Lstrok" + string="\(/L" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ł" + u:name="LATIN SMALL LETTER L WITH STROKE" + u:entity="lstrok" + string="\(/l" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="Œ" + u:name="LATIN CAPITAL LIGATURE OE" + u:entity="OElig" + string="\(OE" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="œ" + u:name="LATIN SMALL LIGATURE OE" + u:entity="oelig" + string="\(oe" + u:block="Latin Extended-A" + /> + <xsl:output-character + character="ƒ" + u:name="LATIN SMALL LETTER F WITH HOOK" + u:entity="fnof" + string="\(Fn" + u:block="Latin Extended-B" + /> + <xsl:output-character + character="ˆ" + u:name="MODIFIER LETTER CIRCUMFLEX ACCENT" + u:entity="circ" + string="\(a^" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="ˇ" + u:name="CARON" + u:entity="caron" + string="\(ac" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="ˉ" + u:name="MODIFIER LETTER MACRON" + string="\(a-" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="˘" + u:name="BREVE" + u:entity="breve" + string="\(ab" + u:block="Spacing Modifier Letters" + /> +<!-- * there does not seem to by any roff equivalent for "dot above" --> +<!-- * <xsl:output-character --> +<!-- * character="˙" --> +<!-- * u:name="DOT ABOVE" --> +<!-- * u:entity="dot" --> +<!-- * /> --> + <xsl:output-character + character="˚" + u:name="RING ABOVE" + u:entity="ring" + string="\(ao" + u:block="Spacing Modifier Letters" + /> + <xsl:output-character + character="˛" + u:name="OGONEK" + u:entity="ogon" + string="\(ho" + u:block="Spacing Modifier Letters" + /> + <!-- groff_char(7) calls Unicode x02dd a "Hungarian umlaut" --> + <xsl:output-character + character="˝" + u:name="DOUBLE ACUTE ACCENT" + u:entity="dblac" + string='\(a"' + u:block="Spacing Modifier Letters" + /> + + <!-- * **************************************************************** --> + <!-- * End: --> + <!-- * - Latin Extended-A --> + <!-- * - Latin Extended-B --> + <!-- * - IPA Extensions --> + <!-- * - Spacing Modifier Letters --> + <!-- * - Combining Diacritical Marks --> + <!-- * **************************************************************** --> + + <!-- * **************************************************************** --> + <!-- * Begin: Greek and Coptic --> + <!-- * x0370 to x03ff --> + <!-- * **************************************************************** --> + + <xsl:output-character + character="Α" + u:name="GREEK CAPITAL LETTER ALPHA" + u:entity="Agr" + string="\(*A)" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Β" + u:name="GREEK CAPITAL LETTER BETA" + u:entity="Bgr" + string="\(*B" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Γ" + u:name="GREEK CAPITAL LETTER GAMMA" + u:entity="Gamma" + string="\(*G" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Δ" + u:name="GREEK CAPITAL LETTER DELTA" + u:entity="Delta" + string="\(*D" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ε" + u:name="GREEK CAPITAL LETTER EPSILON" + u:entity="Egr" + string="\(*E" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ζ" + u:name="GREEK CAPITAL LETTER ZETA" + u:entity="Zgr" + string="\(*Z" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Η" + u:name="GREEK CAPITAL LETTER ETA" + u:entity="EEgr" + string="\(*Y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Θ" + u:name="GREEK CAPITAL LETTER THETA" + u:entity="THgr" + string="\(*H" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ι" + u:name="GREEK CAPITAL LETTER IOTA" + u:entity="Igr" + string="\(*I" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Κ" + u:name="GREEK CAPITAL LETTER KAPPA" + u:entity="Kgr" + string="\(*K" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Λ" + u:name="GREEK CAPITAL LETTER LAMDA" + u:entity="Lambda" + string="\(*L" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Μ" + u:name="GREEK CAPITAL LETTER MU" + u:entity="Mgr" + string="\(*M" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ν" + u:name="GREEK CAPITAL LETTER NU" + u:entity="Ngr" + string="\(*N" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ξ" + u:name="GREEK CAPITAL LETTER XI" + u:entity="Xgr" + string="\(*C" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ο" + u:name="GREEK CAPITAL LETTER OMICRON" + u:entity="Ogr" + string="\(*O" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Π" + u:name="GREEK CAPITAL LETTER PI" + u:entity="Pgr" + string="\(*P" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ρ" + u:name="GREEK CAPITAL LETTER RHO" + u:entity="Rgr" + string="\(*R" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Σ" + u:name="GREEK CAPITAL LETTER SIGMA" + u:entity="Sgr" + string="\(*S" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Τ" + u:name="GREEK CAPITAL LETTER TAU" + u:entity="Tgr" + string="\(*T" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Υ" + u:name="GREEK CAPITAL LETTER UPSILON" + u:entity="Ugr" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Φ" + u:name="GREEK CAPITAL LETTER PHI" + u:entity="PHgr" + string="\(*F" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Χ" + u:name="GREEK CAPITAL LETTER CHI" + u:entity="KHgr" + string="\(*X" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ψ" + u:name="GREEK CAPITAL LETTER PSI" + u:entity="PSgr" + string="\(*Q" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ω" + u:name="GREEK CAPITAL LETTER OMEGA" + u:entity="OHgr" + string="\(*W" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ϊ" + u:name="GREEK CAPITAL LETTER IOTA WITH DIALYTIKA" + u:entity="Idigr" + string="\(*I" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="Ϋ" + u:name="GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA" + u:entity="Udigr" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ά" + u:name="GREEK SMALL LETTER ALPHA WITH TONOS" + u:entity="aacgr" + string="\(*a" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="έ" + u:name="GREEK SMALL LETTER EPSILON WITH TONOS" + u:entity="eacgr" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ή" + u:name="GREEK SMALL LETTER ETA WITH TONOS" + u:entity="eeacgr" + string="\(*y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ί" + u:name="GREEK SMALL LETTER IOTA WITH TONOS" + u:entity="iacgr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ΰ" + u:name="GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS" + u:entity="udiagr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="α" + u:name="GREEK SMALL LETTER ALPHA" + u:entity="agr" + string="\(*a" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="β" + u:name="GREEK SMALL LETTER BETA" + u:entity="beta" + string="\(*b" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="γ" + u:name="GREEK SMALL LETTER GAMMA" + u:entity="gamma" + string="\(*g" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="δ" + u:name="GREEK SMALL LETTER DELTA" + u:entity="delta" + string="\(*d" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ε" + u:name="GREEK SMALL LETTER EPSILON" + u:entity="epsi" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ζ" + u:name="GREEK SMALL LETTER ZETA" + u:entity="zeta" + string="\(*z" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="η" + u:name="GREEK SMALL LETTER ETA" + u:entity="eegr" + string="\(*y" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="θ" + u:name="GREEK SMALL LETTER THETA" + u:entity="thetas" + string="\(*h" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ι" + u:name="GREEK SMALL LETTER IOTA" + u:entity="igr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="κ" + u:name="GREEK SMALL LETTER KAPPA" + u:entity="kappa" + string="\(*k" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="λ" + u:name="GREEK SMALL LETTER LAMDA" + u:entity="lambda" + string="\(*l" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="μ" + u:name="GREEK SMALL LETTER MU" + u:entity="mgr" + string="\(*m" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ν" + u:name="GREEK SMALL LETTER NU" + u:entity="ngr" + string="\(*n" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ξ" + u:name="GREEK SMALL LETTER XI" + u:entity="xgr" + string="\(*c" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ο" + u:name="GREEK SMALL LETTER OMICRON" + u:entity="ogr" + string="\(*o" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="π" + u:name="GREEK SMALL LETTER PI" + u:entity="pgr" + string="\(*p" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ρ" + u:name="GREEK SMALL LETTER RHO" + u:entity="rgr" + string="\(*r" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ς" + u:name="GREEK SMALL LETTER FINAL SIGMA" + u:entity="sfgr" + string="\(ts" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="σ" + u:name="GREEK SMALL LETTER SIGMA" + u:entity="sgr" + string="\(*s" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="τ" + u:name="GREEK SMALL LETTER TAU" + u:entity="tau" + string="\(*t" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="υ" + u:name="GREEK SMALL LETTER UPSILON" + u:entity="ugr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="φ" + u:name="GREEK SMALL LETTER PHI" + u:entity="phgr" + string="\(*f" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="χ" + u:name="GREEK SMALL LETTER CHI" + u:entity="chi" + string="\(*x" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ψ" + u:name="GREEK SMALL LETTER PSI" + u:entity="psgr" + string="\(*q" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ω" + u:name="GREEK SMALL LETTER OMEGA" + u:entity="ohgr" + string="\(*w" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϊ" + u:name="GREEK SMALL LETTER IOTA WITH DIALYTIKA" + u:entity="idigr" + string="\(*i" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϋ" + u:name="GREEK SMALL LETTER UPSILON WITH DIALYTIKA" + u:entity="udigr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ό" + u:name="GREEK SMALL LETTER OMICRON WITH TONOS" + u:entity="oacgr" + string="\(*o" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ύ" + u:name="GREEK SMALL LETTER UPSILON WITH TONOS" + u:entity="uacgr" + string="\(*u" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ώ" + u:name="GREEK SMALL LETTER OMEGA WITH TONOS" + u:entity="ohacgr" + string="\(*w" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϐ" + u:name="GREEK BETA SYMBOL" + string="\(*B" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϑ" + u:name="GREEK THETA SYMBOL" + u:entity="thetav" + string="\(+h" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϒ" + u:name="GREEK UPSILON WITH HOOK SYMBOL" + u:entity="Upsi" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϓ" + u:name="GREEK UPSILON WITH ACUTE AND HOOK SYMBOL" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϔ" + u:name="GREEK UPSILON WITH DIAERESIS AND HOOK SYMBOL" + string="\(*U" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϕ" + u:name="GREEK PHI SYMBOL" + u:entity="phis" + string="\(+f" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϖ" + u:name="GREEK PI SYMBOL" + u:entity="piv" + string="\(+p" + u:block="Greek and Coptic" + /> + <!-- no mappings for remaining chars x03d7 to x03ef --> + <xsl:output-character + character="ϰ" + u:name="GREEK KAPPA SYMBOL" + u:entity="kappav" + string="(*k" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϱ" + u:name="GREEK RHO SYMBOL" + u:entity="rhov" + string="\(*r" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϲ" + u:name="GREEK LUNATE SIGMA SYMBOL" + string="\(*s" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϴ" + u:name="GREEK CAPITAL THETA SYMBOL" + string="\(*H" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="ϵ" + u:name="GREEK LUNATE EPSILON SYMBOL" + string="\(*e" + u:block="Greek and Coptic" + /> + <xsl:output-character + character="϶" + u:name="GREEK REVERSED LUNATE EPSILON SYMBOL" + u:entity="bepsi" + string="\(*e" + u:block="Greek and Coptic" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Greek and Coptic --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Cyrillic --> + <!-- * x0400 to x04ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: General Punctuation --> + <!-- * x2000 to x206f --> + <!-- * ***************************************************************** --> + + <!-- * first, spaces of various widths --> + + <!-- * Note: There does not seem to be either a real em space or en space --> + <!-- * in roff; to approximate them, this character map assumes that in --> + <!-- * most fonts, an en space is about the same as the width of a digit --> + <!-- * (in roff, "\0"), so an em space (which by definition is --> + <!-- * equal to the width of two en spaces) is about the same as the width --> + <!-- * of two digits (thus, in roff, "\0\0") --> + + <xsl:output-character + character=" " + u:name="EN QUAD" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EM QUAD" + string="\0\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EN SPACE" + u:entity="ensp" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character=" " + u:name="EM SPACE" + u:entity="emsp" + string="\0\0" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="THREE-PER-EM SPACE" + u:entity="emsp13" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="FOUR-PER-EM SPACE" + u:entity="emsp14" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * roughly same width as a normal space --> + <xsl:output-character + character=" " + u:name="SIX-PER-EM SPACE" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * same as roff "digit" space --> + <xsl:output-character + character=" " + u:name="FIGURE SPACE" + u:entity="numsp" + string="\0" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * punctuation space in most fonts is actually closer to a normal --> + <!-- * space than it is to a thin space --> + <xsl:output-character + character=" " + u:name="PUNCTUATION SPACE" + u:entity="puncsp" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * Note: Not sure how best to deal with thin space, because the roff --> + <!-- * thin space, "\^", prints as a zero-width space in TTY --> + <!-- * output. However, it seems that, unlike a hair space, a thin space, --> + <!-- * at 1/12 of an em, is still recognizable to most people as a space, --> + <!-- * so treating it as zero-width seems wrong. So, for the sake of making --> + <!-- * TTY output look OK, just substitute with a normal space; but real --> + <!-- * roff escape is "\(\^" --> + <xsl:output-character + character=" " + u:name="THIN SPACE" + u:entity="thinsp" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * I don't think there's a standard definition of what a hair --> + <!-- * space is; some guides just say it's "less than 1/5 of an em" or --> + <!-- * that it's "narrower than a thin space"; seems like in practice, --> + <!-- * it's *a lot* narrower than a thin space, to the point where --> + <!-- * it's close to being a non-space, so here it's substituted with --> + <!-- * roff equivalent of a zero-width no-break space --> + <xsl:output-character + character=" " + u:name="HAIR SPACE" + u:entity="hairsp" + string="\&" + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * map to roff "zero-width break point" --> + <xsl:output-character + character="​" + u:name="ZERO WIDTH SPACE" + string="\:" + u:block="General Punctuation" + u:class="spaces" + /> + + <!-- * x200c and x200d have special purposes in some Indic languages (I --> + <!-- * think); for the "correct" zero-width space, according to Unicode docs, --> + <!-- * use x2060, not x200c or x200d --> + <!-- * <xsl:output-character --> + <!-- * character="‌" --> + <!-- * u:name="ZERO WIDTH NON-JOINER" --> + <!-- * string="\:" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‍" --> + <!-- * u:name="ZERO WIDTH JOINER" --> + <!-- * string="\&" --> + <!-- * /> --> + <!-- * non-visible --> + <!-- * <xsl:output-character --> + <!-- * character="‎" --> + <!-- * u:name="LEFT-TO-RIGHT MARK" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‏" --> + <!-- * u:name="RIGHT-TO-LEFT MARK" --> + <!-- * /> --> + + <!-- * .................................................... --> + <!-- * next, hyphens and various dashes, bars, underscores --> + <xsl:output-character + character="‐" + u:name="HYPHEN" + u:entity="hyphen" + string="\(hy" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * although the groff docs do not make it clear, testing --> + <!-- * indicates that the only reliable way to make a non-breaking --> + <!-- * hyphen is to put just a backslash in front of it. --> + <!-- * --> + <!-- * based on testing, it also appears that no character is needed --> + <!-- * after the hyphen in order to make it non-breaking --> + <xsl:output-character + character="‑" + u:name="NON-BREAKING HYPHEN" + string="\-" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * roughly same width as en dash --> + <xsl:output-character + character="‒" + u:name="FIGURE DASH" + string="\(en" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="–" + u:name="EN DASH" + u:entity="ndash" + string="\(en" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="—" + u:name="EM DASH" + u:entity="mdash" + string="\(em" + u:block="General Punctuation" + u:class="dashes" + /> + <!-- * seems roughly same width as em dash --> + <xsl:output-character + character="―" + u:name="HORIZONTAL BAR" + u:entity="horbar" + string="\(em" + u:block="General Punctuation" + u:class="dashes" + /> + <xsl:output-character + character="‖" + u:name="DOUBLE VERTICAL LINE" + u:entity="Verbar" + string="\(bv\(bv" + u:block="General Punctuation" + /> + <!-- * no double-underscore in roff; so just make it a single --> + <!-- * underscore --> + <xsl:output-character + character="‗" + u:name="DOUBLE LOW LINE" + string="_" + u:block="General Punctuation" + /> + + <!-- * .................................................... --> + <!-- * various quotation marks --> + <xsl:output-character + character="‘" + u:name="LEFT SINGLE QUOTATION MARK" + u:entity="lsquo" + string="\(oq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="’" + u:name="RIGHT SINGLE QUOTATION MARK" + u:entity="rsquo" + string="\(cq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="‚" + u:name="SINGLE LOW-9 QUOTATION MARK" + u:entity="lsquor" + string="\(bq" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * no roff equiv; treat same as lsquo --> + <xsl:output-character + character="‛" + u:name="SINGLE HIGH-REVERSED-9 QUOTATION MARK" + string="\(oq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="“" + u:name="LEFT DOUBLE QUOTATION MARK" + u:entity="ldquo" + string="\(lq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="”" + u:name="RIGHT DOUBLE QUOTATION MARK" + u:entity="rdquo" + string="\(rq" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="„" + u:name="DOUBLE LOW-9 QUOTATION MARK" + u:entity="ldquor" + string="\(Bq" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * no roff equiv; treat same as rdquo --> + <xsl:output-character + character="‟" + u:name="DOUBLE HIGH-REVERSED-9 QUOTATION MARK" + string="\(rq" + u:block="General Punctuation" + u:class="quotes" + /> + + <!-- * .................................................... --> + <!-- * various symbols --> + <xsl:output-character + character="†" + u:name="DAGGER" + u:entity="dagger" + string="\(dg" + u:block="General Punctuation_daggers" + /> + <xsl:output-character + character="‡" + u:name="DOUBLE DAGGER" + u:entity="Dagger" + string="\(dd" + u:block="General Punctuation_daggers" + /> + <xsl:output-character + character="•" + u:name="BULLET" + u:entity="bull" + string="\(bu" + u:block="General Punctuation" + u:class="bullets" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="‣" + u:name="TRIANGULAR BULLET" + string=">\&" + u:block="General Punctuation" + u:class="bullets" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="․" + u:name="ONE DOT LEADER" + string="\&." + u:block="General Punctuation_leaders" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="‥" + u:name="TWO DOT LEADER" + u:entity="nldr" + string="\&.." + u:block="General Punctuation_leaders" + /> + <!-- * no roff equiv --> + <xsl:output-character + character="…" + u:name="HORIZONTAL ELLIPSIS" + u:entity="hellip" + string="\&..." + u:block="General Punctuation" + /> + <!-- what is "hyphenation point" used for? looks like middot to me... --> + <xsl:output-character + character="‧" + u:name="HYPHENATION POINT" + string="\(md" + u:block="General Punctuation" + /> + <!-- * Begin x2028 to x202e - no idea what to do with these --> + <!-- * <xsl:output-character --> + <!-- * character="
" --> + <!-- * u:name="LINE SEPARATOR" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="
" --> + <!-- * u:name="PARAGRAPH SEPARATOR" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‪" --> + <!-- * u:name="LEFT-TO-RIGHT EMBEDDING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‫" --> + <!-- * u:name="RIGHT-TO-LEFT EMBEDDING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‬" --> + <!-- * u:name="POP DIRECTIONAL FORMATTING" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‭" --> + <!-- * u:name="LEFT-TO-RIGHT OVERRIDE" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‮" --> + <!-- * u:name="RIGHT-TO-LEFT OVERRIDE" --> + <!-- * /> --> + <!-- * End x2028 to x202e - no idea what to do with these --> + + <!-- * seems like "narrow" nbsp is basically the same as a no-break --> + <!-- * space --> + <xsl:output-character + character=" " + u:name="NARROW NO-BREAK SPACE" + string="\ " + u:block="General Punctuation" + u:class="spaces" + /> + <xsl:output-character + character="‰" + u:name="PER MILLE SIGN" + u:entity="permil" + string="\(%0" + u:block="General Punctuation" + /> + <!-- * no roff equiv; no idea what to do with it --> + <!-- * <xsl:output-character --> + <!-- * character="‱" --> + <!-- * u:name="PER TEN THOUSAND SIGN" --> + <!-- * /> --> + <xsl:output-character + character="′" + u:name="PRIME" + u:entity="prime" + string="\(fm" + u:block="General Punctuation" + u:class="primes" + /> + <xsl:output-character + character="″" + u:name="DOUBLE PRIME" + u:entity="Prime" + string="\(sd" + u:block="General Punctuation" + u:class="primes" + /> + <xsl:output-character + character="‴" + u:name="TRIPLE PRIME" + u:entity="tprime" + string="\(sd\(fm" + u:block="General Punctuation" + u:class="primes" + /> + <!-- * no idea for these --> + <!-- * <xsl:output-character --> + <!-- * character="‵" --> + <!-- * u:name="REVERSED PRIME" --> + <!-- * u:entity="bprime" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‶" --> + <!-- * u:name="REVERSED DOUBLE PRIME" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="‷" --> + <!-- * u:name="REVERSED TRIPLE PRIME" --> + <!-- * /> --> + + <!-- * there is no low caret in roff --> + <xsl:output-character + character="‸" + u:name="CARET" + string="^" + u:block="General Punctuation" + /> + <xsl:output-character + character="‹" + u:name="SINGLE LEFT-POINTING ANGLE QUOTATION MARK" + string="\(fo" + u:block="General Punctuation" + u:class="quotes" + /> + <xsl:output-character + character="›" + u:name="SINGLE RIGHT-POINTING ANGLE QUOTATION MARK" + string="\(fc" + u:block="General Punctuation" + u:class="quotes" + /> + <!-- * not in roff --> + <xsl:output-character + character="※" + u:name="REFERENCE MARK" + string="*" + u:block="General Punctuation" + /> + <xsl:output-character + character="‼" + u:name="DOUBLE EXCLAMATION MARK" + string="!!" + u:block="General Punctuation" + /> + <xsl:output-character + character="‽" + u:name="INTERROBANG" + string="?!" + u:block="General Punctuation" + /> + <xsl:output-character + character="‾" + u:name="OVERLINE" + string="\(rn" + u:block="General Punctuation" + /> + <xsl:output-character + character="‿" + u:name="UNDERTIE" + string="\(ul" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁀" + u:name="CHARACTER TIE" + string="\(rn" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁁" + u:name="CARET INSERTION POINT" + u:entity="caret" + string="^" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁂" + u:name="ASTERISM" + string="*" + u:block="General Punctuation" + /> + <!-- * not in roff; just make bold hyphen --> + <xsl:output-character + character="⁃" + u:name="HYPHEN BULLET" + u:entity="hybull" + string="\fB-\fR" + u:block="General Punctuation" + u:class="bullets" + /> + <xsl:output-character + character="⁄" + u:name="FRACTION SLASH" + string="\(sl" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁅" + u:name="LEFT SQUARE BRACKET WITH QUILL" + string="[" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁆" + u:name="RIGHT SQUARE BRACKET WITH QUILL" + string="]" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁇" + u:name="DOUBLE QUESTION MARK" + string="??" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁈" + u:name="QUESTION EXCLAMATION MARK" + string="?!" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁉" + u:name="EXCLAMATION QUESTION MARK" + string="!?" + u:block="General Punctuation" + /> + <!-- * not in roff --> + <xsl:output-character + character="⁊" + u:name="TIRONIAN SIGN ET" + string="7" + u:block="General Punctuation" + /> + <!-- * not in roff; just replace with un-reversed pilcrow --> + <xsl:output-character + character="⁋" + u:name="REVERSED PILCROW SIGN" + string="\(ps" + u:block="General Punctuation" + /> + <!-- * not in roff; just make regular bullet --> + <xsl:output-character + character="⁌" + u:name="BLACK LEFTWARDS BULLET" + string="\(bu" + u:block="General Punctuation" + /> + <!-- * not in roff; just make regular bullet --> + <xsl:output-character + character="⁍" + u:name="BLACK RIGHTWARDS BULLET" + string="\(bu" + u:block="General Punctuation" + /> + <xsl:output-character + character="⁎" + u:name="LOW ASTERISK" + string="*" + u:block="General Punctuation" + /> + + <!-- * ............................................................... --> + <!-- * Remaining General Punctuation --> + <!-- * from x2050 to x206f --> + <!-- * only map a couple of these --> + <!-- * ............................................................... --> + + <!-- * basically same as a normal space --> + <xsl:output-character + character=" " + u:name="MEDIUM MATHEMATICAL SPACE" + string=" " + u:block="General Punctuation" + u:class="spaces" + /> + <!-- * Regarding x2060 vs. xFEFF, the document "Unicode Standard Annex #14, --> + <!-- * Line Breaking Properties"[1] says: --> + <!-- * --> + <!-- * The word joiner character [x2060 a.k.a "WJ"] is the preferred --> + <!-- * choice for an invisible character to keep other characters --> + <!-- * together that would otherwise be split across the line at a direct --> + <!-- * break. The character FEFF has the same effect, but because it is --> + <!-- * also used in an unrelated way as a byte order mark, the use of the --> + <!-- * WJ as the preferred interword glue simplifies the handling of FEFF. --> + <!-- * --> + <!-- * [1] http://www.unicode.org/reports/tr14/ --> + <!-- * --> + <!-- * The groff docs seem ambiguous about whether \& is a joiner and --> + <!-- * prevents breaks, but, based on testing, seems like it does --> + <xsl:output-character + character="⁠" + u:name="WORD JOINER" + string="\&" + u:block="General Punctuation" + /> + + <!-- * ***************************************************************** --> + <!-- * End: General Punctuation --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Superscripts and Subscripts --> + <!-- * x2070 to x209f --> + <!-- * For superscripts, just do a^n thing --> + <!-- * For subscripts, just do a_n --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="⁰" + u:name="SUPERSCRIPT ZERO" + string="^0" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="ⁱ" + u:name="SUPERSCRIPT LATIN SMALL LETTER I" + string="^i" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁴" + u:name="SUPERSCRIPT FOUR" + string="^4" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁵" + u:name="SUPERSCRIPT FIVE" + string="^5" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁶" + u:name="SUPERSCRIPT SIX" + string="^6" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁷" + u:name="SUPERSCRIPT SEVEN" + string="^7" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁸" + u:name="SUPERSCRIPT EIGHT" + string="^8" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁹" + u:name="SUPERSCRIPT NINE" + string="^9" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁺" + u:name="SUPERSCRIPT PLUS SIGN" + string="^+" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁻" + u:name="SUPERSCRIPT MINUS" + string="^-" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁼" + u:name="SUPERSCRIPT EQUALS SIGN" + string="^=" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁽" + u:name="SUPERSCRIPT LEFT PARENTHESIS" + string="^(" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="⁾" + u:name="SUPERSCRIPT RIGHT PARENTHESIS" + string="^)" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="ⁿ" + u:name="SUPERSCRIPT LATIN SMALL LETTER N" + string="^n" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₀" + u:name="SUBSCRIPT ZERO" + string="_0" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₁" + u:name="SUBSCRIPT ONE" + string="_1" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₂" + u:name="SUBSCRIPT TWO" + string="_2" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₃" + u:name="SUBSCRIPT THREE" + string="_3" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₄" + u:name="SUBSCRIPT FOUR" + string="_4" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₅" + u:name="SUBSCRIPT FIVE" + string="_5" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₆" + u:name="SUBSCRIPT SIX" + string="_6" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₇" + u:name="SUBSCRIPT SEVEN" + string="_7" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₈" + u:name="SUBSCRIPT EIGHT" + string="_8" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₉" + u:name="SUBSCRIPT NINE" + string="_9" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₊" + u:name="SUBSCRIPT PLUS SIGN" + string="_+" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₋" + u:name="SUBSCRIPT MINUS" + string="_-" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₌" + u:name="SUBSCRIPT EQUALS SIGN" + string="_=" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₍" + u:name="SUBSCRIPT LEFT PARENTHESIS" + string="_(" + u:block="Superscripts and Subscripts" + /> + <xsl:output-character + character="₎" + u:name="SUBSCRIPT RIGHT PARENTHESIS" + string="_)" + u:block="Superscripts and Subscripts" + /> + <!-- * ***************************************************************** --> + <!-- * End: Superscripts and Subscripts --> + <!-- * x2070 to x209f --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Currency Symbols --> + <!-- * x20a0 to x20b1 --> + <!-- * No mappings for any of these; just spell out --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="₠" + u:name="EURO-CURRENCY SIGN" + string="EUR" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₡" + u:name="COLON SIGN" + string="COLON" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₢" + u:name="CRUZEIRO SIGN" + string="CRUZEIRO" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₣" + u:name="FRENCH FRANC SIGN" + string="FRANC" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₤" + u:name="LIRA SIGN" + string="LIRA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₥" + u:name="MILL SIGN" + string="MILL" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₦" + u:name="NAIRA SIGN" + string="NAIRA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₧" + u:name="PESETA SIGN" + string="PESETA" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₨" + u:name="RUPEE SIGN" + string="RUPEE" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₩" + u:name="WON SIGN" + string="WON" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₪" + u:name="NEW SHEQEL SIGN" + string="SHEQEL" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₫" + u:name="DONG SIGN" + string="DONG" + u:block="Currency Symbols" + /> + <xsl:output-character + character="€" + u:name="EURO SIGN" + string="EUR" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₭" + u:name="KIP SIGN" + string="KIP" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₮" + u:name="TUGRIK SIGN" + string="TUGRIK" + u:block="Currency Symbols" + /> + <xsl:output-character + character="₯" + u:name="DRACHMA SIGN" + string="DRACHMA" + u:block="Currency Symbols" + /> + <!-- <xsl:output-character --> + <!-- character="₰" --> + <!-- u:name="GERMAN PENNY SIGN" --> + <!-- string="?" --> + <!-- u:block="Currency Symbols" --> + <!-- /> --> + <xsl:output-character + character="₱" + u:name="PESO SIGN" + string="PESO" + u:block="Currency Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Currency Symbols --> + <!-- * x20a0 to x20b1 --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Combining Diacritical Marks for Symbols --> + <!-- * x20d0 to x20ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Letterlike Symbols --> + <!-- * x2100 to x214b --> + <!-- * --> + <!-- * No mappings for any of these, and nothing appropriate for --> + <!-- * most of them; so, just spell out the ones that we can --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="℃" + u:name="DEGREE CELSIUS" + string="\(deC" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℅" + u:name="CARE OF" + u:entity="incare" + string="c/o" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℉" + u:name="DEGREE FAHRENHEIT" + string="\(deF" + u:block="Letterlike Symbols" + /> + <!-- roff Ifraktur --> + <xsl:output-character + character="ℑ" + u:name="BLACK-LETTER CAPITAL I" + string="\(Im" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℓ" + u:name="SCRIPT SMALL L" + u:entity="ell" + string="l" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="№" + u:name="NUMERO SIGN" + u:entity="numero" + string="No." + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℘" + u:name="SCRIPT CAPITAL P" + u:entity="weierp" + string="\(wp" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℜ" + u:name="BLACK-LETTER CAPITAL R" + u:entity="real" + string="\(Re" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℞" + u:name="PRESCRIPTION TAKE" + u:entity="rx" + string="Rx" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="℠" + u:name="SERVICE MARK" + string="(SM)" + u:block="Letterlike Symbols" + /> + <!-- * We don't do "\(tm" for ™ because for console output, groff --> + <!-- * just renders that as "tm", without any preceding space, parens, --> + <!-- * or anything. So it just gets run into the preceding word; i.e.: --> + <!-- * --> + <!-- * Product™ -> Producttm --> + <!-- * --> + <!-- * That it probably not what most people would want. So we just --> + <!-- * render it as (TM) instead, Thus: --> + <!-- * --> + <!-- * Product™ -> Product(TM) --> + <xsl:output-character + character="™" + u:name="TRADE MARK SIGN" + u:entity="trade" + string="(TM)" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="Ω" + u:name="OHM SIGN" + u:entity="ohm" + string="\(*W" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="K" + u:name="KELVIN SIGN" + string="K" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="Å" + u:name="ANGSTROM SIGN" + u:entity="angst" + string="\(oA" + u:block="Letterlike Symbols" + /> + <xsl:output-character + character="ℵ" + u:name="ALEF SYMBOL" + u:entity="aleph" + string="\(Ah" + u:block="Letterlike Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Letterlike Symbols --> + <!-- * x2100 to x214b --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Number Forms --> + <!-- * x2150 to x218f --> + <!-- * --> + <!-- * No mappings for any of these, and nothing appropriate for most --> + <!-- * of them; so, just spell out the ones that we can --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="⅓" + u:name="VULGAR FRACTION ONE THIRD" + u:entity="frac13" + string="1/3" + u:block="Number Forms" + /> + <xsl:output-character + character="⅔" + u:name="VULGAR FRACTION TWO THIRDS" + u:entity="frac23" + string="2/3" + u:block="Number Forms" + /> + <xsl:output-character + character="⅕" + u:name="VULGAR FRACTION ONE FIFTH" + u:entity="frac15" + string="1/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅖" + u:name="VULGAR FRACTION TWO FIFTHS" + u:entity="frac25" + string="2/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅗" + u:name="VULGAR FRACTION THREE FIFTHS" + u:entity="frac35" + string="3/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅘" + u:name="VULGAR FRACTION FOUR FIFTHS" + u:entity="frac45" + string="4/5" + u:block="Number Forms" + /> + <xsl:output-character + character="⅙" + u:name="VULGAR FRACTION ONE SIXTH" + u:entity="frac16" + string="1/6" + u:block="Number Forms" + /> + <xsl:output-character + character="⅚" + u:name="VULGAR FRACTION FIVE SIXTHS" + u:entity="frac56" + string="5/6" + u:block="Number Forms" + /> + <xsl:output-character + character="⅛" + u:name="VULGAR FRACTION ONE EIGHTH" + u:entity="frac18" + string="1/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅜" + u:name="VULGAR FRACTION THREE EIGHTHS" + u:entity="frac38" + string="3/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅝" + u:name="VULGAR FRACTION FIVE EIGHTHS" + u:entity="frac58" + string="5/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅞" + u:name="VULGAR FRACTION SEVEN EIGHTHS" + u:entity="frac78" + string="7/8" + u:block="Number Forms" + /> + <xsl:output-character + character="⅟" + u:name="FRACTION NUMERATOR ONE" + string="1/" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅰ" + u:name="ROMAN NUMERAL ONE" + string="I" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅱ" + u:name="ROMAN NUMERAL TWO" + string="II" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅲ" + u:name="ROMAN NUMERAL THREE" + string="III" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅳ" + u:name="ROMAN NUMERAL FOUR" + string="IV" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅴ" + u:name="ROMAN NUMERAL FIVE" + string="V" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅵ" + u:name="ROMAN NUMERAL SIX" + string="VI" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅶ" + u:name="ROMAN NUMERAL SEVEN" + string="VII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅷ" + u:name="ROMAN NUMERAL EIGHT" + string="VIII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅸ" + u:name="ROMAN NUMERAL NINE" + string="IX" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅹ" + u:name="ROMAN NUMERAL TEN" + string="X" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅺ" + u:name="ROMAN NUMERAL ELEVEN" + string="XI" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅻ" + u:name="ROMAN NUMERAL TWELVE" + string="XII" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅼ" + u:name="ROMAN NUMERAL FIFTY" + string="L" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅽ" + u:name="ROMAN NUMERAL ONE HUNDRED" + string="C" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅾ" + u:name="ROMAN NUMERAL FIVE HUNDRED" + string="D" + u:block="Number Forms" + /> + <xsl:output-character + character="Ⅿ" + u:name="ROMAN NUMERAL ONE THOUSAND" + string="M" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅰ" + u:name="SMALL ROMAN NUMERAL ONE" + string="i" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅱ" + u:name="SMALL ROMAN NUMERAL TWO" + string="ii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅲ" + u:name="SMALL ROMAN NUMERAL THREE" + string="iii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅳ" + u:name="SMALL ROMAN NUMERAL FOUR" + string="iv" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅴ" + u:name="SMALL ROMAN NUMERAL FIVE" + string="v" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅵ" + u:name="SMALL ROMAN NUMERAL SIX" + string="vi" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅶ" + u:name="SMALL ROMAN NUMERAL SEVEN" + string="vii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅷ" + u:name="SMALL ROMAN NUMERAL EIGHT" + string="viii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅸ" + u:name="SMALL ROMAN NUMERAL NINE" + string="ix" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅹ" + u:name="SMALL ROMAN NUMERAL TEN" + string="x" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅺ" + u:name="SMALL ROMAN NUMERAL ELEVEN" + string="xi" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅻ" + u:name="SMALL ROMAN NUMERAL TWELVE" + string="xii" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅼ" + u:name="SMALL ROMAN NUMERAL FIFTY" + string="l" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅽ" + u:name="SMALL ROMAN NUMERAL ONE HUNDRED" + string="c" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅾ" + u:name="SMALL ROMAN NUMERAL FIVE HUNDRED" + string="d" + u:block="Number Forms" + /> + <xsl:output-character + character="ⅿ" + u:name="SMALL ROMAN NUMERAL ONE THOUSAND" + string="m" + u:block="Number Forms" + /> + <xsl:output-character + character="ↀ" + u:name="ROMAN NUMERAL ONE THOUSAND C D" + string="CD" + u:block="Number Forms" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Number Forms --> + <!-- * x2150 to x218f --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Arrows --> + <!-- * x2190 to x21ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="←" + u:name="LEFTWARDS ARROW" + u:entity="larr" + string="\(<-" + u:block="Arrows" + /> + <xsl:output-character + character="↑" + u:name="UPWARDS ARROW" + u:entity="uarr" + string="\(ua" + u:block="Arrows" + /> + <xsl:output-character + character="→" + u:name="RIGHTWARDS ARROW" + u:entity="rarr" + string="\(->" + u:block="Arrows" + /> + <xsl:output-character + character="↓" + u:name="DOWNWARDS ARROW" + u:entity="darr" + string="\(da" + u:block="Arrows" + /> + <xsl:output-character + character="↔" + u:name="LEFT RIGHT ARROW" + u:entity="harr" + string="\(<>" + u:block="Arrows" + /> + <xsl:output-character + character="↕" + u:name="UP DOWN ARROW" + u:entity="varr" + string="\(va" + u:block="Arrows" + /> + <xsl:output-character + character="↵" + u:name="DOWNWARDS ARROW WITH CORNER LEFTWARDS" + u:entity="crarr" + string="\(CR" + u:block="Arrows" + /> + <xsl:output-character + character="⇐" + u:name="LEFTWARDS DOUBLE ARROW" + u:entity="lArr" + string="\(la" + u:block="Arrows" + /> + <xsl:output-character + character="⇑" + u:name="UPWARDS DOUBLE ARROW" + u:entity="uArr" + string="\(uA" + u:block="Arrows" + /> + <xsl:output-character + character="⇒" + u:name="RIGHTWARDS DOUBLE ARROW" + u:entity="rArr" + string="\(rA" + u:block="Arrows" + /> + <xsl:output-character + character="⇓" + u:name="DOWNWARDS DOUBLE ARROW" + u:entity="dArr" + string="\(dA" + u:block="Arrows" + /> + <xsl:output-character + character="⇔" + u:name="LEFT RIGHT DOUBLE ARROW" + u:entity="hArr" + string="\(hA" + u:block="Arrows" + /> + <!-- no roff equiv; render same as single arrow --> + <xsl:output-character + character="⇕" + u:name="UP DOWN DOUBLE ARROW" + u:entity="vArr" + string="\(va" + u:block="Arrows" + /> + + <!-- * ***************************************************************** --> + <!-- * Begin: Mathematical Operators --> + <!-- * x2200 to x22ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="∀" + u:name="FOR ALL" + u:entity="forall" + string="\(fa" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∁" + u:name="COMPLEMENT" + u:entity="comp" + string="C" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∂" + u:name="PARTIAL DIFFERENTIAL" + u:entity="part" + string="\(pd" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∃" + u:name="THERE EXISTS" + u:entity="exist" + string="\(te" + u:block="Mathematical Operators" + /> + <!-- * no roff equiv --> + <!-- * <xsl:output-character --> + <!-- * character="∄" --> + <!-- * u:name="THERE DOES NOT EXIST" --> + <!-- * u:entity="nexist" --> + <!-- * /> --> + <xsl:output-character + character="∅" + u:name="EMPTY SET" + u:entity="empty" + string="\(es" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∆" + u:name="INCREMENT" + string="\(*D" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∇" + u:name="NABLA" + u:entity="nabla" + string="\(gr" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∈" + u:name="ELEMENT OF" + u:entity="isin" + string="\(mo" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∉" + u:name="NOT AN ELEMENT OF" + u:entity="notin" + string="\(nm" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∊" + u:name="SMALL ELEMENT OF" + string="\(mo" + u:block="Mathematical Operators" + /> + + <xsl:output-character + character="∋" + u:name="CONTAINS AS MEMBER" + u:entity="ni" + string="\(st" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∌" --> + <!-- * u:name="DOES NOT CONTAIN AS MEMBER" --> + <!-- * /> --> + <xsl:output-character + character="∍" + u:name="SMALL CONTAINS AS MEMBER" + string="\(st" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∎" --> + <!-- * u:name="END OF PROOF" --> + <!-- * /> --> + <xsl:output-character + character="∏" + u:name="N-ARY PRODUCT" + u:entity="prod" + string="\(product" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∐" --> + <!-- * u:name="N-ARY COPRODUCT" --> + <!-- * u:entity="coprod" --> + <!-- * /> --> + <xsl:output-character + character="∑" + u:name="N-ARY SUMMATION" + u:entity="sum" + string="\(sum" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="−" + u:name="MINUS SIGN" + u:entity="minus" + string="\-" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∓" + u:name="MINUS-OR-PLUS SIGN" + u:entity="mnplus" + string="\(+-" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∔" --> + <!-- * u:name="DOT PLUS" --> + <!-- * u:entity="plusdo" --> + <!-- * /> --> + <xsl:output-character + character="∕" + u:name="DIVISION SLASH" + string="\(f/" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∖" + u:name="SET MINUS" + u:entity="setmn" + string="\e" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∗" + u:name="ASTERISK OPERATOR" + u:entity="lowast" + string="\(**" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∘" --> + <!-- * u:name="RING OPERATOR" --> + <!-- * u:entity="compfn" --> + <!-- * /> --> + <xsl:output-character + character="∙" + u:name="BULLET OPERATOR" + string="\(bu" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="√" + u:name="SQUARE ROOT" + u:entity="radic" + string="\(sr" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∛" --> + <!-- * u:name="CUBE ROOT" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="∜" --> + <!-- * u:name="FOURTH ROOT" --> + <!-- * /> --> + <xsl:output-character + character="∝" + u:name="PROPORTIONAL TO" + u:entity="prop" + string="\(pt" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∞" + u:name="INFINITY" + u:entity="infin" + string="\(if" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∟" --> + <!-- * u:name="RIGHT ANGLE" --> + <!-- * u:entity="ang90" --> + <!-- * /> --> + <xsl:output-character + character="∠" + u:name="ANGLE" + u:entity="ang" + string="\(/_" + u:block="Mathematical Operators" + /> + + <!-- * 0x2221 to 0x2226 not in roff; --> + <!-- * but fake a parallel sign with vert bars --> + + <xsl:output-character + character="∥" + u:name="PARALLEL TO" + u:entity="par" + string="\(bv\(bv" + u:block="Mathematical Operators" + /> + + <xsl:output-character + character="∧" + u:name="LOGICAL AND" + u:entity="and" + string="\(AN" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∨" + u:name="LOGICAL OR" + u:entity="or" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∩" + u:name="INTERSECTION" + u:entity="cap" + string="\(ca" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∪" + u:name="UNION" + u:entity="cup" + string="\(cu" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∫" + u:name="INTEGRAL" + u:entity="int" + string="\(is" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∬" + u:name="DOUBLE INTEGRAL" + string="\(is\(is" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∭" + u:name="TRIPLE INTEGRAL" + string="\(is\(is\(is" + u:block="Mathematical Operators" + /> + + <!-- * 0x222e to 0x2233 not in roff --> + + <xsl:output-character + character="∴" + u:name="THEREFORE" + u:entity="there4" + string="\(tf" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="∵" --> + <!-- * u:name="BECAUSE" --> + <!-- * u:entity="becaus" --> + <!-- * /> --> + <xsl:output-character + character="∶" + u:name="RATIO" + string=":" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="∷" + u:name="PROPORTION" + string="::" + u:block="Mathematical Operators" + /> + + <!-- * 0x2238 to 0x223b not in roff --> + + <xsl:output-character + character="∼" + u:name="TILDE OPERATOR" + u:entity="sim" + string="\(ti" + u:block="Mathematical Operators" + /> + + <!-- * 0x223d to 0x224b not in roff --> + + <xsl:output-character + character="≅" + u:name="APPROXIMATELY EQUAL TO" + u:entity="cong" + string="\(=~" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="≆" --> + <!-- * u:name="APPROXIMATELY BUT NOT ACTUALLY EQUAL TO" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="≇" --> + <!-- * u:name="NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO" --> + <!-- * u:entity="ncong" --> + <!-- * /> --> + + <xsl:output-character + character="≈" + u:name="ALMOST EQUAL TO" + u:entity="asymp" + string="\(~~" + u:block="Mathematical Operators" + /> + + <!-- * x2249 to x2253 not in roff --> + + <xsl:output-character + character="≔" + u:name="COLON EQUALS" + u:entity="colone" + string=":=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≕" + u:name="EQUALS COLON" + u:entity="ecolon" + string="=:" + u:block="Mathematical Operators" + /> + + <!-- * x2256 to x2255 not in roff --> + + <xsl:output-character + character="≟" + u:name="QUESTIONED EQUAL TO" + string="?=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≠" + u:name="NOT EQUAL TO" + u:entity="ne" + string="\(!=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≡" + u:name="IDENTICAL TO" + u:entity="equiv" + string="\(==" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="≢" --> + <!-- * u:name="NOT IDENTICAL TO" --> + <!-- * u:entity="nequiv" --> + <!-- * /> --> + <!-- * <xsl:output-character --> + <!-- * character="≣" --> + <!-- * u:name="STRICTLY EQUIVALENT TO" --> + <!-- * /> --> + <xsl:output-character + character="≤" + u:name="LESS-THAN OR EQUAL TO" + u:entity="le" + string="\(<=" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≥" + u:name="GREATER-THAN OR EQUAL TO" + u:entity="ge" + string="\(>=" + u:block="Mathematical Operators" + /> + <!-- * x2266 to x2269 not in roff --> + + <xsl:output-character + character="≪" + u:name="MUCH LESS-THAN" + u:entity="Lt" + string="<<" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="≫" + u:name="MUCH GREATER-THAN" + u:entity="Gt" + string=">>" + u:block="Mathematical Operators" + /> + <!-- * x226c to x2281 not in roff --> + + <xsl:output-character + character="⊂" + u:name="SUBSET OF" + u:entity="sub" + string="\(sb" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊃" + u:name="SUPERSET OF" + u:entity="sup" + string="\(sp" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊄" + u:name="NOT A SUBSET OF" + u:entity="nsub" + string="\(nb" + u:block="Mathematical Operators" + /> + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="⊅" --> + <!-- * u:name="NOT A SUPERSET OF" --> + <!-- * u:entity="nsup" --> + <!-- * /> --> + <xsl:output-character + character="⊆" + u:name="SUBSET OF OR EQUAL TO" + u:entity="sube" + string="\(ib" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊇" + u:name="SUPERSET OF OR EQUAL TO" + u:entity="supe" + string="\(ip" + u:block="Mathematical Operators" + /> + <!-- * x2288 to x2294 not in roff --> + + <xsl:output-character + character="⊕" + u:name="CIRCLED PLUS" + u:entity="oplus" + string="\(c+" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊖" + u:name="CIRCLED MINUS" + u:entity="ominus" + string="\(c*" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⊗" + u:name="CIRCLED TIMES" + u:entity="otimes" + string="\(c*" + u:block="Mathematical Operators" + /> + + <!-- * x2298 to x22a4 not in roff --> + + <xsl:output-character + character="⊥" + u:name="UP TACK" + u:entity="bottom" + string="\(pp" + u:block="Mathematical Operators" + /> + + <!-- * x22a6 to x22bf not in roff --> + + <xsl:output-character + character="⋀" + u:name="N-ARY LOGICAL AND" + string="\(AN" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋁" + u:name="N-ARY LOGICAL OR" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋂" + u:name="N-ARY INTERSECTION" + string="\(ca" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋃" + u:name="N-ARY UNION" + string="\(cu" + u:block="Mathematical Operators" + /> + + <!-- * not in roff --> + <!-- * <xsl:output-character --> + <!-- * character="⋄" --> + <!-- * u:name="DIAMOND OPERATOR" --> + <!-- * u:entity="diam" --> + <!-- * /> --> + <xsl:output-character + character="⋅" + u:name="DOT OPERATOR" + u:entity="sdot" + string="\(md" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋆" + u:name="STAR OPERATOR" + u:entity="sstarf" + string="\(**" + u:block="Mathematical Operators" + /> + <!-- * x22c7 to x22cd not in roff --> + + <xsl:output-character + character="⋎" + u:name="CURLY LOGICAL OR" + u:entity="cuvee" + string="\(OR" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋏" + u:name="CURLY LOGICAL AND" + u:entity="cuwed" + string="\(AN" + u:block="Mathematical Operators" + /> + + <!-- * x22d0 to x22d7 not in roff --> + + <xsl:output-character + character="⋘" + u:name="VERY MUCH LESS-THAN" + u:entity="Ll" + string="<<<" + u:block="Mathematical Operators" + /> + <xsl:output-character + character="⋙" + u:name="VERY MUCH GREATER-THAN" + u:entity="Gg" + string=">>>" + u:block="Mathematical Operators" + /> + + <!-- * x22da to x22ee not in roff --> + + <xsl:output-character + character="⋯" + u:name="MIDLINE HORIZONTAL ELLIPSIS" + string="\&..." + u:block="Mathematical Operators" + /> + + <!-- * x22fo to x22ff not in roff --> + + <!-- * ***************************************************************** --> + <!-- * End: Mathematical Operators --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Miscellaneous Technical --> + <!-- * x2300 to x23ff --> + <!-- * - do nothing except for angle brackets - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <xsl:output-character + character="〈" + u:name="LEFT-POINTING ANGLE BRACKET" + u:entity="lang" + string="\(la" + u:block="Miscellaneous Technical" + /> + <xsl:output-character + character="〉" + u:name="RIGHT-POINTING ANGLE BRACKET" + u:entity="rang" + string="\(ra" + u:block="Miscellaneous Technical" + /> + + <!-- * ***************************************************************** --> + <!-- * Begin: Control Pictures --> + <!-- * x2400 to x243f --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="␀" + u:name="SYMBOL FOR NULL" + string="NUL" + u:block="Control Pictures" + /> + <xsl:output-character + character="␁" + u:name="SYMBOL FOR START OF HEADING" + string="SOH" + u:block="Control Pictures" + /> + <xsl:output-character + character="␂" + u:name="SYMBOL FOR START OF TEXT" + string="STX" + u:block="Control Pictures" + /> + <xsl:output-character + character="␃" + u:name="SYMBOL FOR END OF TEXT" + string="ETX" + u:block="Control Pictures" + /> + <xsl:output-character + character="␄" + u:name="SYMBOL FOR END OF TRANSMISSION" + string="EOT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␅" + u:name="SYMBOL FOR ENQUIRY" + string="ENQ" + u:block="Control Pictures" + /> + <xsl:output-character + character="␆" + u:name="SYMBOL FOR ACKNOWLEDGE" + string="ACK" + u:block="Control Pictures" + /> + <xsl:output-character + character="␇" + u:name="SYMBOL FOR BELL" + string="BEL" + u:block="Control Pictures" + /> + <xsl:output-character + character="␈" + u:name="SYMBOL FOR BACKSPACE" + string="BS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␉" + u:name="SYMBOL FOR HORIZONTAL TABULATION" + string="HT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␊" + u:name="SYMBOL FOR LINE FEED" + string="LF" + u:block="Control Pictures" + /> + <xsl:output-character + character="␋" + u:name="SYMBOL FOR VERTICAL TABULATION" + string="VT" + u:block="Control Pictures" + /> + <xsl:output-character + character="␌" + u:name="SYMBOL FOR FORM FEED" + string="FF" + u:block="Control Pictures" + /> + <xsl:output-character + character="␍" + u:name="SYMBOL FOR CARRIAGE RETURN" + string="CR" + u:block="Control Pictures" + /> + <xsl:output-character + character="␎" + u:name="SYMBOL FOR SHIFT OUT" + string="SO" + u:block="Control Pictures" + /> + <xsl:output-character + character="␏" + u:name="SYMBOL FOR SHIFT IN" + string="SI" + u:block="Control Pictures" + /> + <xsl:output-character + character="␐" + u:name="SYMBOL FOR DATA LINK ESCAPE" + string="DLE" + u:block="Control Pictures" + /> + <xsl:output-character + character="␑" + u:name="SYMBOL FOR DEVICE CONTROL ONE" + string="DC1" + u:block="Control Pictures" + /> + <xsl:output-character + character="␒" + u:name="SYMBOL FOR DEVICE CONTROL TWO" + string="DC2" + u:block="Control Pictures" + /> + <xsl:output-character + character="␓" + u:name="SYMBOL FOR DEVICE CONTROL THREE" + string="DC3" + u:block="Control Pictures" + /> + <xsl:output-character + character="␔" + u:name="SYMBOL FOR DEVICE CONTROL FOUR" + string="DC4" + u:block="Control Pictures" + /> + <xsl:output-character + character="␕" + u:name="SYMBOL FOR NEGATIVE ACKNOWLEDGE" + string="NAK" + u:block="Control Pictures" + /> + <xsl:output-character + character="␖" + u:name="SYMBOL FOR SYNCHRONOUS IDLE" + string="SYN" + u:block="Control Pictures" + /> + <xsl:output-character + character="␗" + u:name="SYMBOL FOR END OF TRANSMISSION BLOCK" + string="ETB" + u:block="Control Pictures" + /> + <xsl:output-character + character="␘" + u:name="SYMBOL FOR CANCEL" + string="CAN" + u:block="Control Pictures" + /> + <xsl:output-character + character="␙" + u:name="SYMBOL FOR END OF MEDIUM" + string="EM" + u:block="Control Pictures" + /> + <xsl:output-character + character="␚" + u:name="SYMBOL FOR SUBSTITUTE" + string="SUB" + u:block="Control Pictures" + /> + <xsl:output-character + character="␛" + u:name="SYMBOL FOR ESCAPE" + string="ESC" + u:block="Control Pictures" + /> + <xsl:output-character + character="␜" + u:name="SYMBOL FOR FILE SEPARATOR" + string="FS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␝" + u:name="SYMBOL FOR GROUP SEPARATOR" + string="GS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␞" + u:name="SYMBOL FOR RECORD SEPARATOR" + string="RS" + u:block="Control Pictures" + /> + <xsl:output-character + character="␟" + u:name="SYMBOL FOR UNIT SEPARATOR" + string="US" + u:block="Control Pictures" + /> + <xsl:output-character + character="␠" + u:name="SYMBOL FOR SPACE" + string="SP" + u:block="Control Pictures" + /> + <xsl:output-character + character="␡" + u:name="SYMBOL FOR DELETE" + string="DEL" + u:block="Control Pictures" + /> + <!-- * no roff equivs for x2422 and x2423 --> + <!-- * <xsl:output-character --> + <!-- * character="␢" --> + <!-- * u:name="BLANK SYMBOL" --> + <!-- * string="?" --> + <!-- * u:block="Control Pictures" --> + <!-- * /> --> + <!-- * I think there should be a roff equiv for ␣, but as far as I --> + <!-- * know, there is not... --> + <!-- * <xsl:output-character --> + <!-- * character="␣" --> + <!-- * u:name="OPEN BOX" --> + <!-- * u:entity="blank" --> + <!-- * string="?" --> + <!-- * u:block="Control Pictures" --> + <!-- * /> --> + <xsl:output-character + character="␤" + u:name="SYMBOL FOR NEWLINE" + string="NL" + u:block="Control Pictures" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Control Pictures --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Optical Character Recognition --> + <!-- * x2440 to x24ff --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Enclosed Alphanumerics --> + <!-- * x2460 to x24ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="①" + u:name="CIRCLED DIGIT ONE" + string="1" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="②" + u:name="CIRCLED DIGIT TWO" + string="2" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="③" + u:name="CIRCLED DIGIT THREE" + string="3" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="④" + u:name="CIRCLED DIGIT FOUR" + string="4" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑤" + u:name="CIRCLED DIGIT FIVE" + string="5" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑥" + u:name="CIRCLED DIGIT SIX" + string="6" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑦" + u:name="CIRCLED DIGIT SEVEN" + string="7" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑧" + u:name="CIRCLED DIGIT EIGHT" + string="8" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑨" + u:name="CIRCLED DIGIT NINE" + string="9" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑩" + u:name="CIRCLED NUMBER TEN" + string="10" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑪" + u:name="CIRCLED NUMBER ELEVEN" + string="11" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑫" + u:name="CIRCLED NUMBER TWELVE" + string="12" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑬" + u:name="CIRCLED NUMBER THIRTEEN" + string="13" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑭" + u:name="CIRCLED NUMBER FOURTEEN" + string="14" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑮" + u:name="CIRCLED NUMBER FIFTEEN" + string="15" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑯" + u:name="CIRCLED NUMBER SIXTEEN" + string="16" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑰" + u:name="CIRCLED NUMBER SEVENTEEN" + string="17" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑱" + u:name="CIRCLED NUMBER EIGHTEEN" + string="18" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑲" + u:name="CIRCLED NUMBER NINETEEN" + string="19" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑳" + u:name="CIRCLED NUMBER TWENTY" + string="20" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑴" + u:name="PARENTHESIZED DIGIT ONE" + string="(1)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑵" + u:name="PARENTHESIZED DIGIT TWO" + string="(2)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑶" + u:name="PARENTHESIZED DIGIT THREE" + string="(3)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑷" + u:name="PARENTHESIZED DIGIT FOUR" + string="(4)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑸" + u:name="PARENTHESIZED DIGIT FIVE" + string="(5)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑹" + u:name="PARENTHESIZED DIGIT SIX" + string="(6)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑺" + u:name="PARENTHESIZED DIGIT SEVEN" + string="(7)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑻" + u:name="PARENTHESIZED DIGIT EIGHT" + string="(8)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑼" + u:name="PARENTHESIZED DIGIT NINE" + string="(9)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑽" + u:name="PARENTHESIZED NUMBER TEN" + string="(10)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑾" + u:name="PARENTHESIZED NUMBER ELEVEN" + string="(11)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⑿" + u:name="PARENTHESIZED NUMBER TWELVE" + string="(12)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒀" + u:name="PARENTHESIZED NUMBER THIRTEEN" + string="(13)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒁" + u:name="PARENTHESIZED NUMBER FOURTEEN" + string="(14)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒂" + u:name="PARENTHESIZED NUMBER FIFTEEN" + string="(15)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒃" + u:name="PARENTHESIZED NUMBER SIXTEEN" + string="(16)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒄" + u:name="PARENTHESIZED NUMBER SEVENTEEN" + string="(17)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒅" + u:name="PARENTHESIZED NUMBER EIGHTEEN" + string="(18)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒆" + u:name="PARENTHESIZED NUMBER NINETEEN" + string="(19)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒇" + u:name="PARENTHESIZED NUMBER TWENTY" + string="(20)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒈" + u:name="DIGIT ONE FULL STOP" + string="1." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒉" + u:name="DIGIT TWO FULL STOP" + string="2." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒊" + u:name="DIGIT THREE FULL STOP" + string="3." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒋" + u:name="DIGIT FOUR FULL STOP" + string="4." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒌" + u:name="DIGIT FIVE FULL STOP" + string="5." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒍" + u:name="DIGIT SIX FULL STOP" + string="6." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒎" + u:name="DIGIT SEVEN FULL STOP" + string="7." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒏" + u:name="DIGIT EIGHT FULL STOP" + string="8." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒐" + u:name="DIGIT NINE FULL STOP" + string="9." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒑" + u:name="NUMBER TEN FULL STOP" + string="10." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒒" + u:name="NUMBER ELEVEN FULL STOP" + string="11." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒓" + u:name="NUMBER TWELVE FULL STOP" + string="12." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒔" + u:name="NUMBER THIRTEEN FULL STOP" + string="13." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒕" + u:name="NUMBER FOURTEEN FULL STOP" + string="14." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒖" + u:name="NUMBER FIFTEEN FULL STOP" + string="15." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒗" + u:name="NUMBER SIXTEEN FULL STOP" + string="16." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒘" + u:name="NUMBER SEVENTEEN FULL STOP" + string="17." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒙" + u:name="NUMBER EIGHTEEN FULL STOP" + string="18." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒚" + u:name="NUMBER NINETEEN FULL STOP" + string="19." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒛" + u:name="NUMBER TWENTY FULL STOP" + string="20." + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒜" + u:name="PARENTHESIZED LATIN SMALL LETTER A" + string="(a)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒝" + u:name="PARENTHESIZED LATIN SMALL LETTER B" + string="(b)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒞" + u:name="PARENTHESIZED LATIN SMALL LETTER C" + string="(c)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒟" + u:name="PARENTHESIZED LATIN SMALL LETTER D" + string="(d)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒠" + u:name="PARENTHESIZED LATIN SMALL LETTER E" + string="(e)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒡" + u:name="PARENTHESIZED LATIN SMALL LETTER F" + string="(f)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒢" + u:name="PARENTHESIZED LATIN SMALL LETTER G" + string="(g)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒣" + u:name="PARENTHESIZED LATIN SMALL LETTER H" + string="(h)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒤" + u:name="PARENTHESIZED LATIN SMALL LETTER I" + string="(i)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒥" + u:name="PARENTHESIZED LATIN SMALL LETTER J" + string="(j)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒦" + u:name="PARENTHESIZED LATIN SMALL LETTER K" + string="(k)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒧" + u:name="PARENTHESIZED LATIN SMALL LETTER L" + string="(l)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒨" + u:name="PARENTHESIZED LATIN SMALL LETTER M" + string="(m)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒩" + u:name="PARENTHESIZED LATIN SMALL LETTER N" + string="(n)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒪" + u:name="PARENTHESIZED LATIN SMALL LETTER O" + string="(o)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒫" + u:name="PARENTHESIZED LATIN SMALL LETTER P" + string="(p)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒬" + u:name="PARENTHESIZED LATIN SMALL LETTER Q" + string="(q)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒭" + u:name="PARENTHESIZED LATIN SMALL LETTER R" + string="(r)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒮" + u:name="PARENTHESIZED LATIN SMALL LETTER S" + string="(s)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒯" + u:name="PARENTHESIZED LATIN SMALL LETTER T" + string="(t)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒰" + u:name="PARENTHESIZED LATIN SMALL LETTER U" + string="(u)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒱" + u:name="PARENTHESIZED LATIN SMALL LETTER V" + string="(v)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒲" + u:name="PARENTHESIZED LATIN SMALL LETTER W" + string="(w)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒳" + u:name="PARENTHESIZED LATIN SMALL LETTER X" + string="(x)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒴" + u:name="PARENTHESIZED LATIN SMALL LETTER Y" + string="(y)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⒵" + u:name="PARENTHESIZED LATIN SMALL LETTER Z" + string="(z)" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓐ" + u:name="CIRCLED LATIN CAPITAL LETTER A" + string="A" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓑ" + u:name="CIRCLED LATIN CAPITAL LETTER B" + string="B" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓒ" + u:name="CIRCLED LATIN CAPITAL LETTER C" + string="C" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓓ" + u:name="CIRCLED LATIN CAPITAL LETTER D" + string="D" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓔ" + u:name="CIRCLED LATIN CAPITAL LETTER E" + string="E" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓕ" + u:name="CIRCLED LATIN CAPITAL LETTER F" + string="F" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓖ" + u:name="CIRCLED LATIN CAPITAL LETTER G" + string="G" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓗ" + u:name="CIRCLED LATIN CAPITAL LETTER H" + string="H" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓘ" + u:name="CIRCLED LATIN CAPITAL LETTER I" + string="I" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓙ" + u:name="CIRCLED LATIN CAPITAL LETTER J" + string="J" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓚ" + u:name="CIRCLED LATIN CAPITAL LETTER K" + string="K" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓛ" + u:name="CIRCLED LATIN CAPITAL LETTER L" + string="L" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓜ" + u:name="CIRCLED LATIN CAPITAL LETTER M" + string="M" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓝ" + u:name="CIRCLED LATIN CAPITAL LETTER N" + string="N" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓞ" + u:name="CIRCLED LATIN CAPITAL LETTER O" + string="O" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓟ" + u:name="CIRCLED LATIN CAPITAL LETTER P" + string="P" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓠ" + u:name="CIRCLED LATIN CAPITAL LETTER Q" + string="Q" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓡ" + u:name="CIRCLED LATIN CAPITAL LETTER R" + string="R" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓢ" + u:name="CIRCLED LATIN CAPITAL LETTER S" + u:entity="oS" + string="S" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓣ" + u:name="CIRCLED LATIN CAPITAL LETTER T" + string="T" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓤ" + u:name="CIRCLED LATIN CAPITAL LETTER U" + string="U" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓥ" + u:name="CIRCLED LATIN CAPITAL LETTER V" + string="V" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓦ" + u:name="CIRCLED LATIN CAPITAL LETTER W" + string="W" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓧ" + u:name="CIRCLED LATIN CAPITAL LETTER X" + string="X" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓨ" + u:name="CIRCLED LATIN CAPITAL LETTER Y" + string="Y" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="Ⓩ" + u:name="CIRCLED LATIN CAPITAL LETTER Z" + string="Z" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓐ" + u:name="CIRCLED LATIN SMALL LETTER A" + string="a" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓑ" + u:name="CIRCLED LATIN SMALL LETTER B" + string="b" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓒ" + u:name="CIRCLED LATIN SMALL LETTER C" + string="c" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓓ" + u:name="CIRCLED LATIN SMALL LETTER D" + string="d" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓔ" + u:name="CIRCLED LATIN SMALL LETTER E" + string="e" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓕ" + u:name="CIRCLED LATIN SMALL LETTER F" + string="f" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓖ" + u:name="CIRCLED LATIN SMALL LETTER G" + string="g" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓗ" + u:name="CIRCLED LATIN SMALL LETTER H" + string="h" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓘ" + u:name="CIRCLED LATIN SMALL LETTER I" + string="i" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓙ" + u:name="CIRCLED LATIN SMALL LETTER J" + string="j" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓚ" + u:name="CIRCLED LATIN SMALL LETTER K" + string="k" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓛ" + u:name="CIRCLED LATIN SMALL LETTER L" + string="l" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓜ" + u:name="CIRCLED LATIN SMALL LETTER M" + string="m" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓝ" + u:name="CIRCLED LATIN SMALL LETTER N" + string="n" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓞ" + u:name="CIRCLED LATIN SMALL LETTER O" + string="o" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓟ" + u:name="CIRCLED LATIN SMALL LETTER P" + string="p" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓠ" + u:name="CIRCLED LATIN SMALL LETTER Q" + string="q" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓡ" + u:name="CIRCLED LATIN SMALL LETTER R" + string="r" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓢ" + u:name="CIRCLED LATIN SMALL LETTER S" + string="s" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓣ" + u:name="CIRCLED LATIN SMALL LETTER T" + string="t" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓤ" + u:name="CIRCLED LATIN SMALL LETTER U" + string="u" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓥ" + u:name="CIRCLED LATIN SMALL LETTER V" + string="b" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓦ" + u:name="CIRCLED LATIN SMALL LETTER W" + string="w" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓧ" + u:name="CIRCLED LATIN SMALL LETTER X" + string="x" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓨ" + u:name="CIRCLED LATIN SMALL LETTER Y" + string="y" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="ⓩ" + u:name="CIRCLED LATIN SMALL LETTER Z" + string="z" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓪" + u:name="CIRCLED DIGIT ZERO" + string="0" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓫" + u:name="NEGATIVE CIRCLED NUMBER ELEVEN" + string="11" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓬" + u:name="NEGATIVE CIRCLED NUMBER TWELVE" + string="12" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓭" + u:name="NEGATIVE CIRCLED NUMBER THIRTEEN" + string="13" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓮" + u:name="NEGATIVE CIRCLED NUMBER FOURTEEN" + string="14" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓯" + u:name="NEGATIVE CIRCLED NUMBER FIFTEEN" + string="15" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓰" + u:name="NEGATIVE CIRCLED NUMBER SIXTEEN" + string="16" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓱" + u:name="NEGATIVE CIRCLED NUMBER SEVENTEEN" + string="17" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓲" + u:name="NEGATIVE CIRCLED NUMBER EIGHTEEN" + string="18" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓳" + u:name="NEGATIVE CIRCLED NUMBER NINETEEN" + string="19" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓴" + u:name="NEGATIVE CIRCLED NUMBER TWENTY" + string="20" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓵" + u:name="DOUBLE CIRCLED DIGIT ONE" + string="1" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓶" + u:name="DOUBLE CIRCLED DIGIT TWO" + string="2" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓷" + u:name="DOUBLE CIRCLED DIGIT THREE" + string="3" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓸" + u:name="DOUBLE CIRCLED DIGIT FOUR" + string="4" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓹" + u:name="DOUBLE CIRCLED DIGIT FIVE" + string="5" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓺" + u:name="DOUBLE CIRCLED DIGIT SIX" + string="6" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓻" + u:name="DOUBLE CIRCLED DIGIT SEVEN" + string="7" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓼" + u:name="DOUBLE CIRCLED DIGIT EIGHT" + string="8" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓽" + u:name="DOUBLE CIRCLED DIGIT NINE" + string="9" + u:block="Enclosed Alphanumerics" + /> + <xsl:output-character + character="⓾" + u:name="DOUBLE CIRCLED NUMBER TEN" + string="10" + u:block="Enclosed Alphanumerics" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Enclosed Alphanumerics --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Box Drawing --> + <!-- * x2500 to x257f --> + <!-- * Block Elements --> + <!-- * x2580 to x259f --> + <!-- * - do nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Geometric Shapes --> + <!-- * x25a0 to x25f7 --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="□" + u:name="WHITE SQUARE" + u:entity="squ" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▢" + u:name="WHITE SQUARE WITH ROUNDED CORNERS" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▫" + u:name="WHITE SMALL SQUARE" + string="\(sq" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="►" + u:name="BLACK RIGHT-POINTING POINTER" + string="\fB>\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▻" + u:name="WHITE RIGHT-POINTING POINTER" + string=">" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▼" + u:name="BLACK DOWN-POINTING TRIANGLE" + string="\fBv\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="▽" + u:name="WHITE DOWN-POINTING TRIANGLE" + u:entity="xdtri" + string="v" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◄" + u:name="BLACK LEFT-POINTING POINTER" + string="\fB<\fR" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◅" + u:name="WHITE LEFT-POINTING POINTER" + string="<" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◆" + u:name="BLACK DIAMOND" + string="\(DI" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◇" + u:name="WHITE DIAMOND" + string="\(lz" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◊" + u:name="LOZENGE" + u:entity="loz" + string="\(lz" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="○" + u:name="WHITE CIRCLE" + u:entity="cir" + string="\(ci" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="●" + u:name="BLACK CIRCLE" + string="\(bu" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◦" + u:name="WHITE BULLET" + string="\(ci" + u:block="Geometric Shapes" + /> + <xsl:output-character + character="◯" + u:name="LARGE CIRCLE" + u:entity="xcirc" + string="\(ci" + u:block="Geometric Shapes" + /> + <!-- * ***************************************************************** --> + <!-- * End: Geometric Shapes --> + <!-- * x25a0 to x25f7 --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Miscellaneous Symbols --> + <!-- * x2600 to x26ff --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="☚" + u:name="BLACK LEFT POINTING INDEX" + string="\(lh" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☛" + u:name="BLACK RIGHT POINTING INDEX" + string="\(rh)" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☜" + u:name="WHITE LEFT POINTING INDEX" + string="\(lh" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="☞" + u:name="WHITE RIGHT POINTING INDEX" + string="\(rh)" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♠" + u:name="BLACK SPADE SUIT" + u:entity="spades" + string="\(SP" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♡" + u:name="WHITE HEART SUIT" + string="\(HE" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♢" + u:name="WHITE DIAMOND SUIT" + string="\(DI" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♣" + u:name="BLACK CLUB SUIT" + u:entity="clubs" + string="\(CL" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♤" + u:name="WHITE SPADE SUIT" + string="\(SP" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♥" + u:name="BLACK HEART SUIT" + u:entity="hearts" + string="\(HE" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♦" + u:name="BLACK DIAMOND SUIT" + u:entity="diams" + string="\(DI" + u:block="Miscellaneous Symbols" + /> + <xsl:output-character + character="♧" + u:name="WHITE CLUB SUIT" + string="\(CL" + u:block="Miscellaneous Symbols" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Miscellaneous Symbols --> + <!-- * ***************************************************************** --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Dingbats --> + <!-- * x2700 to x27be --> + <!-- * No roff equiv for most of these; just map to something close --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="✓" + u:name="CHECK MARK" + u:entity="check" + string="\(OK" + u:block="Dingbats" + /> + <xsl:output-character + character="✔" + u:name="HEAVY CHECK MARK" + string="\fB\(OK\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✕" + u:name="MULTIPLICATION X" + string="\(mu" + u:block="Dingbats" + /> + <xsl:output-character + character="✖" + u:name="HEAVY MULTIPLICATION X" + string="\fB\(mu\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✗" + u:name="BALLOT X" + u:entity="cross" + string="\(mu" + u:block="Dingbats" + /> + <xsl:output-character + character="✘" + u:name="HEAVY BALLOT X" + string="\fB\(mu\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✙" + u:name="OUTLINED GREEK CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✚" + u:name="HEAVY GREEK CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✛" + u:name="OPEN CENTRE CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✜" + u:name="HEAVY OPEN CENTRE CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✝" + u:name="LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✞" + u:name="SHADOWED WHITE LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✟" + u:name="OUTLINED LATIN CROSS" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✠" + u:name="MALTESE CROSS" + u:entity="malt" + string="\fB+\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="✡" + u:name="STAR OF DAVID" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✢" + u:name="FOUR TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✣" + u:name="FOUR BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✤" + u:name="HEAVY FOUR BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✥" + u:name="FOUR CLUB-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✦" + u:name="BLACK FOUR POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✧" + u:name="WHITE FOUR POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✩" + u:name="STRESS OUTLINED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✪" + u:name="CIRCLED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✫" + u:name="OPEN CENTRE BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✬" + u:name="BLACK CENTRE WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✭" + u:name="OUTLINED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✮" + u:name="HEAVY OUTLINED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✯" + u:name="PINWHEEL STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✰" + u:name="SHADOWED WHITE STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✱" + u:name="HEAVY ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✲" + u:name="OPEN CENTRE ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✳" + u:name="EIGHT SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✴" + u:name="EIGHT POINTED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✵" + u:name="EIGHT POINTED PINWHEEL STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✶" + u:name="SIX POINTED BLACK STAR" + u:entity="sext" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✷" + u:name="EIGHT POINTED RECTILINEAR BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✸" + u:name="HEAVY EIGHT POINTED RECTILINEAR BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✹" + u:name="TWELVE POINTED BLACK STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✺" + u:name="SIXTEEN POINTED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✻" + u:name="TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✼" + u:name="OPEN CENTRE TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✽" + u:name="HEAVY TEARDROP-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✾" + u:name="SIX PETALLED BLACK AND WHITE FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="✿" + u:name="BLACK FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❀" + u:name="WHITE FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❁" + u:name="EIGHT PETALLED OUTLINED BLACK FLORETTE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❂" + u:name="CIRCLED OPEN CENTRE EIGHT POINTED STAR" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❃" + u:name="HEAVY TEARDROP-SPOKED PINWHEEL ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❄" + u:name="SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❅" + u:name="TIGHT TRIFOLIATE SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❆" + u:name="HEAVY CHEVRON SNOWFLAKE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❇" + u:name="SPARKLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❈" + u:name="HEAVY SPARKLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❉" + u:name="BALLOON-SPOKED ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❊" + u:name="EIGHT TEARDROP-SPOKED PROPELLER ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❋" + u:name="HEAVY EIGHT TEARDROP-SPOKED PROPELLER ASTERISK" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❍" + u:name="SHADOWED WHITE CIRCLE" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❏" + u:name="LOWER RIGHT DROP-SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❐" + u:name="UPPER RIGHT DROP-SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❑" + u:name="LOWER RIGHT SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❒" + u:name="UPPER RIGHT SHADOWED WHITE SQUARE" + string="\(sq" + u:block="Dingbats" + /> + <xsl:output-character + character="❖" + u:name="BLACK DIAMOND MINUS WHITE X" + string="*" + u:block="Dingbats" + /> + <xsl:output-character + character="❘" + u:name="LIGHT VERTICAL BAR" + string="\(bv" + u:block="Dingbats" + /> + <xsl:output-character + character="❙" + u:name="MEDIUM VERTICAL BAR" + string="\fB\(bv\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❚" + u:name="HEAVY VERTICAL BAR" + string="\fB\(bv\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❛" + u:name="HEAVY SINGLE TURNED COMMA QUOTATION MARK ORNAMENT" + string="\fB\(oq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❜" + u:name="HEAVY SINGLE COMMA QUOTATION MARK ORNAMENT" + string="\fB\(cq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❝" + u:name="HEAVY DOUBLE TURNED COMMA QUOTATION MARK ORNAMENT" + string="\fB\(lq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❞" + u:name="HEAVY DOUBLE COMMA QUOTATION MARK ORNAMENT" + string="\fB\(rq\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❡" + u:name="CURVED STEM PARAGRAPH SIGN ORNAMENT" + string="\(ps" + u:block="Dingbats" + /> + <xsl:output-character + character="❢" + u:name="HEAVY EXCLAMATION MARK ORNAMENT" + string="\fB!\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❣" + u:name="HEAVY HEART EXCLAMATION MARK ORNAMENT" + string="\fB!\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❤" + u:name="HEAVY BLACK HEART" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❥" + u:name="ROTATED HEAVY BLACK HEART BULLET" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❦" + u:name="FLORAL HEART" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❧" + u:name="ROTATED FLORAL HEART BULLET" + string="\fB\(HE\fR" + u:block="Dingbats" + /> + <xsl:output-character + character="❶" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="❷" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="❸" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="❹" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="❺" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="❻" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="❼" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="❽" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="❾" + u:name="DINGBAT NEGATIVE CIRCLED DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="❿" + u:name="DINGBAT NEGATIVE CIRCLED NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➀" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="➁" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="➂" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="➃" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="➄" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="➅" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="➆" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="➇" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="➈" + u:name="DINGBAT CIRCLED SANS-SERIF DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="➉" + u:name="DINGBAT CIRCLED SANS-SERIF NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➊" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT ONE" + string="1" + u:block="Dingbats" + /> + <xsl:output-character + character="➋" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT TWO" + string="2" + u:block="Dingbats" + /> + <xsl:output-character + character="➌" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT THREE" + string="3" + u:block="Dingbats" + /> + <xsl:output-character + character="➍" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FOUR" + string="4" + u:block="Dingbats" + /> + <xsl:output-character + character="➎" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT FIVE" + string="5" + u:block="Dingbats" + /> + <xsl:output-character + character="➏" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SIX" + string="6" + u:block="Dingbats" + /> + <xsl:output-character + character="➐" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT SEVEN" + string="7" + u:block="Dingbats" + /> + <xsl:output-character + character="➑" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT EIGHT" + string="8" + u:block="Dingbats" + /> + <xsl:output-character + character="➒" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF DIGIT NINE" + string="9" + u:block="Dingbats" + /> + <xsl:output-character + character="➓" + u:name="DINGBAT NEGATIVE CIRCLED SANS-SERIF NUMBER TEN" + string="10" + u:block="Dingbats" + /> + <xsl:output-character + character="➔" + u:name="HEAVY WIDE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➙" + u:name="HEAVY RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➛" + u:name="DRAFTING POINT RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➜" + u:name="HEAVY ROUND-TIPPED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➝" + u:name="TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➞" + u:name="HEAVY TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➟" + u:name="DASHED TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➠" + u:name="HEAVY DASHED TRIANGLE-HEADED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➡" + u:name="BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➢" + u:name="THREE-D TOP-LIGHTED RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➣" + u:name="THREE-D BOTTOM-LIGHTED RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➤" + u:name="BLACK RIGHTWARDS ARROWHEAD" + string="\(->" + u:block="Dingbats" + /> + <xsl:output-character + character="➧" + u:name="SQUAT BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➨" + u:name="HEAVY CONCAVE-POINTED BLACK RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➩" + u:name="RIGHT-SHADED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➪" + u:name="LEFT-SHADED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➫" + u:name="BACK-TILTED SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➬" + u:name="FRONT-TILTED SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➭" + u:name="HEAVY LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➮" + u:name="HEAVY UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➯" + u:name="NOTCHED LOWER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➱" + u:name="NOTCHED UPPER RIGHT-SHADOWED WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➲" + u:name="CIRCLED HEAVY WHITE RIGHTWARDS ARROW" + string="\(rA" + u:block="Dingbats" + /> + <xsl:output-character + character="➳" + u:name="WHITE-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➴" + u:name="BLACK-FEATHERED SOUTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➵" + u:name="BLACK-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➶" + u:name="BLACK-FEATHERED NORTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➷" + u:name="HEAVY BLACK-FEATHERED SOUTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➸" + u:name="HEAVY BLACK-FEATHERED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➹" + u:name="HEAVY BLACK-FEATHERED NORTH EAST ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➺" + u:name="TEARDROP-BARBED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➻" + u:name="HEAVY TEARDROP-SHANKED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➼" + u:name="WEDGE-TAILED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➽" + u:name="HEAVY WEDGE-TAILED RIGHTWARDS ARROW" + string="\fR\(->\fB" + u:block="Dingbats" + /> + <xsl:output-character + character="➾" + u:name="OPEN-OUTLINED RIGHTWARDS ARROW" + string="\fR\(rA\fB" + u:block="Dingbats" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Dingbats --> + <!-- * ***************************************************************** --> + + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + <!-- * Miscellaneous Mathematical Symbols --> + <!-- * x27c0 to x27ef --> + <!-- * Supplemental Arrows --> + <!-- * x27f0 to x297f --> + <!-- * Miscellaneous Mathematical Symbols --> + <!-- * x2980 to x29ff --> + <!-- * Supplemental Mathematical Operators --> + <!-- * x2a00 to x2aff --> + <!-- * - no nothing - --> + <!-- * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! --> + + <!-- * ***************************************************************** --> + <!-- * Begin: Alphabetic Presentation Forms --> + <!-- * xfb00 to xfb04 --> + <!-- * ***************************************************************** --> + + <xsl:output-character + character="ff" + u:name="LATIN SMALL LIGATURE FF" + u:entity="fflig" + string="\(ff" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="fi" + u:name="LATIN SMALL LIGATURE FI" + u:entity="filig" + string="\(fi" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="fl" + u:name="LATIN SMALL LIGATURE FL" + u:entity="fllig" + string="\(fl" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="ffi" + u:name="LATIN SMALL LIGATURE FFI" + u:entity="ffilig" + string="\(Fi" + u:block="Alphabetic Presentation Forms" + /> + <xsl:output-character + character="ffl" + u:name="LATIN SMALL LIGATURE FFL" + u:entity="ffllig" + string="\(Fl" + u:block="Alphabetic Presentation Forms" + /> + + <!-- * ***************************************************************** --> + <!-- * End: Alphabetic Presentation Forms --> + <!-- * ***************************************************************** --> + + <!-- * ================================================================= --> + + <!-- * Regarding x2060 vs. xFEFF, the document "Unicode Standard Annex #14, --> + <!-- * Line Breaking Properties"[1] says: --> + <!-- * --> + <!-- * The word joiner character [x2060 a.k.a "WJ"] is the preferred --> + <!-- * choice for an invisible character to keep other characters --> + <!-- * together that would otherwise be split across the line at a direct --> + <!-- * break. The character FEFF has the same effect, but because it is --> + <!-- * also used in an unrelated way as a byte order mark, the use of the --> + <!-- * WJ as the preferred interword glue simplifies the handling of FEFF. --> + <!-- * --> + <!-- * [1] http://www.unicode.org/reports/tr14/ --> + <!-- * --> + <!-- * We include it here anyway & map to the roff zero-width no-break --> + <xsl:output-character + character="" + u:name="ZERO WIDTH NO-BREAK SPACE" + string="\&" + u:block="Arabic Presentation Forms-B" + /> +</xsl:character-map> +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/docbook.xsl b/docs/xsl-generic/manpages/docbook.xsl new file mode 100644 index 00000000..87c7ade8 --- /dev/null +++ b/docs/xsl-generic/manpages/docbook.xsl @@ -0,0 +1,293 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + exclude-result-prefixes="exsl" + version='1.0'> + + <xsl:import href="../html/docbook.xsl"/> + <xsl:import href="../html/manifest.xsl"/> + <!-- * html-synop.xsl file is generated by build --> + <xsl:import href="html-synop.xsl"/> + <xsl:output method="text" + encoding="UTF-8" + indent="no"/> + <!-- ******************************************************************** + $Id: docbook.xsl 7153 2007-07-26 14:08:55Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <!-- ==================================================================== --> + + <xsl:include href="../common/refentry.xsl"/> + <xsl:include href="../common/charmap.xsl"/> + <xsl:include href="param.xsl"/> + <xsl:include href="utility.xsl"/> + <xsl:include href="info.xsl"/> + <xsl:include href="other.xsl"/> + <xsl:include href="refentry.xsl"/> + <xsl:include href="block.xsl"/> + <xsl:include href="inline.xsl"/> + <xsl:include href="synop.xsl"/> + <xsl:include href="lists.xsl"/> + <xsl:include href="endnotes.xsl"/> + <xsl:include href="table.xsl"/> + + <!-- * we rename the following just to avoid using params with "man" --> + <!-- * prefixes in the table.xsl stylesheet (because that stylesheet --> + <!-- * can potentially be reused for more than just man output) --> + <xsl:param name="tbl.font.headings" select="$man.font.table.headings"/> + <xsl:param name="tbl.font.title" select="$man.font.table.title"/> + + <!-- ==================================================================== --> + + <xsl:template match="/"> + <!-- * Get a title for current doc so that we let the user --> + <!-- * know what document we are processing at this point. --> + <xsl:variable name="doc.title"> + <xsl:call-template name="get.doc.title"/> + </xsl:variable> + <xsl:choose> + <!-- * when we find a namespaced document, strip the --> + <!-- * namespace and then continue processing it. --> + <xsl:when test="//self::db:*"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text>namesp. cut</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>stripped namespace before processing</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:variable name="stripns"> + <xsl:apply-templates mode="stripNS"/> + </xsl:variable> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text>namesp. cut</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>processing stripped document</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:apply-templates select="exsl:node-set($stripns)"/> + </xsl:when> + <xsl:when test="//*[local-name() = 'refentry']"> + <!-- * Check to see if we have any refentry children in this --> + <!-- * document; if so, process them. The reason we use --> + <!-- * local-name()=refentry (instead of just //refentry) to to --> + <!-- * check for refentry children is because this stylsheet is --> + <!-- * also post-processed by the stylesheet build to create the --> + <!-- * manpages/profile-docbook.xsl, and the refentry child check --> + <!-- * in the profile-docbook.xsl stylesheet won't work if we do --> + <!-- * a simple //refentry check. --> + <xsl:apply-templates select="//refentry"/> + <!-- * if $man.output.manifest.enabled is non-zero, --> + <!-- * generate a manifest file --> + <xsl:if test="not($man.output.manifest.enabled = 0)"> + <xsl:call-template name="generate.manifest"> + <xsl:with-param name="filename"> + <xsl:choose> + <xsl:when test="not($man.output.manifest.filename = '')"> + <!-- * If a name for the manifest file is specified, --> + <!-- * use that name. --> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, if user has unset --> + <!-- * $man.output.manifest.filename, default to --> + <!-- * using "MAN.MANIFEST" as the filename. Because --> + <!-- * $man.output.manifest.enabled is non-zero and --> + <!-- * so we must have a filename in order to --> + <!-- * generate the manifest. --> + <xsl:text>MAN.MANIFEST</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, the document does not contain any --> + <!-- * refentry elements, so log/emit message and stop. --> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Erro</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text> no refentry</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>No refentry elements found</xsl:text> + <xsl:if test="$doc.title != ''"> + <xsl:text> in "</xsl:text> + <xsl:choose> + <xsl:when test="string-length($doc.title) > 30"> + <xsl:value-of select="substring($doc.title,1,30)"/> + <xsl:text>...</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$doc.title"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>"</xsl:text> + </xsl:if> + <xsl:text>.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template match="refentry"> + <xsl:param name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:param> + <!-- * Just use the first refname found as the "name" of the man --> + <!-- * page (which may different from the "title"...) --> + <xsl:variable name="first.refname" select="refnamediv[1]/refname[1]"/> + + <xsl:call-template name="root.messages"> + <xsl:with-param name="refname" select="$first.refname"/> + </xsl:call-template> + + <!-- * Because there are several times when we need to check *info of --> + <!-- * each refentry and its ancestors, we get those and store the --> + <!-- * data from them as a node-set in memory. --> + + <!-- * Make a node-set with contents of *info --> + <xsl:variable name="get.info" + select="ancestor-or-self::*/*[substring(local-name(), + string-length(local-name()) - 3) = 'info']" + /> + <xsl:variable name="info" select="exsl:node-set($get.info)"/> + + <!-- * The get.refentry.metadata template is in --> + <!-- * ../common/refentry.xsl. It looks for metadata in $info --> + <!-- * and in various other places and then puts it into a form --> + <!-- * that's easier for us to digest. --> + <xsl:variable name="get.refentry.metadata"> + <xsl:call-template name="get.refentry.metadata"> + <xsl:with-param name="refname" select="$first.refname"/> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="prefs" select="$refentry.metadata.prefs"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="refentry.metadata" select="exsl:node-set($get.refentry.metadata)"/> + + <!-- * Assemble the various parts into a complete page, then store into --> + <!-- * $manpage.contents so that we can manipluate them further. --> + <xsl:variable name="manpage.contents"> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * top.comment = commented-out section at top of roff source --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="top.comment"> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="date" select="$refentry.metadata/date"/> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="manual" select="$refentry.metadata/manual"/> + <xsl:with-param name="source" select="$refentry.metadata/source"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * TH.title.line = title line in header/footer of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="TH.title.line"> + <!-- * .TH TITLE section extra1 extra2 extra3 --> + <!-- * --> + <!-- * According to the man(7) man page: --> + <!-- * --> + <!-- * extra1 = date, "the date of the last revision" --> + <!-- * extra2 = source, "the source of the command" --> + <!-- * extra3 = manual, "the title of the manual --> + <!-- * (e.g., Linux Programmer's Manual)" --> + <!-- * --> + <!-- * So, we end up with: --> + <!-- * --> + <!-- * .TH TITLE section date source manual --> + <!-- * --> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="extra1" select="$refentry.metadata/date"/> + <xsl:with-param name="extra2" select="$refentry.metadata/source"/> + <xsl:with-param name="extra3" select="$refentry.metadata/manual"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Set default hyphenation, justification, indentation, and --> + <!-- * line-breaking --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="set.default.formatting"/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Main body of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:apply-templates/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * AUTHOR section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.authors.section.enabled = 0)"> + <xsl:call-template name="author.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * COPYRIGHT section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.copyright.section.enabled = 0)"> + <xsl:call-template name="copyright.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * NOTES list (only if user wants endnotes numbered and/or listed) --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="$man.endnotes.list.enabled != 0 or + $man.endnotes.are.numbered != 0"> + <xsl:call-template name="endnotes.list"/> + </xsl:if> + </xsl:variable> <!-- * end of manpage.contents --> + + <!-- * Prepare the page contents for final output, then store in --> + <!-- * $manpage.contents.prepared so the we can pass it on to the --> + <!-- * write.text.chunk() function --> + <xsl:variable name="manpage.contents.prepared"> + <!-- * "Preparing" the page contents involves, at a minimum, --> + <!-- * doubling any backslashes found (so they aren't interpreted --> + <!-- * as roff escapes). --> + <!-- * --> + <!-- * If $charmap.enabled is true, "preparing" the page contents also --> + <!-- * involves applying a character map to convert Unicode symbols and --> + <!-- * special characters into corresponding roff escape sequences. --> + <xsl:call-template name="prepare.manpage.contents"> + <xsl:with-param name="content" select="$manpage.contents"/> + </xsl:call-template> + </xsl:variable> + + <!-- * Write the prepared page contents to disk to create --> + <!-- * the final man page. --> + <xsl:call-template name="write.man.file"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + <xsl:with-param name="content" select="$manpage.contents.prepared"/> + </xsl:call-template> + + <!-- * Generate "stub" (alias) pages (if any needed) --> + <xsl:call-template name="write.stubs"> + <xsl:with-param name="first.refname" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/endnotes.xsl b/docs/xsl-generic/manpages/endnotes.xsl new file mode 100644 index 00000000..f1c7480c --- /dev/null +++ b/docs/xsl-generic/manpages/endnotes.xsl @@ -0,0 +1,535 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + xmlns:xlink="http://www.w3.org/1999/xlink" + exclude-result-prefixes="db ng exsl xlink" + version='1.0'> + +<!-- ******************************************************************** + $Id: endnotes.xsl 7254 2007-08-18 23:59:53Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> +<!-- * --> +<!-- * The templates in this file handle elements whose contents can't --> +<!-- * be displayed completely within the main text flow in output, but --> +<!-- * instead need to be displayed "out of line". Those elements are: --> +<!-- * --> +<!-- * - elements providing annotative text (annotation|alt|footnote) --> +<!-- * - elements pointing at external resources (ulink, link, and --> +<!-- * any elements with xlink:href attributes; and imagedata, --> +<!-- * audiodata, and videodata - which (using their fileref --> +<!-- * attribute) reference external files --> +<!-- * --> +<!-- * Within this stylesheet, the above are collectively referred to as --> +<!-- * a "notesources". This stylesheet handles those notesources in --> +<!-- * this way: --> +<!-- * --> +<!-- * 1. Constructs a numbered in-memory index of all unique "earmarks“ --> +<!-- * of all notesources in the document. For each link, the --> +<!-- * earmark is the value of its url or xlink:href attribute; for --> +<!-- * each imagedata|audiodata|videodata: the value of its fileref --> +<!-- * attribute; for each annotative element: its content. --> +<!-- * --> +<!-- * Notesources with the same earmark are assigned the same --> +<!-- * number. --> +<!-- * --> +<!-- * By design, that index excludes any element whose whose string --> +<!-- * value is identical to value of its url xlink:href attribute). --> +<!-- * --> +<!-- * 2. Puts a numbered marker inline to mark the place where the --> +<!-- * notesource occurs in the main text flow. --> +<!-- * --> +<!-- * 3. Generates a numbered endnotes list (titled NOTES in English) --> +<!-- * at the end of the man page, with the contents of each --> +<!-- * notesource. --> +<!-- * --> +<!-- * Note that table footnotes are not listed in the endnotes list, --> +<!-- * and are not handled by this stylesheet (they are instead handled --> +<!-- * by the table.xsl stylesheet). --> +<!-- * --> +<!-- * Also, we don't get notesources in *info sections or Refmeta or --> +<!-- * Refnamediv or Indexterm, because, in manpages output, contents of --> +<!-- * those are either suppressed or are displayed out of document --> +<!-- * order - for example, the Info/Author content gets moved to the --> +<!-- * end of the page. So, if we were to number notesources in the --> +<!-- * Author content, it would "throw off" the numbering at the --> +<!-- * beginning of the main text flow. --> +<!-- * --> +<!-- * And for the record, one reason we don't use xsl:key to index the --> +<!-- * earmarks is that we need to get and check the sets of --> +<!-- * earmarks for uniqueness per-Refentry (not per-document). --> +<!-- * --> +<!-- * FIXME: as --> +<!-- * with "repeat" URLS, alt instances that have the same string value --> +<!-- * as preceding ones (likely to occur for repeat acroynyms and --> +<!-- * abbreviations) should be listed only once in the endnotes list, --> +<!-- * and numbered accordingly inline; split man.indent.width into --> +<!-- * man.indent.width.value (default 4) and man.indent.width.units --> +<!-- * (default n); also, if the first child of notesource is some block --> +<!-- * content other than a (non-formal) paragraph, the current code --> +<!-- * will probably end up generating a blank line after the --> +<!-- * corresponding number in the endnotes list... we should probably --> +<!-- * try to instead display the title of that block content there (if --> +<!-- * there is one: e.g., the list title, admonition title, etc.) --> + +<!-- ==================================================================== --> + +<xsl:template name="get.all.earmark.indexes.in.current.document"> + <!-- * Here we create a tree to hold indexes of all earmarks in --> + <!-- * the current document. If the current document contains --> + <!-- * multiple refentry instances, then this tree will contain --> + <!-- * multiple indexes. --> + <xsl:if test="$man.endnotes.are.numbered != 0"> + <!-- * Only create earmark indexes if user wants numbered endnotes --> + <xsl:for-each select="//refentry"> + <earmark.index> + <xsl:attribute name="idref"> + <xsl:value-of select="generate-id()"/> + </xsl:attribute> + <xsl:for-each + select=".//*[self::*[@xlink:href] + or self::ulink + or self::imagedata + or self::audiodata + or self::videodata + or self::footnote[not(ancestor::table)] + or self::annotation + or self::alt] + [(node() + or self::imagedata + or self::audiodata + or self::videodata + ) + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and not(. = @url) + and not(. = @xlink:href) + and not(@url = + preceding::ulink[node() + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@url) + and not(@xlink:href = + preceding::*[@xlink:href][node() + and not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@xlink:href) + and not(@fileref = + preceding::*[@fileref][ + not(ancestor::refentryinfo) + and not(ancestor::info) + and not(ancestor::docinfo) + and not(ancestor::refmeta) + and not(ancestor::refnamediv) + and not(ancestor::indexterm) + and (generate-id(ancestor::refentry) + = generate-id(current()))]/@fileref)]"> + <earmark> + <xsl:attribute name="id"> + <xsl:value-of select="generate-id()"/> + </xsl:attribute> + <xsl:attribute name="number"> + <xsl:value-of select="position()"/> + </xsl:attribute> + <xsl:if test="@url|@xlink:href|@fileref"> + <!-- * Only add a uri attribute if the notesource is --> + <!-- * a link or an element that references an external --> + <!-- * (an imagedata, audiodata, or videodata element) --> + <xsl:attribute name="uri"> + <xsl:value-of select="@url|@xlink:href|@fileref"/> + </xsl:attribute> + </xsl:if> + <xsl:copy> + <xsl:copy-of select="node()"/> + </xsl:copy> + </earmark> + </xsl:for-each> + </earmark.index> + </xsl:for-each> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="*[@xlink:href]|ulink + |imagedata|audiodata|videodata + |footnote[not(ancestor::table)] + |annotation|alt"> + <xsl:variable name="all.earmark.indexes.in.current.document.rtf"> + <xsl:call-template name="get.all.earmark.indexes.in.current.document"/> + </xsl:variable> + <xsl:variable name="all.earmark.indexes.in.current.document" + select="exsl:node-set($all.earmark.indexes.in.current.document.rtf)"/> + <xsl:variable name="all.earmarks.in.current.refentry.rtf"> + <!-- * get the set of all earmarks for the ancestor Refentry of --> + <!-- * this notesource --> + <xsl:copy-of + select="$all.earmark.indexes.in.current.document/earmark.index + [@idref = + generate-id(current()/ancestor::refentry)]/earmark"/> + </xsl:variable> + <xsl:variable name="all.earmarks.in.current.refentry" + select="exsl:node-set($all.earmarks.in.current.refentry.rtf)"/> + + <!-- * identify the earmark for the current element --> + <xsl:variable name="earmark"> + <xsl:choose> + <xsl:when test="@url|@xlink:href"> + <xsl:value-of select="@url|@xlink:href"/> + </xsl:when> + <xsl:when test="@fileref"> + <xsl:value-of select="@fileref"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="generate-id()"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="notesource.number"> + <!-- * Get the number for this notesource --> + <!-- * --> + <!-- * If this is an imagedata, audiodata, or videodata element --> + <!-- * OR if it's a non-empty element AND its string value is not --> + <!-- * equal to the value of its url or xlink:href attribute (if --> + <!-- * it has one) AND user wants endnotes numbered, only then --> + <!-- * do we output a number for it --> + <xsl:if test="(self::imagedata or + self::audiodata or + self::videodata or + (node() + and not(. = @url) + and not(. = @xlink:href)) + ) + and $man.endnotes.are.numbered != 0"> + <!-- * To select the number for this notesource, we --> + <!-- * check the index of all earmarks for the current refentry --> + <!-- * and find the number of the indexed earmark which matches --> + <!-- * this notesource's earmark. --> + <!-- * Note that multiple notesources may share the same --> + <!-- * numbered earmark; in that case, they get the same number. --> + <!-- * --> + <xsl:choose> + <xsl:when test="self::ulink or + self::*[@xlink:href] or + self::imagedata or + self::audiodata or + self::videodata"> + <xsl:value-of select="$all.earmarks.in.current.refentry/earmark[@uri = $earmark]/@number"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$all.earmarks.in.current.refentry/earmark[@id = $earmark]/@number"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:variable> + + <xsl:variable name="notesource.contents"> + <xsl:choose> + <!-- * check to see if the element is empty or not --> + <xsl:when test="node()"> + <!-- * this is a non-empty node, so process its contents --> + <xsl:apply-templates/> + <xsl:if test="../footnote or ../annotation"> + <!-- * if this element is a footnote or annotation, we need to --> + <!-- * do some further checking on it, so we can emit warnings --> + <!-- * about potential problems --> + <xsl:for-each select="node()"> + <xsl:if test="local-name() != 'para' and local-name() !=''"> + <!-- * for each node we find as a child of a footnote or --> + <!-- * annotation, if it's not a para or a text node, emit a --> + <!-- * warning... because in manpages output, we can't render --> + <!-- * block-level child content of an endnote properly unless --> + <!-- * it's wrapped in a para that has some "prefatory" text --> + <xsl:variable name="parent-name" select="local-name(..)"/> + <xsl:variable name="refname" select="ancestor::refentry/refnamediv[1]/refname[1]"/> + <xsl:variable name="endnote-number"> + <xsl:call-template name="pad-string"> + <!-- * endnote number may be 2 digits, so pad it with a space --> + <!-- * if we have only 1 digit --> + <xsl:with-param name="padVar" select="concat('#',$notesource.number)"/> + <xsl:with-param name="length" select="3"/> + </xsl:call-template> + </xsl:variable> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Bad: </xsl:text> + <xsl:value-of select="$parent-name"/> + <!-- * figure out which occurance of this element type this --> + <!-- * instance is and output a number in square brackets so --> + <!-- * that end-user can know which element to fix --> + <xsl:text>[</xsl:text> + <xsl:value-of select="count(preceding::*[local-name() = $parent-name]) + 1"/> + <xsl:text>]</xsl:text> + <xsl:text> in source</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Has: </xsl:text> + <xsl:value-of select="$parent-name"/> + <xsl:text>/</xsl:text> + <xsl:value-of select="local-name(.)"/> + </xsl:with-param> + </xsl:call-template> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Note</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>endnote </xsl:text> + <xsl:value-of select="$endnote-number"/> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Fix: </xsl:text> + <xsl:value-of select="$parent-name"/> + <xsl:text>/</xsl:text> + <xsl:text>para/</xsl:text> + <xsl:value-of select="local-name(.)"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:for-each> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise this is an empty link or an empty imagedata, --> + <!-- * audiodata, or videodata element, so we just get the --> + <!-- * value of its url, xlink:href, or fileref attribute. --> + <xsl:if test="$man.hyphenate.urls = 0 + and $man.break.after.slash = 0"> + <!-- * Add hyphenation suppression in URL output only if --> + <!-- * break.after.slash is also non-zero --> + <xsl:call-template name="suppress.hyphenation"/> + <xsl:text>\%</xsl:text> + </xsl:if> + <xsl:value-of select="$earmark"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="self::ulink or self::*[@xlink:href]"> + <!-- * This is a hyperlink, so we need to decide how to format --> + <!-- * the inline contents of the link (to underline or not). --> + <xsl:choose> + <!-- * if user wants links underlined, underline (ital) it --> + <xsl:when test="$man.links.are.underlined != 0"> + <xsl:variable name="link.wrapper"> + <xsl:value-of select="$notesource.contents"/> + </xsl:variable> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="exsl:node-set($link.wrapper)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * user doesn't want links underlined, so just display content --> + <xsl:value-of select="$notesource.contents"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <xsl:if test="$notesource.number != ''"> + <!-- * Format the number by placing it in square brackets. FIXME: --> + <!-- * This formatting should probably be made user-configurable, --> + <!-- * to allow something other than just square brackets; e.g., --> + <!-- * Angle brackets<10> or Braces{10} --> + <xsl:text>\&[</xsl:text> + <xsl:value-of select="$notesource.number"/> + <xsl:text>]</xsl:text> + <!-- * Note that the reason for the \& before the opening bracket --> + <!-- * is to prevent any possible linebreak from being introduced --> + <!-- * between the opening bracket and the following text. --> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="endnotes.list"> + <!-- We have stored earmark indexes for all refentry instances in the --> + <!-- current document, with the ID for each index being the same ID as --> + <!-- its corresponding refentry; so we now need to get the ID for the --> + <!-- current refentry so we can grab its corresponding earmark index --> + <xsl:variable name="current.refentry.id"> + <xsl:value-of select="generate-id(.)"/> + </xsl:variable> + + <xsl:variable name="endnotes.rtf"> + <xsl:variable name="all.earmark.indexes.in.current.document.rtf"> + <xsl:call-template name="get.all.earmark.indexes.in.current.document"/> + </xsl:variable> + <xsl:variable name="all.earmark.indexes.in.current.document" + select="exsl:node-set($all.earmark.indexes.in.current.document.rtf)"/> + <xsl:copy-of + select="$all.earmark.indexes.in.current.document/earmark.index + [@idref = $current.refentry.id]/earmark"/> + </xsl:variable> + + <xsl:variable name="endnotes" select="exsl:node-set($endnotes.rtf)"/> + + <!-- * check to see if we have actually found any content to use as --> + <!-- * endnotes; if we have, we generate the endnotes list, if not, --> + <!-- * we do nothing --> + <xsl:if test="$endnotes/node()"> + <xsl:call-template name="format.endnotes.list"> + <xsl:with-param name="endnotes" select="$endnotes"/> + </xsl:call-template> + </xsl:if> + +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template name="format.endnotes.list"> + <xsl:param name="endnotes"/> + <xsl:call-template name="mark.subheading"/> + + <!-- * ======= make the endnotes-list section heading ============= --> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <!-- * if user has specified a heading, use that --> + <xsl:when test="$man.endnotes.list.heading != ''"> + <xsl:value-of select="$man.endnotes.list.heading"/> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, get localized heading from gentext --> + <!-- * (in English, NOTES) --> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Notes'"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + + <!-- * ================ process each earmark ====================== --> + <xsl:for-each select="$endnotes/earmark"> + <!-- * make paragraph with hanging indent, and starting with a --> + <!-- * number in the form " 1." (padded to $man.indent.width - 1) --> + <xsl:text>.IP</xsl:text> + <xsl:text> "</xsl:text> + <xsl:variable name="endnote.number"> + <xsl:value-of select="@number"/> + <xsl:text>.</xsl:text> + </xsl:variable> + <xsl:call-template name="pad-string"> + <xsl:with-param name="padVar" select="$endnote.number"/> + <!-- FIXME: the following assumes that $man.indent.width is in --> + <!-- en's; also, this should probably use $list.indent instead --> + <xsl:with-param name="length" select="$man.indent.width - 1"/> + </xsl:call-template> + <xsl:text>"</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + + <!-- * ========================================================= --> + <!-- * print the notesource/endnote contents --> + <!-- * ========================================================= --> + <xsl:choose> + <xsl:when test="*/node()"> + <!-- * if the earmark has non-empty child content, then --> + <!-- * its corresponding notesource is either a link or --> + <!-- * an instance of annotative text, so we want to --> + <!-- * display that content --> + <xsl:choose> + <xsl:when test="*/node()[name(.)!='']"> + <!-- * if node is not text only, then process it as-is --> + <xsl:apply-templates select="*/node()"/> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise node is text-only, so normalize it --> + <xsl:value-of select="normalize-space(*/node())"/> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, this earmark has empty content, --> + <!-- * which means its corresponding notesources is an --> + <!-- * imagedata, audiodata, or videodata instance; in --> + <!-- * that case, we use the value of the notesoures's --> + <!-- * @fileref attribute (which is stored in the --> + <!-- * earmark uri attribute) as the "contents" for --> + <!-- * this endnote/notesource --> + <xsl:value-of select="@uri"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text> </xsl:text> + + <!-- * ========================================================= --> + <!-- * print the URL for links --> + <!-- * ========================================================= --> + <!-- * In addition to the notesource contents, if the --> + <!-- * notesource is a link, we display the URL for the link. --> + <!-- * But for notesources that are imagedata, audiodata, or --> + <!-- * videodata instances, we don't want to (re)display the --> + <!-- * URL for those here, because for those elements, the --> + <!-- * notesource contents are the URL (the value of the --> + <!-- * @fileref attribute), and we have already rendered them. --> + <!-- * --> + <!-- * We know an earmark is a link if it has non-empty child --> + <!-- * content and a uri attribute; so we check for that --> + <!-- * condition here. --> + <xsl:if test="*/node() and @uri"> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <!-- * Add hyphenation suppression in URL output only if --> + <!-- * $break.after.slash is also non-zero --> + <xsl:if test="$man.hyphenate.urls = 0 + and $man.break.after.slash = 0"> + <xsl:call-template name="suppress.hyphenation"/> + <xsl:text>\%</xsl:text> + </xsl:if> + <xsl:value-of select="@uri"/> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + + </xsl:for-each> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/html-synop.xsl b/docs/xsl-generic/manpages/html-synop.xsl new file mode 100644 index 00000000..a6182a01 --- /dev/null +++ b/docs/xsl-generic/manpages/html-synop.xsl @@ -0,0 +1,1605 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<!-- ******************************************************************** + $Id: synop.xsl 7250 2007-08-18 10:19:00Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<!-- synopsis is in verbatim --> + +<!-- ==================================================================== --> + +<xsl:template match="cmdsynopsis"> + <div> + <xsl:apply-templates select="." mode="class.attribute"/> + <p> + <xsl:if test="..//processing-instruction('dbcmdlist')"> + <!-- * Placing a dbcmdlist PI as a child of a particular element --> + <!-- * creates a hyperlinked list of all cmdsynopsis instances --> + <!-- * that are descendants of that element; so for any --> + <!-- * cmdsynopsis that is a descendant of an element containing --> + <!-- * a dbcmdlist PI, we need to output an a@id instance so that --> + <!-- * we will have something to link to --> + <xsl:call-template name="anchor"> + <xsl:with-param name="conditional" select="0"/> + </xsl:call-template> + </xsl:if> + <xsl:apply-templates/> + </p> + </div> +</xsl:template> + +<xsl:template match="cmdsynopsis/command"> + <xsl:text> +. +</xsl:text> + <xsl:call-template name="inline.monoseq"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="cmdsynopsis/command[1]" priority="2"> + <xsl:call-template name="inline.monoseq"/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="group|arg" name="group-or-arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:variable name="sepchar"> + <xsl:choose> + <xsl:when test="ancestor-or-self::*/@sepchar"> + <xsl:value-of select="ancestor-or-self::*/@sepchar"/> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="preceding-sibling::*"> + <xsl:value-of select="$sepchar"/> + </xsl:if> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:value-of select="$arg.choice.plain.open.str"/> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.open.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.open.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates/> + <xsl:choose> + <xsl:when test="$rep='repeat'"> + <xsl:value-of select="$arg.rep.repeat.str"/> + </xsl:when> + <xsl:when test="$rep='norepeat'"> + <xsl:value-of select="$arg.rep.norepeat.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.rep.def.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:value-of select="$arg.choice.plain.close.str"/> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.close.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.close.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.close.str"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="group/arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:if test="preceding-sibling::*"> + <xsl:value-of select="$arg.or.sep"/> + </xsl:if> + <xsl:call-template name="group-or-arg"/> +</xsl:template> + +<xsl:template match="sbr"> + <xsl:text> +. +</xsl:text> +</xsl:template> + +<!-- ==================================================================== --> + +<xsl:template match="synopfragmentref"> + <xsl:variable name="target" select="key('id',@linkend)"/> + <xsl:variable name="snum"> + <xsl:apply-templates select="$target" mode="synopfragment.number"/> + </xsl:variable> + <i> + <a href="#{@linkend}"> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + </a> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + </i> +</xsl:template> + +<xsl:template match="synopfragment" mode="synopfragment.number"> + <xsl:number format="1"/> +</xsl:template> + +<xsl:template match="synopfragment"> + <xsl:variable name="snum"> + <xsl:apply-templates select="." mode="synopfragment.number"/> + </xsl:variable> + <p> + <xsl:variable name="id"> + <xsl:call-template name="object.id"/> + </xsl:variable> + <a name="{$id}"> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + </a> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + </p> +</xsl:template> + +<xsl:template match="funcsynopsis"> + <xsl:if test="..//processing-instruction('dbfunclist')"> + <!-- * Placing a dbfunclist PI as a child of a particular element --> + <!-- * creates a hyperlinked list of all funcsynopsis instances that --> + <!-- * are descendants of that element; so for any funcsynopsis that is --> + <!-- * a descendant of an element containing a dbfunclist PI, we need --> + <!-- * to output an a@id instance so that we will have something to --> + <!-- * link to --> + <xsl:call-template name="anchor"> + <xsl:with-param name="conditional" select="0"/> + </xsl:call-template> + </xsl:if> + <xsl:call-template name="informal.object"/> +</xsl:template> + +<xsl:template match="funcsynopsisinfo"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates/> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype --> +<!-- + +funcprototype ::= (funcdef, + (void|varargs|paramdef+)) + +funcdef ::= (#PCDATA|type|replaceable|function)* + +paramdef ::= (#PCDATA|type|replaceable|parameter|funcparams)* +--> + +<xsl:template match="funcprototype"> + <xsl:variable name="html-style"> + <xsl:call-template name="pi.dbhtml_funcsynopsis-style"> + <xsl:with-param name="node" select="ancestor::funcsynopsis/descendant-or-self::*"/> + </xsl:call-template> + </xsl:variable> + + <xsl:variable name="style"> + <xsl:choose> + <xsl:when test="$html-style != ''"> + <xsl:value-of select="$html-style"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$funcsynopsis.style"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + +<!-- + <xsl:variable name="tabular-p" + select="$funcsynopsis.tabular.threshold > 0 + and string-length(.) > $funcsynopsis.tabular.threshold"/> +--> + + <xsl:variable name="tabular-p" select="true()"/> + + <xsl:choose> + <xsl:when test="$style = 'kr' and $tabular-p"> + <xsl:apply-templates select="." mode="kr-tabular"/> + </xsl:when> + <xsl:when test="$style = 'kr'"> + <xsl:apply-templates select="." mode="kr-nontabular"/> + </xsl:when> + <xsl:when test="$style = 'ansi' and $tabular-p"> + <xsl:apply-templates select="." mode="ansi-tabular"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="ansi-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: kr, non-tabular --> + +<xsl:template match="funcprototype" mode="kr-nontabular"> + <p> + <xsl:apply-templates mode="kr-nontabular"/> + <xsl:if test="paramdef"> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="paramdef" mode="kr-funcsynopsis-mode"/> + </xsl:if> + </p> +</xsl:template> + +<xsl:template match="funcdef" mode="kr-nontabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="kr-nontabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="kr-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="kr-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="kr-nontabular"> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="varargs" mode="kr-nontabular"> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-nontabular"> + <xsl:apply-templates select="parameter" mode="kr-nontabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-nontabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-nontabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-funcsynopsis-mode"> + <xsl:if test="preceding-sibling::paramdef"><xsl:text> +. +</xsl:text></xsl:if> + <code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-funcsynopsis-mode"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="kr-funcsynopsis-mode"> + <code>(</code> + <xsl:apply-templates mode="kr-funcsynopsis-mode"/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: kr, tabular --> + +<xsl:template match="funcprototype" mode="kr-tabular"> + <table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0" style="padding-bottom: 1em"> + <tr> + <td> + <xsl:apply-templates select="funcdef" mode="kr-tabular"/> + </td> + <xsl:apply-templates select="(void|varargs|paramdef)[1]" mode="kr-tabular"/> + </tr> + <xsl:for-each select="(void|varargs|paramdef)[preceding-sibling::*[not(self::funcdef)]]"> + <tr> + <td> </td> + <xsl:apply-templates select="." mode="kr-tabular"/> + </tr> + </xsl:for-each> + </table> + <xsl:if test="paramdef"> + <table border="0" summary="Function argument synopsis" cellspacing="0" cellpadding="0"> + <xsl:if test="following-sibling::funcprototype"> + <xsl:attribute name="style">padding-bottom: 1em</xsl:attribute> + </xsl:if> + <xsl:apply-templates select="paramdef" mode="kr-tabular-funcsynopsis-mode"/> + </table> + </xsl:if> +</xsl:template> + +<xsl:template match="funcdef" mode="kr-tabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="kr-tabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="kr-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="kr-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-tabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="kr-tabular"> + <td> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="varargs" mode="kr-tabular"> + <td> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-tabular"> + <td> + <xsl:apply-templates select="parameter" mode="kr-tabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-tabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-tabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef" mode="kr-tabular-funcsynopsis-mode"> + <xsl:variable name="type"> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:when> + <xsl:when test="normalize-space(parameter/preceding-sibling::node()[not(self::parameter)]) != ''"> + <xsl:copy-of select="parameter/preceding-sibling::node()[not(self::parameter)]"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <tr> + <xsl:choose> + <xsl:when test="$type != '' and funcparams"> + <td> + <code> + <xsl:copy-of select="$type"/> + </code> + <xsl:text> </xsl:text> + </td> + <td> + <code> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type/following-sibling::*" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*" mode="kr-tabular-funcsynopsis-mode"/> + </xsl:otherwise> + </xsl:choose> + </code> + </td> + </xsl:when> + + <xsl:when test="funcparams"> + <td colspan="2"> + <code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </code> + </td> + </xsl:when> + + <xsl:otherwise> + <td> + <code> + <xsl:apply-templates select="parameter/preceding-sibling::node()[not(self::parameter)]" mode="kr-tabular-funcsynopsis-mode"/> + </code> + <xsl:text> </xsl:text> + </td> + <td> + <code> + <xsl:apply-templates select="parameter" mode="kr-tabular"/> + <xsl:apply-templates select="parameter/following-sibling::*[not(self::parameter)]" mode="kr-tabular-funcsynopsis-mode"/> + <xsl:text>;</xsl:text> + </code> + </td> + </xsl:otherwise> + </xsl:choose> + </tr> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="kr-tabular-funcsynopsis-mode"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="kr-tabular-funcsynopsis-mode"> + <code>(</code> + <xsl:apply-templates mode="kr-tabular-funcsynopsis-mode"/> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: ansi, non-tabular --> + +<xsl:template match="funcprototype" mode="ansi-nontabular"> + <p> + <xsl:apply-templates mode="ansi-nontabular"/> + </p> +</xsl:template> + +<xsl:template match="funcdef" mode="ansi-nontabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="ansi-nontabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="ansi-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="ansi-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="ansi-nontabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="ansi-nontabular"> + <code>void)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="varargs" mode="ansi-nontabular"> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> +</xsl:template> + +<xsl:template match="paramdef" mode="ansi-nontabular"> + <xsl:apply-templates mode="ansi-nontabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="ansi-nontabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="ansi-nontabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="ansi-nontabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="ansi-nontabular"> + <code>(</code> + <xsl:apply-templates mode="ansi-nontabular"/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> +<!-- funcprototype: ansi, tabular --> + +<xsl:template match="funcprototype" mode="ansi-tabular"> + <table border="0" summary="Function synopsis" cellspacing="0" cellpadding="0"> + <xsl:if test="following-sibling::funcprototype"> + <xsl:attribute name="style">padding-bottom: 1em</xsl:attribute> + </xsl:if> + <tr> + <td> + <xsl:apply-templates select="funcdef" mode="ansi-tabular"/> + </td> + <xsl:apply-templates select="(void|varargs|paramdef)[1]" mode="ansi-tabular"/> + </tr> + <xsl:for-each select="(void|varargs|paramdef)[preceding-sibling::*[not(self::funcdef)]]"> + <tr> + <td> </td> + <xsl:apply-templates select="." mode="ansi-tabular"/> + </tr> + </xsl:for-each> + </table> +</xsl:template> + +<xsl:template match="funcdef" mode="ansi-tabular"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="ansi-tabular"/> + <xsl:text>(</xsl:text> + </code> +</xsl:template> + +<xsl:template match="funcdef/function" mode="ansi-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <b class="fsfunc"><xsl:apply-templates mode="ansi-nontabular"/></b> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates mode="kr-tabular"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="void" mode="ansi-tabular"> + <td> + <code>void)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="varargs" mode="ansi-tabular"> + <td> + <xsl:text>...</xsl:text> + <code>)</code> + <xsl:text>;</xsl:text> + </td> + <td> </td> +</xsl:template> + +<xsl:template match="paramdef" mode="ansi-tabular"> + <xsl:variable name="type"> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type" mode="ansi-tabular"/> + </xsl:when> + <xsl:when test="normalize-space(parameter/preceding-sibling::node()[not(self::parameter)]) != ''"> + <xsl:copy-of select="parameter/preceding-sibling::node()[not(self::parameter)]"/> + </xsl:when> + </xsl:choose> + </xsl:variable> + + <xsl:choose> + <xsl:when test="$type != '' and funcparams"> + <td> + <xsl:copy-of select="$type"/> + <xsl:text> </xsl:text> + </td> + <td> + <xsl:choose> + <xsl:when test="type"> + <xsl:apply-templates select="type/following-sibling::*" mode="ansi-tabular"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="*" mode="ansi-tabular"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + </xsl:when> + <xsl:otherwise> + <td> + <xsl:apply-templates select="parameter/preceding-sibling::node()[not(self::parameter)]" mode="ansi-tabular"/> + <xsl:text> </xsl:text> + </td> + <td> + <xsl:apply-templates select="parameter" mode="ansi-tabular"/> + <xsl:apply-templates select="parameter/following-sibling::*[not(self::parameter)]" mode="ansi-tabular"/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <code>)</code> + <xsl:text>;</xsl:text> + </xsl:otherwise> + </xsl:choose> + </td> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter" mode="ansi-tabular"> + <xsl:choose> + <xsl:when test="$funcsynopsis.decoration != 0"> + <var class="pdparam"> + <xsl:apply-templates mode="ansi-tabular"/> + </var> + </xsl:when> + <xsl:otherwise> + <code> + <xsl:apply-templates mode="ansi-tabular"/> + </code> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="funcparams" mode="ansi-tabular"> + <code>(</code> + <xsl:apply-templates/> + <code>)</code> +</xsl:template> + +<!-- ====================================================================== --> + +<xsl:variable name="default-classsynopsis-language">java</xsl:variable> + +<xsl:template match="classsynopsis |fieldsynopsis |methodsynopsis |constructorsynopsis |destructorsynopsis"> + <xsl:param name="language"> + <xsl:choose> + <xsl:when test="@language"> + <xsl:value-of select="@language"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$default-classsynopsis-language"/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + + <xsl:choose> + <xsl:when test="$language='java' or $language='Java'"> + <xsl:apply-templates select="." mode="java"/> + </xsl:when> + <xsl:when test="$language='perl' or $language='Perl'"> + <xsl:apply-templates select="." mode="perl"/> + </xsl:when> + <xsl:when test="$language='idl' or $language='IDL'"> + <xsl:apply-templates select="." mode="idl"/> + </xsl:when> + <xsl:when test="$language='cpp' or $language='c++' or $language='C++'"> + <xsl:apply-templates select="." mode="cpp"/> + </xsl:when> + <xsl:otherwise> + <xsl:message> + <xsl:text>Unrecognized language on </xsl:text> + <xsl:value-of select="local-name(.)"/> + <xsl:text>: </xsl:text> + <xsl:value-of select="$language"/> + </xsl:message> + <xsl:apply-templates select="."> + <xsl:with-param name="language" select="$default-classsynopsis-language"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template name="synop-break"> + <xsl:if test="parent::classsynopsis or (following-sibling::fieldsynopsis |following-sibling::methodsynopsis |following-sibling::constructorsynopsis |following-sibling::destructorsynopsis)"> + <xsl:text> +. +</xsl:text> + </xsl:if> +</xsl:template> + + +<!-- ===== Java ======================================================== --> + +<xsl:template match="classsynopsis" mode="java"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="ooclass[1]" mode="java"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text> extends</xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="java"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text>implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="java"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text>throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="java"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="java"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="java"> + <xsl:apply-templates mode="java"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="java"> + <xsl:choose> + <xsl:when test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="java"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="java"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="java"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="java"> + <xsl:param name="indent">0</xsl:param> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>,</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:if test="$indent > 0"> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string"> </xsl:with-param> + <xsl:with-param name="count" select="$indent + 1"/> + </xsl:call-template> + </xsl:if> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="java"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="java"/> + </span> +</xsl:template> + +<xsl:template mode="java" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="decl"> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="java"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="java"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="java"/> + </xsl:variable> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:copy-of select="$decl"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="java"> + <xsl:with-param name="indent" select="string-length($decl)"/> + </xsl:apply-templates> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    throws </xsl:text> + <xsl:apply-templates select="exceptionname" mode="java"/> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="java"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== C++ ========================================================= --> + +<xsl:template match="classsynopsis" mode="cpp"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates select="ooclass[1]" mode="cpp"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>: </xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="cpp"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text> implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="cpp"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text> throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="cpp"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="cpp"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="cpp"> + <xsl:apply-templates mode="cpp"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="cpp"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="cpp"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="cpp"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="cpp"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="cpp"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="cpp"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="cpp"/> + </span> +</xsl:template> + +<xsl:template mode="cpp" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="cpp"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="cpp"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="cpp"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="cpp"/> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    throws </xsl:text> + <xsl:apply-templates select="exceptionname" mode="cpp"/> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="cpp"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== IDL ========================================================= --> + +<xsl:template match="classsynopsis" mode="idl"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>interface </xsl:text> + <xsl:apply-templates select="ooclass[1]" mode="idl"/> + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>: </xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="idl"/> + <xsl:if test="oointerface|ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="oointerface"> + <xsl:text> implements</xsl:text> + <xsl:apply-templates select="oointerface" mode="idl"/> + <xsl:if test="ooexception"> + <xsl:text> +. +</xsl:text> + <xsl:text>    </xsl:text> + </xsl:if> + </xsl:if> + <xsl:if test="ooexception"> + <xsl:text> throws</xsl:text> + <xsl:apply-templates select="ooexception" mode="idl"/> + </xsl:if> + <xsl:text> {</xsl:text> + <xsl:text> +. +</xsl:text> + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="idl"/> + <xsl:text>}</xsl:text> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="idl"> + <xsl:apply-templates mode="idl"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="idl"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="idl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="idl"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="idl"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="idl"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="idl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="idl"/> + </span> +</xsl:template> + +<xsl:template mode="idl" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates select="$start-modifiers" mode="idl"/> + + <!-- type --> + <xsl:if test="local-name($notmod[1]) != 'methodname'"> + <xsl:apply-templates select="$notmod[1]" mode="idl"/> + </xsl:if> + + <xsl:apply-templates select="methodname" mode="idl"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="methodparam" mode="idl"/> + <xsl:text>)</xsl:text> + <xsl:if test="exceptionname"> + <xsl:text> +. +</xsl:text> + <xsl:text>    raises(</xsl:text> + <xsl:apply-templates select="exceptionname" mode="idl"/> + <xsl:text>)</xsl:text> + </xsl:if> + <xsl:if test="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"> + <xsl:text> </xsl:text> + <xsl:apply-templates select="$end-modifiers" mode="idl"/> + </xsl:if> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ===== Perl ======================================================== --> + +<xsl:template match="classsynopsis" mode="perl"> + <xsl:text>.sp +</xsl:text><xsl:text>.nf +</xsl:text><pre> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>package </xsl:text> + <xsl:apply-templates select="ooclass[1]" mode="perl"/> + <xsl:text>;</xsl:text> + <xsl:text> +. +</xsl:text> + + <xsl:if test="ooclass[preceding-sibling::*]"> + <xsl:text>@ISA = (</xsl:text> + <xsl:apply-templates select="ooclass[preceding-sibling::*]" mode="perl"/> + <xsl:text>);</xsl:text> + <xsl:text> +. +</xsl:text> + </xsl:if> + + <xsl:apply-templates select="constructorsynopsis |destructorsynopsis |fieldsynopsis |methodsynopsis |classsynopsisinfo" mode="perl"/> + </pre><xsl:text/><xsl:text>.fi +</xsl:text> +</xsl:template> + +<xsl:template match="classsynopsisinfo" mode="perl"> + <xsl:apply-templates mode="perl"/> +</xsl:template> + +<xsl:template match="ooclass|oointerface|ooexception" mode="perl"> + <xsl:if test="preceding-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="modifier|package" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:if test="following-sibling::*"> + <xsl:text> </xsl:text> + </xsl:if> + </span> +</xsl:template> + +<xsl:template match="classname" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'classname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="interfacename" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'interfacename'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="exceptionname" mode="perl"> + <xsl:if test="local-name(preceding-sibling::*[1]) = 'exceptionname'"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="fieldsynopsis" mode="perl"> + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:if test="parent::classsynopsis"> + <xsl:text>  </xsl:text> + </xsl:if> + <xsl:apply-templates mode="perl"/> + <xsl:text>;</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<xsl:template match="type" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="varname" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + <xsl:text> </xsl:text> + </span> +</xsl:template> + +<xsl:template match="initializer" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>= </xsl:text> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="void" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>void </xsl:text> + </span> +</xsl:template> + +<xsl:template match="methodname" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="methodparam" mode="perl"> + <xsl:if test="preceding-sibling::methodparam"> + <xsl:text>, </xsl:text> + </xsl:if> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template match="parameter" mode="perl"> + <span> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:apply-templates mode="perl"/> + </span> +</xsl:template> + +<xsl:template mode="perl" match="constructorsynopsis|destructorsynopsis|methodsynopsis"> + <xsl:variable name="start-modifiers" select="modifier[following-sibling::*[local-name(.) != 'modifier']]"/> + <xsl:variable name="notmod" select="*[local-name(.) != 'modifier']"/> + <xsl:variable name="end-modifiers" select="modifier[preceding-sibling::*[local-name(.) != 'modifier']]"/> + + <code> + <xsl:apply-templates select="." mode="class.attribute"/> + <xsl:text>sub </xsl:text> + + <xsl:apply-templates select="methodname" mode="perl"/> + <xsl:text> { ... };</xsl:text> + </code> + <xsl:call-template name="synop-break"/> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * DocBook 5 allows linking elements (link, olink, and xref) --> +<!-- * within the OO *synopsis elements (classsynopsis, fieldsynopsis, --> +<!-- * methodsynopsis, constructorsynopsis, destructorsynopsis) and --> +<!-- * their children. So we need to have mode="java|cpp|idl|perl" --> +<!-- * per-mode matches for those linking elements in order for them --> +<!-- * to be processed as expected. --> + +<xsl:template match="link|olink|xref" mode="java"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="cpp"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="idl"> + <xsl:apply-templates select="."/> +</xsl:template> + +<xsl:template match="link|olink|xref" mode="perl"> + <xsl:apply-templates select="."/> +</xsl:template> + +</xsl:stylesheet> + diff --git a/docs/xsl-generic/manpages/info.xsl b/docs/xsl-generic/manpages/info.xsl new file mode 100644 index 00000000..36dded00 --- /dev/null +++ b/docs/xsl-generic/manpages/info.xsl @@ -0,0 +1,630 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:date="http://exslt.org/dates-and-times" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="date exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: info.xsl 7087 2007-07-19 07:20:38Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <xsl:variable name="blurb-indent"> + <xsl:choose> + <xsl:when test="not($man.indent.blurbs = 0)"> + <xsl:value-of select="$man.indent.width"/> + </xsl:when> + <xsl:when test="not($man.indent.refsect = 0)"> + <!-- * "zq" is the name of a register we set for --> + <!-- * preserving the original default indent value --> + <!-- * when $man.indent.refsect is non-zero; --> + <!-- * "u" is a roff unit specifier --> + <xsl:text>\n(zqu</xsl:text> + </xsl:when> + <xsl:otherwise/> <!-- * otherwise, just leave it empty --> + </xsl:choose> + </xsl:variable> + + <!-- ================================================================== --> + <!-- * About the $info param used in this stylesheet --> + <!-- * --> + <!-- * The $info param is a "master info" node set that contains --> + <!-- * the entire contents of the *info child of the current --> + <!-- * Refentry, plus the entire contents of the *info children of --> + <!-- * all ancestors of the current Refentry, in document order. --> + <!-- * --> + <!-- * We try to find a "best match" for selecting content from --> + <!-- * $infor; we look through it in reverse document order until we --> + <!-- * can find something usable. --> + <!-- * --> + <!-- * Specifically what the basic metadata-gathering XPath expression --> + <!-- * in this stylesheet does is: --> + <!-- * --> + <!-- * 1. Look through the entire "master info" node set.--> + <!-- * 2. Get the last node in the set that contains, for --> + <!-- * example, an Author element. That amounts to being the --> + <!-- * closest *info node to the Refentry - either its *info --> + <!-- * child, or the *info node of its closest ancestor that --> + <!-- * contains an Author. --> + + <!-- ================================================================== --> + <!-- * Get user "refentry metadata" preferences --> + <!-- ================================================================== --> + <!-- * The DocBook XSL stylesheets include several user-configurable --> + <!-- * global stylesheet parameters for controlling refentry metadata --> + <!-- * gathering. Those parameters are not read directly by the other --> + <!-- * refentry metadata-gathering templates. Instead, they are read --> + <!-- * only by the get.refentry.metadata.prefs template, which --> + <!-- * assembles them into a structure that is then passed to the --> + <!-- * other refentry metadata-gathering template. --> + + <xsl:variable name="get.refentry.metadata.prefs"> + <!-- * get.refentry.metadata.prefs is in common/refentry.xsl --> + <xsl:call-template name="get.refentry.metadata.prefs"/> + </xsl:variable> + + <xsl:variable name="refentry.metadata.prefs" + select="exsl:node-set($get.refentry.metadata.prefs)"/> + + <!-- * ============================================================== --> + <!-- * Get content for Author metadata field. --> + <!-- * ============================================================== --> + + <!-- * The make.roff.metatada.author template and metadata.author --> + <!-- * mode are used only for populating the Author field in the --> + <!-- * metadata "top comment" we embed in roff source of each page. --> + + <xsl:template name="make.roff.metadata.author"> + <xsl:param name="info"/> + <xsl:choose> + <xsl:when test="$info//author"> + <xsl:apply-templates + select="(($info[//author])[last()]//author)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//corpauthor"> + <xsl:apply-templates + select="(($info[//corpauthor])[last()]//corpauthor)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//editor"> + <xsl:apply-templates + select="(($info[//editor])[last()]//editor)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//corpcredit"> + <xsl:apply-templates + select="(($info[//corpcredit])[last()]//corpcredit)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//othercredit"> + <xsl:apply-templates + select="(($info[//othercredit])[last()]//othercredit)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//collab"> + <xsl:apply-templates + select="(($info[//collab])[last()]//collab)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//orgname"> + <xsl:apply-templates + select="(($info[//orgname])[last()]//orgname)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:when test="$info//publishername"> + <xsl:apply-templates + select="(($info[//publishername])[last()]//publishername)[1]" + mode="metadata.author"/> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no author info found --> + </xsl:choose> + </xsl:template> + + <xsl:template match="author|editor|othercredit|collab" mode="metadata.author"> + <xsl:choose> + <xsl:when test="collabname"> + <!-- * If this node is a Collab, then it should have a --> + <!-- * Collabname child, so get that. --> + <xsl:variable name="contents"> + <xsl:apply-templates select="collabname"/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, this node is not a Collab, but instead --> + <!-- * an author|editor|othercredit, which must have a name --> + <!-- * of some kind; so get that name --> + <xsl:call-template name="person.name.normalized"/> + </xsl:otherwise> + </xsl:choose> + <xsl:if test=".//email|address/otheraddr/ulink"> + <xsl:text> </xsl:text> + <!-- * For each attribution found, use only the first e-mail --> + <!-- * address or ulink value found --> + <xsl:apply-templates select="(.//email|address/otheraddr/ulink)[1]" + mode="metadata.author"/> + </xsl:if> + </xsl:template> + + <xsl:template match="email|address/otheraddr/ulink" mode="metadata.author"> + <xsl:text><</xsl:text> + <xsl:choose> + <xsl:when test="self::email"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:when test="self::ulink"> + <xsl:variable name="contents"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + </xsl:choose> + <xsl:text>></xsl:text> + </xsl:template> + + <xsl:template match="corpauthor|corpcredit|orgname|publishername" mode="metadata.author"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Assemble the AUTHOR/AUTHORS section --> + <!-- * ============================================================== --> + + <xsl:template name="author.section"> + <xsl:param name="info"/> + <!-- * The $info param is a "master info" node set that contains --> + <!-- * the entires contents of the *info child of the current --> + <!-- * Refentry, plus the entire contents of the *info children of --> + <!-- * all ancestors of the current Refentry, in document order. --> + <xsl:choose> + <xsl:when test="$info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit|$info/orgname| + $info/publishername|$info/publisher"> + <xsl:variable name="authorcount"> + <xsl:value-of + select="count( + $info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit)"> + </xsl:value-of> + </xsl:variable> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="make.authorsecttitle"> + <xsl:with-param name="authorcount" select="$authorcount"/> + </xsl:call-template> + <!-- * Now output all the actual author, editor, etc. content --> + <xsl:for-each + select="$info//author|$info//editor|$info//collab| + $info//corpauthor|$info//corpcredit| + $info//othercredit|$info/orgname| + $info/publishername|$info/publisher"> + <xsl:apply-templates select="." mode="authorsect"/> + </xsl:for-each> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no author info found --> + </xsl:choose> + </xsl:template> + + <xsl:template name="make.authorsecttitle"> + <!-- * If we have exactly one attributable person/entity, then output --> + <!-- * localized gentext for 'Author'; otherwise, output 'Authors'. --> + <xsl:param name="authorcount"/> + <xsl:param name="authorsecttitle"> + <xsl:choose> + <xsl:when test="$authorcount = 1"> + <xsl:text>Author</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>Authors</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="$authorsecttitle"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + </xsl:template> + + <xsl:template match="author|editor|othercredit" mode="authorsect"> + <xsl:variable name="person-name"> + <xsl:call-template name="person.name.normalized"/> + </xsl:variable> + <!-- * If we have a person-name or email or ulink content, then --> + <!-- * output name and email or ulink content on the same line --> + <xsl:choose> + <xsl:when test="not($person-name = '') or .//email or address/otheraddr/ulink"> + <xsl:text>.PP </xsl:text> + <!-- * Display person name in bold --> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($person-name)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>.br </xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * Display affiliation(s) on separate lines --> + <xsl:apply-templates select="affiliation" mode="authorsect"/> + <!-- * Display direct-child addresses on separate lines --> + <xsl:apply-templates select="address" mode="authorsect"/> + <!-- * Call template for handling various attribution possibilities --> + <xsl:call-template name="attribution"/> + </xsl:template> + + <xsl:template match="collab" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="collabname"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <xsl:text> </xsl:text> + <!-- * Display affilition(s) on separate lines --> + <xsl:apply-templates select="affiliation" mode="authorsect"/> + </xsl:template> + + <xsl:template match="corpauthor|corpcredit|orgname|publishername" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:if test="self::publishername"> + <!-- * Display localized "Publisher" gentext --> + <xsl:call-template name="publisher.attribution"/> + </xsl:if> + </xsl:template> + + <xsl:template match="publisher" mode="authorsect"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="publishername"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <!-- * Display e-mail address(es) and ulink(s) on same line as name --> + <xsl:apply-templates select=".//email|address/otheraddr/ulink" mode="authorsect"/> + <!-- * Display addresses on separate lines --> + <xsl:apply-templates select="address" mode="authorsect"/> + <!-- * Display localized "Publisher" literal --> + <xsl:call-template name="publisher.attribution"/> + </xsl:template> + + <xsl:template name="publisher.attribution"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Publisher'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:template> + + <xsl:template match="email|address/otheraddr/ulink" mode="authorsect"> + <xsl:choose> + <xsl:when test="preceding-sibling::*[descendant-or-self::email] + or preceding-sibling::address/otheraddr/ulink + or ancestor::address[preceding-sibling::*[descendant-or-self::email]] + or ancestor::address[preceding-sibling::address/otheraddr/ulink]"> + <!-- * This is not the first instance, so do nothing. --> + </xsl:when> + <xsl:otherwise> + <!-- * This is first instances of an e-mail address or ulink, --> + <!-- * so put a space before it. --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * Note that the reason for the \& character after the opening --> + <!-- * angle bracket and before the closing angle bracket is to --> + <!-- * prevent groff from inserting a linebreak at those points and --> + <!-- * outputting a hyphen character where the break occurs --> + <xsl:text><\&</xsl:text> + <xsl:choose> + <xsl:when test="self::email"> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + <xsl:when test="self::ulink"> + <xsl:variable name="contents"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + </xsl:when> + </xsl:choose> + <xsl:text>\&></xsl:text> + <xsl:choose> + <xsl:when test="not(following-sibling::*[descendant-or-self::email] + or following-sibling::address/otheraddr/ulink + or ancestor::address[following-sibling::*[descendant-or-self::email]] + or ancestor::address[following-sibling::address/otheraddr/ulink])"> + <!-- * This is the final instance, so do nothing. --> + </xsl:when> + <xsl:otherwise> + <!-- * Separate multiple e-mail addresses or ulinks with a comma --> + <xsl:text>, </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="affiliation" mode="authorsect"> + <!-- * Get the string value of the contents of this Affiliation. If the --> + <!-- * affiliation only contains an Address child whose only content is --> + <!-- * an email address or ulink, then these contents will end up empty. --> + <xsl:variable name="contents"> + <xsl:apply-templates mode="authorsect"/> + </xsl:variable> + <!-- * If contents are actually empty except for an email address --> + <!-- * or ulink, then output nothing. --> + <xsl:if test="$contents != ''"> + <xsl:text>.br </xsl:text> + <xsl:for-each select="shortaffil|jobtitle|orgname|orgdiv|address"> + <!-- * only display output of nodes other than email or ulink --> + <xsl:apply-templates select="node()[not(self::email) and not(self::otheraddr/ulink)]"/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * only add comma if the node has a child node other than --> + <!-- * an email address or ulink --> + <xsl:if test="child::node()[not(self::email) and not(self::otheraddr/ulink)]"> + <xsl:text>, </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * put a line break after every Affiliation instance except --> + <!-- * the last one in the set --> + <xsl:text>.br </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:template> + + <xsl:template match="address" mode="authorsect"> + <xsl:variable name="contents" + select="normalize-space(node()[not(self::email) + and not(self::otheraddr/ulink)])"/> + <!-- * If this contents of this Address do not contain anything except --> + <!-- * an email address or ulink, then output nothing. --> + <xsl:if test="$contents != ''"> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + <!--* Skip email and ulink descendants of Address (rendered elsewhere) --> + <xsl:apply-templates select="node()[not(self::email) and not(self::otheraddr/ulink)]"/> + </xsl:if> + </xsl:template> + + <xsl:template name="attribution"> + <!-- * Determine appropriate attribution for a particular person's role. --> + <xsl:choose> + <!-- * if we have a *blurb or contrib, just use that --> + <xsl:when test="contrib|personblurb|authorblurb"> + <xsl:apply-templates select="contrib|personblurb|authorblurb" mode="authorsect"/> + <xsl:text> </xsl:text> + </xsl:when> + <!-- * If we have no *blurb or contrib, but this is an Author or --> + <!-- * Editor, then render the corresponding localized gentext --> + <xsl:when test="self::author"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Author'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <xsl:when test="self::editor"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'Editor'"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <!-- * If we have no *blurb or contrib, but this is an Othercredit, --> + <!-- * check value of Class attribute and use corresponding gentext. --> + <xsl:when test="self::othercredit"> + <xsl:choose> + <xsl:when test="@class and @class != 'other'"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="@class"/> + </xsl:call-template> + <xsl:text>. </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * We have an Othercredit, but not usable value for the Class --> + <!-- * attribute, so nothing to show, do nothing --> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <!-- * We have no *blurb or contrib or anything else we can use to --> + <!-- * display appropriate attribution for this person, so do nothing --> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="personblurb|authorblurb" mode="authorsect"> + <xsl:call-template name="mark.up.blurb.or.contrib"/> + <!-- * yeah, it's possible for a *blurb to have a "title" --> + <xsl:apply-templates select="title"/> + <xsl:apply-templates select="*[not(self::title)]"/> + </xsl:template> + + <xsl:template match="personblurb/title|authorblurb/title"> + <!-- * always render period after title --> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text>.</xsl:text> + <!-- * render space after Title+period if the title is followed --> + <!-- * by something element content --> + <xsl:if test="following-sibling::*[name() != '']"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="contrib" mode="authorsect"> + <xsl:call-template name="mark.up.blurb.or.contrib"/> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template name="mark.up.blurb.or.contrib"> + <xsl:choose> + <!-- * If this *blurb has a sibling "name" element of some kind, then --> + <!-- * we are already outputting the name content, and we need to --> + <!-- * indent the *blurb content after that. --> + <xsl:when + test="../personname|../surname|../firstname + |../othername|../lineage|../honorific + |../affiliation|../email|../address"> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($blurb-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$blurb-indent"/> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, we have no "name" content, so don't indent; --> + <!-- * instead, decide if we need a .PP or just a .br --> + <xsl:choose> + <xsl:when test="not(preceding-sibling::*)"> + <!-- * if this *blurb or contrib has no preceding --> + <!-- * siblings, then we need to start a new paragraph --> + <xsl:text>.PP</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise, this has no preceding siblings, so --> + <!-- * just put a linebreak --> + <xsl:text>.br</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:otherwise> + </xsl:choose> + <xsl:text> </xsl:text> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Assemble the COPYRIGHT section --> + <!-- * ============================================================== --> + <!-- * The COPYRIGHT section is output only if a copyright or --> + <!-- * legalnotice is found. It contains the copyright contents --> + <!-- * followed by the legalnotice contents. --> + <xsl:template name="copyright.section"> + <xsl:param name="info"/> + <xsl:choose> + <xsl:when test="$info//copyright|$info//legalnotice"> + <xsl:text>.SH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key">Copyright</xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" </xsl:text> + <!-- * the copyright mode="titlepage.mode" template is --> + <!-- * imported from the HTML stylesheets --> + <xsl:for-each select=" + (($info[//copyright])[last()]//copyright) + | (($info[//legalnotice])[last()]//legalnotice)"> + <xsl:choose> + <xsl:when test="local-name(.) = 'copyright'"> + <xsl:variable name="contents"> + <xsl:apply-templates select="." mode="titlepage.mode"/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="titlepage.mode"/> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:when> + <xsl:otherwise/> <!-- * do nothing, no copyright or legalnotice found --> + </xsl:choose> + </xsl:template> + + <xsl:template match="legalnotice"> + <xsl:apply-templates/> + </xsl:template> + + <!-- * ============================================================== --> + + <!-- * suppress refmeta and all *info (we grab what we need from them --> + <!-- * elsewhere) --> + + <xsl:template match="refmeta"/> + + <xsl:template match="info|refentryinfo|referenceinfo|refsynopsisdivinfo + |refsectioninfo|refsect1info|refsect2info|refsect3info + |setinfo|bookinfo|articleinfo|chapterinfo|sectioninfo + |sect1info|sect2info|sect3info|sect4info|sect5info + |partinfo|prefaceinfo|appendixinfo|docinfo"/> + + <!-- ============================================================== --> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/inline.xsl b/docs/xsl-generic/manpages/inline.xsl new file mode 100644 index 00000000..a88369c8 --- /dev/null +++ b/docs/xsl-generic/manpages/inline.xsl @@ -0,0 +1,190 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + version='1.0'> + +<!-- ******************************************************************** + $Id: inline.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<xsl:template match="replaceable|varname"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="option|userinput|envar|errorcode|constant|markup"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="classname"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="command"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="type[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="function[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="parameter[not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)]"> + <xsl:if test="$man.hyphenate.computer.inlines = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="filename"> + <!-- * add hyphenation suppression in Filename output only if --> + <!-- * break.after.slash is also non-zero --> + <xsl:if test="$man.hyphenate.filenames = 0 and + $man.break.after.slash = 0"> + <xsl:call-template name="suppress.hyphenation"/> + </xsl:if> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="emphasis"> + <xsl:choose> + <xsl:when test=" + @role = 'bold' or + @role = 'strong' or + @remap = 'B'"> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="optional"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + <xsl:apply-templates/> + <xsl:value-of select="$arg.choice.opt.close.str"/> +</xsl:template> + +<xsl:template name="do-citerefentry"> + <xsl:param name="refentrytitle" select="''"/> + <xsl:param name="manvolnum" select="''"/> + <xsl:variable name="title"> + <xsl:value-of select="$refentrytitle"/> + </xsl:variable> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="exsl:node-set($title)"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text>(</xsl:text> + <xsl:value-of select="$manvolnum"/> + <xsl:text>)</xsl:text> +</xsl:template> + +<xsl:template match="citerefentry"> + <xsl:call-template name="do-citerefentry"> + <xsl:with-param name="refentrytitle" select="refentrytitle"/> + <xsl:with-param name="manvolnum" select="manvolnum"/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="trademark|productname"> + <xsl:apply-templates/> + <xsl:choose> + <!-- * Just use true Unicode chars for copyright, trademark, etc., --> + <!-- * symbols (by default, we later automatically translate them --> + <!-- * with the apply-string-subst-map template, or with the --> + <!-- * default character map, if man.charmap.enabled is true). --> + <xsl:when test="@class = 'copyright'"> + <xsl:text>©</xsl:text> + </xsl:when> + <xsl:when test="@class = 'registered'"> + <xsl:text>®</xsl:text> + </xsl:when> + <xsl:when test="@class = 'service'"> + <xsl:text>℠</xsl:text> + </xsl:when> + <xsl:when test="@class = 'trade'"> + <xsl:text>™</xsl:text> + </xsl:when> + <!-- * for Trademark element, render a trademark symbol by default --> + <!-- * even if no "class" value is specified --> + <xsl:when test="self::trademark" > + <xsl:text>™</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * otherwise we have a Productname with no value for the --> + <!-- * "class" attribute, so don't render any symbol by default --> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<!-- * span seems to sneak through into output sometimes, possibly due --> +<!-- * to failed Olink processing; so we need to catch it --> +<xsl:template match="span"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="inlinemediaobject"> + <xsl:apply-templates/> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/lists.xsl b/docs/xsl-generic/manpages/lists.xsl new file mode 100644 index 00000000..4c18c80c --- /dev/null +++ b/docs/xsl-generic/manpages/lists.xsl @@ -0,0 +1,368 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: lists.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:variable name="list-indent"> + <xsl:choose> + <xsl:when test="not($man.indent.lists = 0)"> + <xsl:value-of select="$man.indent.width"/> + </xsl:when> + <xsl:when test="not($man.indent.refsect = 0)"> + <!-- * "zq" is the name of a register we set for --> + <!-- * preserving the original default indent value --> + <!-- * when $man.indent.refsect is non-zero; --> + <!-- * "u" is a roff unit specifier --> + <xsl:text>\n(zqu</xsl:text> + </xsl:when> + <xsl:otherwise/> <!-- * otherwise, just leave it empty --> + </xsl:choose> +</xsl:variable> + +<!-- ================================================================== --> + +<xsl:template match="para[ancestor::listitem or ancestor::step or ancestor::glossdef]| + simpara[ancestor::listitem or ancestor::step or ancestor::glossdef]| + remark[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:call-template name="mixed-block"/> + <xsl:text> </xsl:text> + <xsl:if test="following-sibling::*[1][ + self::para or + self::simpara or + self::remark + ]"> + <!-- * Make sure multiple paragraphs within a list item don't --> + <!-- * merge together. --> + <xsl:text>.sp </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="variablelist|glosslist"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="varlistentry|glossentry"> + <xsl:text>.PP </xsl:text> + <xsl:for-each select="term|glossterm"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * if we have multiple terms in the same varlistentry, generate --> + <!-- * a separator (", " by default) and/or an additional line --> + <!-- * break after each one except the last --> + <!-- * --> + <!-- * note that it is not valid to have multiple glossterms --> + <!-- * within a glossentry, so this logic never gets exercised --> + <!-- * for glossterms (every glossterm is always the last in --> + <!-- * its parent glossentry) --> + <xsl:value-of select="$variablelist.term.separator"/> + <xsl:if test="not($variablelist.term.break.after = '0')"> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="varlistentry/term"/> +<xsl:template match="glossentry/glossterm"/> + +<xsl:template match="variablelist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + glosslist[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:apply-templates/> + <xsl:if test="following-sibling::node() or + parent::para[following-sibling::node()] or + parent::simpara[following-sibling::node()] or + parent::remark[following-sibling::node()]"> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="varlistentry/listitem|glossentry/glossdef"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="itemizedlist/listitem"> + <!-- * We output a real bullet here (rather than, "\(bu", --> + <!-- * the roff bullet) because, when we do character-map --> + <!-- * processing before final output, the character-map will --> + <!-- * handle conversion of the • to "\(bu" for us --> + <xsl:text> </xsl:text> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:text>\h'-</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text>'</xsl:text> + <xsl:text>•</xsl:text> + <xsl:text>\h'+</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent - 1"/> + <xsl:text>'</xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> +</xsl:template> + +<xsl:template match="orderedlist/listitem|procedure/step"> + <xsl:text> </xsl:text> + <xsl:text>.sp</xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:text>\h'-</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text>'</xsl:text> + <xsl:if test="count(preceding-sibling::listitem) < 9"> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:number format="1."/> + <xsl:text>\h'+</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text>0</xsl:text> + <xsl:value-of select="$list-indent - 2"/> + <xsl:text>'</xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="itemizedlist|orderedlist|procedure"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <!-- * DocBook allows just about any block content to appear in --> + <!-- * lists before the actual list items, so we need to get that --> + <!-- * content (if any) before getting the list items --> + <xsl:apply-templates + select="*[not(self::listitem) and not(self::title)]"/> + <xsl:apply-templates select="listitem"/> + <!-- * If this list is a child of para and has content following --> + <!-- * it, within the same para, then add a blank line and move --> + <!-- * the left margin back to where it was --> + <xsl:if test="parent::para and following-sibling::node()"> + <xsl:text>.sp </xsl:text> + <xsl:text>.RE </xsl:text> + </xsl:if> +</xsl:template> + +<xsl:template match="itemizedlist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + orderedlist[ancestor::listitem or ancestor::step or ancestor::glossdef]| + procedure[ancestor::listitem or ancestor::step or ancestor::glossdef]"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:apply-templates/> + <xsl:if test="following-sibling::node() or + parent::para[following-sibling::node()] or + parent::simpara[following-sibling::node()] or + parent::remark[following-sibling::node()]"> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ================================================================== --> + +<!-- * for simplelist type="inline", render it as a comma-separated list --> +<xsl:template match="simplelist[@type='inline']"> + + <!-- * if dbchoice PI exists, use that to determine the choice separator --> + <!-- * (that is, equivalent of "and" or "or" in current locale), or literal --> + <!-- * value of "choice" otherwise --> + <xsl:variable name="localized-choice-separator"> + <xsl:choose> + <xsl:when test="processing-instruction('dbchoice')"> + <xsl:call-template name="select.choice.separator"/> + </xsl:when> + <xsl:otherwise> + <!-- * empty --> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:for-each select="member"> + <xsl:apply-templates/> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <xsl:text>, </xsl:text> + <xsl:if test="position() = last() - 1"> + <xsl:if test="$localized-choice-separator != ''"> + <xsl:value-of select="$localized-choice-separator"/> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:text> </xsl:text> +</xsl:template> + +<!-- * if simplelist type is not inline, render it as a one-column vertical --> +<!-- * list (ignoring the values of the type and columns attributes) --> +<xsl:template match="simplelist"> + <xsl:for-each select="member"> + <xsl:text>.IP ""</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + </xsl:for-each> +</xsl:template> + +<!-- ================================================================== --> + +<!-- * We output Segmentedlist as a table, using tbl(1) markup. There --> +<!-- * is no option for outputting it in manpages in "list" form. --> +<xsl:template match="segmentedlist"> + <xsl:if test="title"> + <xsl:text>.PP </xsl:text> + <xsl:call-template name="bold"> + <xsl:with-param name="node" select="title"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:text>.\" line length increase to cope w/ tbl weirdness </xsl:text> + <xsl:text>.ll +(\n(LLu * 62u / 100u) </xsl:text> + <!-- * .TS = "Table Start" --> + <xsl:text>.TS </xsl:text> + <!-- * first output the table "format" spec, which tells tbl(1) how --> + <!-- * how to format each row and column. --> + <xsl:for-each select=".//segtitle"> + <!-- * l = "left", which hard-codes left-alignment for tabular --> + <!-- * output of all segmentedlist content --> + <xsl:text>l</xsl:text> + </xsl:for-each> + <!-- * last line of table format section must end with a dot --> + <xsl:text>. </xsl:text> + <!-- * optionally suppress output of segtitle --> + <xsl:choose> + <xsl:when test="$man.segtitle.suppress != 0"> + <!-- * non-zero = "suppress", so do nothing --> + </xsl:when> + <xsl:otherwise> + <!-- * "0" = "do not suppress", so output the segtitle(s) --> + <xsl:apply-templates select=".//segtitle" mode="table-title"/> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:apply-templates/> + <!-- * .TE = "Table End" --> + <xsl:text>.TE </xsl:text> + <xsl:text>.\" line length decrease back to previous value </xsl:text> + <xsl:text>.ll -(\n(LLu * 62u / 100u) </xsl:text> + <!-- * put a blank line of space below the table --> + <xsl:text>.sp </xsl:text> +</xsl:template> + +<xsl:template match="segmentedlist/segtitle" mode="table-title"> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="."/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * tbl(1) treats tab characters as delimiters between --> + <!-- * cells; so we need to output a tab after each except --> + <!-- * segtitle except the last one --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="segmentedlist/seglistitem"> + <xsl:apply-templates/> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="segmentedlist/seglistitem/seg"> + <!-- * the “T{" and “T}” stuff are delimiters to tell tbl(1) that --> + <!-- * the delimited contents are "text blocks" that groff(1) --> + <!-- * needs to process --> + <xsl:text>T{ </xsl:text> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($contents)"/> + <xsl:text> T}</xsl:text> + <xsl:choose> + <xsl:when test="position() = last()"/> <!-- do nothing --> + <xsl:otherwise> + <!-- * tbl(1) treats tab characters as delimiters between --> + <!-- * cells; so we need to output a tab after each except --> + <!-- * segtitle except the last one --> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/other.xsl b/docs/xsl-generic/manpages/other.xsl new file mode 100644 index 00000000..3a3afee9 --- /dev/null +++ b/docs/xsl-generic/manpages/other.xsl @@ -0,0 +1,674 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:ng="http://docbook.org/docbook-ng" + xmlns:db="http://docbook.org/ns/docbook" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: other.xsl 6863 2007-06-23 09:08:06Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- * This file contains named templates related to things other than --> +<!-- * just assembling the actual text of the main text flow of each man --> +<!-- * page. This "other" stuff currently amounts to these steps: --> +<!-- * --> +<!-- * - get contents of the "map" used to convert special characters --> +<!-- * - output boilerplate messages --> +<!-- * - escape backslash, dot, dash, and apostrophe characters --> +<!-- * - convert non-breaking spaces --> +<!-- * - add a comment to top part of roff source of each page --> +<!-- * - make a .TH title line (for controlling page header/footer) --> +<!-- * - set hyphenation, alignment, indent & line-breaking defaults --> +<!-- * - "prepare" the complete man page contents for final output --> +<!-- * - write the actual man file to the filesystem --> +<!-- * - write any "stub" pages to the filesystem --> +<!-- * --> +<!-- * The templates in this file are actually called only once per --> +<!-- * each Refentry; they are just in a separate file for the purpose --> +<!-- * of keeping things modular. --> + +<!-- ==================================================================== --> + +<xsl:preserve-space elements="*"/> + +<xsl:strip-space elements=" +abstract affiliation anchor answer appendix area areaset areaspec +artheader article audiodata audioobject author authorblurb authorgroup +beginpage bibliodiv biblioentry bibliography biblioset blockquote book +bookbiblio bookinfo callout calloutlist caption caution chapter +citerefentry cmdsynopsis co collab colophon colspec confgroup +copyright dedication docinfo editor entrytbl epigraph equation +example figure footnote footnoteref formalpara funcprototype +funcsynopsis glossary glossdef glossdiv glossentry glosslist graphicco +group highlights imagedata imageobject imageobjectco important index +indexdiv indexentry indexterm informalequation informalexample +informalfigure informaltable inlineequation inlinemediaobject +itemizedlist itermset keycombo keywordset legalnotice listitem lot +mediaobject mediaobjectco menuchoice msg msgentry msgexplan msginfo +msgmain msgrel msgset msgsub msgtext note objectinfo +orderedlist othercredit part partintro preface printhistory procedure +programlistingco publisher qandadiv qandaentry qandaset question +refentry reference refmeta refnamediv refsection refsect1 refsect1info refsect2 +refsect2info refsect3 refsect3info refsynopsisdiv refsynopsisdivinfo +revhistory revision row sbr screenco screenshot sect1 sect1info sect2 +sect2info sect3 sect3info sect4 sect4info sect5 sect5info section +sectioninfo seglistitem segmentedlist seriesinfo set setindex setinfo +shortcut sidebar simplelist simplesect spanspec step subject +subjectset substeps synopfragment table tbody textobject tfoot tgroup +thead tip toc tocchap toclevel1 toclevel2 toclevel3 toclevel4 +toclevel5 tocpart varargs variablelist varlistentry videodata +videoobject void warning subjectset + +classsynopsis +constructorsynopsis +destructorsynopsis +fieldsynopsis +methodparam +methodsynopsis +ooclass +ooexception +oointerface +simplemsgentry +manvolnum + +db:abstract db:affiliation db:anchor db:answer db:appendix db:area db:areaset db:areaspec +db:artheader db:article db:audiodata db:audioobject db:author db:authorblurb db:authorgroup +db:beginpage db:bibliodiv db:biblioentry db:bibliography db:biblioset db:blockquote db:book +db:bookbiblio db:bookinfo db:callout db:calloutlist db:caption db:caution db:chapter +db:citerefentry db:cmdsynopsis db:co db:collab db:colophon db:colspec db:confgroup +db:copyright db:dedication db:docinfo db:editor db:entrytbl db:epigraph db:equation +db:example db:figure db:footnote db:footnoteref db:formalpara db:funcprototype +db:funcsynopsis db:glossary db:glossdef db:glossdiv db:glossentry db:glosslist db:graphicco +db:group db:highlights db:imagedata db:imageobject db:imageobjectco db:important db:index +db:indexdiv db:indexentry db:indexterm db:informalequation db:informalexample +db:informalfigure db:informaltable db:inlineequation db:inlinemediaobject +db:itemizedlist db:itermset db:keycombo db:keywordset db:legalnotice db:listitem db:lot +db:mediaobject db:mediaobjectco db:menuchoice db:msg db:msgentry db:msgexplan db:msginfo +db:msgmain db:msgrel db:msgset db:msgsub db:msgtext db:note db:objectinfo +db:orderedlist db:othercredit db:part db:partintro db:preface db:printhistory db:procedure +db:programlistingco db:publisher db:qandadiv db:qandaentry db:qandaset db:question +db:refentry db:reference db:refmeta db:refnamediv db:refsection db:refsect1 db:refsect1info +db:refsect2 +db:refsect2info db:refsect3 db:refsect3info db:refsynopsisdiv db:refsynopsisdivinfo +db:revhistory db:revision db:row db:sbr db:screenco db:screenshot db:sect1 db:sect1info db:sect2 +db:sect2info db:sect3 db:sect3info db:sect4 db:sect4info db:sect5 db:sect5info db:section +db:sectioninfo db:seglistitem db:segmentedlist db:seriesinfo db:set db:setindex db:setinfo +db:shortcut db:sidebar db:simplelist db:simplesect db:spanspec db:step db:subject +db:subjectset db:substeps db:synopfragment db:table db:tbody db:textobject db:tfoot db:tgroup +db:thead db:tip db:toc db:tocchap db:toclevel1 db:toclevel2 db:toclevel3 db:toclevel4 +db:toclevel5 db:tocpart db:varargs db:variablelist db:varlistentry db:videodata +db:videoobject db:void db:warning db:subjectset + +db:classsynopsis +db:constructorsynopsis +db:destructorsynopsis +db:fieldsynopsis +db:methodparam +db:methodsynopsis +db:ooclass +db:ooexception +db:oointerface +db:simplemsgentry +db:manvolnum +"/> + +<!-- ==================================================================== --> +<!-- * Get character map contents --> +<!-- ==================================================================== --> + + <xsl:variable name="man.charmap.contents"> + <xsl:if test="$man.charmap.enabled != 0"> + <xsl:call-template name="read-character-map"> + <xsl:with-param name="use.subset" select="$man.charmap.use.subset"/> + <xsl:with-param name="subset.profile" select="$man.charmap.subset.profile"/> + <xsl:with-param name="uri"> + <xsl:choose> + <xsl:when test="$man.charmap.uri != ''"> + <xsl:value-of select="$man.charmap.uri"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="'../manpages/charmap.groff.xsl'"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:variable> + +<!-- ==================================================================== --> + +<xsl:template name="root.messages"> + <xsl:param name="refname"/> + <!-- redefine this any way you'd like to output messages --> + <!-- DO NOT OUTPUT ANYTHING FROM THIS TEMPLATE --> + <!-- Example: + <xsl:if test="//foo"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$refname"/> + <xsl:with-param name="context-desc"> + <xsl:text>limitation</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Output for foo element is not yet supported.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + --> +</xsl:template> + +<!-- ==================================================================== --> +<!-- * Escape roff special chars --> +<!-- ==================================================================== --> + +<!-- ******************************************************************** --> +<!-- * --> +<!-- * The backslash, dot, dash, and apostrophe (\, ., -, ') characters --> +<!-- * have special meaning for roff, so before we do any other --> +<!-- * processing, we must escape those characters where they appear in --> +<!-- * the source content. --> +<!-- * --> +<!-- * Here we also deal with replacing U+00a0 (non-breaking space) with --> +<!-- * its roff equivalent --> +<!-- * --> +<!-- ******************************************************************** --> + +<xsl:template match="//refentry//text()"> + <xsl:call-template name="escape.roff.specials"> + <xsl:with-param name="content"> + <xsl:value-of select="."/> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.roff.specials"> + <xsl:param name="content"/> + <xsl:call-template name="convert.nobreak-space"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.apostrophe"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.dash"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.dot"> + <xsl:with-param name="content"> + <xsl:call-template name="escape.backslash"> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.backslash"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">\</xsl:with-param> + <!-- * we use "\e" instead of "\\" because the groff docs say --> + <!-- * that's the correct thing to do; also because testing --> + <!-- * shows that "\\" doesn't always work as expected; for --> + <!-- * example, "\\" within a table seems to mess things up --> + <xsl:with-param name="replacement">\e</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.dot"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">.</xsl:with-param> + <xsl:with-param name="replacement">\.</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.dash"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">-</xsl:with-param> + <xsl:with-param name="replacement">\-</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="escape.apostrophe"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target">'</xsl:with-param> + <xsl:with-param name="replacement">\'</xsl:with-param> + </xsl:call-template> +</xsl:template> + +<xsl:template name="convert.nobreak-space"> + <xsl:param name="content"/> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target"> </xsl:with-param> + <!-- * A no-break space can be written two ways in roff; the --> + <!-- * difference, according to the "Page Motions" node in the --> + <!-- * groff info page, is: --> + <!-- * --> + <!-- * "\ " = --> + <!-- * An unbreakable and unpaddable (i.e. not expanded --> + <!-- * during filling) space. --> + <!-- * --> + <!-- * "\~" = --> + <!-- * An unbreakable space that stretches like a normal --> + <!-- * inter-word space when a line is adjusted." --> + <!-- * --> + <!-- * Unfortunately, roff seems to do some weird things with --> + <!-- * long lines that only have words separated by "\~" --> + <!-- * spaces, so it's safer just to stick with the "\ " space --> + <xsl:with-param name="replacement">\ </xsl:with-param> + </xsl:call-template> +</xsl:template> + +<!-- ==================================================================== --> + +<!-- * top.comment generates a comment containing metadata for the man --> +<!-- * page; for example, Author, Generator, and Date information --> + + <xsl:template name="top.comment"> + <xsl:param name="info"/> + <xsl:param name="date"/> + <xsl:param name="title"/> + <xsl:param name="manual"/> + <xsl:param name="source"/> + <xsl:text>.\" Title: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$title"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Author: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content"> + <xsl:call-template name="make.roff.metadata.author"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Generator: DocBook </xsl:text> + <xsl:value-of select="$DistroTitle"/> + <xsl:text> v</xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$VERSION"/> + </xsl:call-template> + <xsl:text> <http://docbook.sf.net/></xsl:text> + <xsl:text> </xsl:text> + <xsl:text>.\" Date: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$date"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Manual: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$manual"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\" Source: </xsl:text> + <xsl:call-template name="replace.dots.and.dashes"> + <xsl:with-param name="content" select="$source"/> + </xsl:call-template> + <xsl:text> </xsl:text> + <xsl:text>.\"</xsl:text> + <xsl:text> </xsl:text> + </xsl:template> + +<!-- ==================================================================== --> + + <xsl:template name="TH.title.line"> + + <!-- * The exact way that .TH contents are displayed is system- --> + <!-- * dependent; it varies somewhat between OSes and roff --> + <!-- * versions. Below is a description of how Linux systems with --> + <!-- * a modern groff seem to render .TH contents. --> + <!-- * --> + <!-- * title(section) extra3 title(section) <- page header --> + <!-- * extra2 extra1 title(section) <- page footer--> + <!-- * --> + <!-- * Or, using the names with which the man(7) man page refers --> + <!-- * to the various fields: --> + <!-- * --> + <!-- * title(section) manual title(section) <- page header --> + <!-- * source date title(section) <- page footer--> + <!-- * --> + <!-- * Note that while extra1, extra2, and extra3 are all (nominally) --> + <!-- * optional, in practice almost all pages include an "extra1" --> + <!-- * field, which is, universally, a date (in some form), and it is --> + <!-- * always rendered in the same place (the middle footer position) --> + <!-- * --> + <!-- * Here are a couple of examples of real-world man pages that --> + <!-- * have somewhat useful page headers/footers: --> + <!-- * --> + <!-- * gtk-options(7) GTK+ User's Manual gtk-options(7) --> + <!-- * GTK+ 1.2 2003-10-20 gtk-options(7) --> + <!-- * --> + <!-- * svgalib(7) Svgalib User Manual svgalib(7) --> + <!-- * Svgalib 1.4.1 16 December 1999 svgalib(7) --> + <!-- * --> + <xsl:param name="title"/> + <xsl:param name="section"/> + <xsl:param name="extra1"/> + <xsl:param name="extra2"/> + <xsl:param name="extra3"/> + + <xsl:call-template name="mark.subheading"/> + <!-- * Note that we generate quotes around _every_ field in the --> + <!-- * .TH title line, including the "title" and "section" --> + <!-- * fields. That is because we use the contents of those "as --> + <!-- * is", unchanged from the DocBook source; and DTD-based --> + <!-- * validation does not provide a way to constrain them to be --> + <!-- * "space free" --> + <xsl:text>.TH "</xsl:text> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <xsl:when test="$man.th.title.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($title, 1, $man.th.title.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($title)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + <xsl:text>" "</xsl:text> + <xsl:value-of select="normalize-space($section)"/> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra1.suppress = 0"> + <!-- * there is no max.length for the extra1 field; the reason --> + <!-- * is, it is almost always a date, and it is not possible --> + <!-- * to truncate dates without changing their meaning --> + <xsl:value-of select="normalize-space($extra1)"/> + </xsl:if> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra2.suppress = 0"> + <xsl:choose> + <!-- * if max.length is non-empty, use value to truncate field --> + <xsl:when test="$man.th.extra2.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($extra2, 1, $man.th.extra2.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($extra2)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + <xsl:text>" "</xsl:text> + <xsl:if test="$man.th.extra3.suppress = 0"> + <xsl:choose> + <!-- * if max.length is non-empty, use value to truncate field --> + <xsl:when test="$man.th.extra3.max.length != ''"> + <xsl:value-of + select="normalize-space(substring($extra3, 1, $man.th.extra3.max.length))"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space($extra3)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template name="set.default.formatting"> + <!-- * Set default hyphenation, justification, indentation and --> + <!-- * line-breaking --> + <!-- * --> + <!-- * If the value of man.hypenate is zero (the default), then --> + <!-- * disable hyphenation (".nh" = "no hyphenation") --> + <xsl:if test="$man.hyphenate = 0"> + <xsl:text>.\" disable hyphenation </xsl:text> + <xsl:text>.nh </xsl:text> + </xsl:if> + <!-- * If the value of man.justify is zero (the default), then --> + <!-- * disable justification (".ad l" means "adjust to left only") --> + <xsl:if test="$man.justify = 0"> + <xsl:text>.\" disable justification</xsl:text> + <xsl:text> (adjust text to left margin only) </xsl:text> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <xsl:if test="not($man.indent.refsect = 0)"> + <xsl:text>.\" store initial "default indentation value" </xsl:text> + <xsl:text>.nr zq \n(IN </xsl:text> + <xsl:text>.\" adjust default indentation </xsl:text> + <xsl:text>.nr IN </xsl:text> + <xsl:value-of select="$man.indent.width"/> + <xsl:text> </xsl:text> + <xsl:text>.\" adjust indentation of SS headings </xsl:text> + <xsl:text>.nr SN \n(IN </xsl:text> + </xsl:if> + <!-- * Unless the value of man.break.after.slash is zero (the --> + <!-- * default), tell groff that it is OK to break a line --> + <!-- * after a slash when needed. --> + <xsl:if test="$man.break.after.slash != 0"> + <xsl:text>.\" enable line breaks after slashes </xsl:text> + <xsl:text>.cflags 4 / </xsl:text> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The prepare.manpage.contents template is called after all --> + <!-- * other processing has been done, before serializing the --> + <!-- * result of all the other processing. It basically works on --> + <!-- * the result as one big string. --> + <xsl:template name="prepare.manpage.contents"> + <xsl:param name="content" select="''"/> + + <!-- * If user has provided a "local" string-substitution map to --> + <!-- * be applied /before/ the standard string-substitution map, --> + <!-- * apply it. --> + <xsl:variable name="pre.adjusted.content"> + <xsl:choose> + <xsl:when test="$man.string.subst.map.local.pre"> + <!-- * normalized value of man.string.subst.map.local.pre --> + <!-- * is non-empty, so get contents of map and apply them --> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map.local.pre)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * value of man.string.subst.map.local.pre is empty, --> + <!-- * so just copy original contents --> + <xsl:value-of select="$content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- * Apply standard string-substitution map. The main purpose --> + <!-- * of this map is to escape certain characters that have --> + <!-- * special meaning in roff, and to replace certain characters --> + <!-- * used within the stylesheet internally to represent roff --> + <!-- * markup characters. --> + <xsl:variable name="adjusted.content"> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$pre.adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map)/*"/> + </xsl:call-template> + </xsl:variable> + + <!-- * If user has provided a "local" string-substitution map to --> + <!-- * be applied /after/ the standard string-substitution map, --> + <!-- * apply it. --> + <xsl:variable name="post.adjusted.content"> + <xsl:choose> + <xsl:when test="$man.string.subst.map.local.post"> + <!-- * normalized value of man.string.subst.map.local.post --> + <!-- * is non-empty, so get contents of map and apply them --> + <xsl:call-template name="apply-string-subst-map"> + <xsl:with-param name="content" select="$adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.string.subst.map.local.post)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * value of man.string.subst.map.local.post is empty, --> + <!-- * so just copy original contents --> + <xsl:value-of select="$adjusted.content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <!-- * Optionally, apply a character map to replace Unicode --> + <!-- * symbols and special characters. --> + <xsl:choose> + <xsl:when test="$man.charmap.enabled != 0"> + <xsl:call-template name="apply-character-map"> + <xsl:with-param name="content" select="$post.adjusted.content"/> + <xsl:with-param name="map.contents" + select="exsl:node-set($man.charmap.contents)/*"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <!-- * if we reach here, value of $man.charmap.enabled is zero, --> + <!-- * so we just pass the adjusted contents through "as is" --> + <xsl:value-of select="$adjusted.content"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="write.man.file"> + <xsl:param name="name"/> + <xsl:param name="section"/> + <xsl:param name="lang"/> + <xsl:param name="content"/> + <xsl:param name="filename"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="$name"/> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + </xsl:param> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename" select="$filename"/> + <xsl:with-param name="suppress-context-node-name" select="1"/> + <xsl:with-param name="quiet" select="$man.output.quietly"/> + <xsl:with-param + name="message-prolog" + >Note: </xsl:with-param> + <xsl:with-param name="encoding" select="$man.output.encoding"/> + <xsl:with-param name="content" select="$content"/> + </xsl:call-template> + </xsl:template> + + <!-- ============================================================== --> + + <!-- * A "stub" is sort of alias for another file, intended to be read --> + <!-- * and expanded by soelim(1); it's simply a file whose complete --> + <!-- * contents are just a single line of the following form: --> + <!-- * --> + <!-- * .so manX/realname.X --> + <!-- * --> + <!-- * "realname" is a name of another man-page file. That .so line is --> + <!-- * basically a roff "include" statement. When the man command finds --> + <!-- * it, it calls soelim(1) and includes and displays the contents of --> + <!-- * the manX/realqname.X file. --> + <!-- * --> + <!-- * If a refentry has multiple refnames, we generate a "stub" page for --> + <!-- * each refname found, except for the first one. --> + <xsl:template name="write.stubs"> + <xsl:param name="first.refname"/> + <xsl:param name="section"/> + <xsl:param name="lang"/> + <xsl:for-each select="refnamediv/refname"> + <xsl:if test=". != $first.refname"> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name"> + <xsl:apply-templates/> + </xsl:with-param> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + </xsl:with-param> + <xsl:with-param name="quiet" select="$man.output.quietly"/> + <xsl:with-param name="suppress-context-node-name" select="1"/> + <xsl:with-param name="message-prolog">Note: </xsl:with-param> + <xsl:with-param name="message-epilog"> (soelim stub)</xsl:with-param> + <xsl:with-param name="content"> + <xsl:value-of select="concat('.so man', $section, '/')"/> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$section"/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:for-each> + </xsl:template> + + <!-- ============================================================== --> + + <!-- * A manifest file is useful for doing "make clean" during --> + <!-- * builds and for other purposes. When we make the manifest --> + <!-- * file, we need to include in it a filename for each man-page --> + <!-- * generated, including any "stub" pages. --> + <xsl:template name="generate.manifest"> + <xsl:variable name="filelist"> + <xsl:for-each select="//refentry"> + <!-- * all refname instances in a Refentry inherit their section --> + <!-- * numbers from the parent Refentry; so we only need to get --> + <!-- * the section once per Refentry, not once per Refname --> + <xsl:variable name="section"> + <xsl:call-template name="get.refentry.section"> + <xsl:with-param name="quiet" select="1"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:variable> + <xsl:for-each select="refnamediv/refname"> + <xsl:call-template name="make.adjusted.man.filename"> + <xsl:with-param name="name" select="."/> + <xsl:with-param name="section" select="$section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + <xsl:text> </xsl:text> + </xsl:for-each> + </xsl:for-each> + </xsl:variable> + + <!-- * we write the manifest file once per document, not once per --> + <!-- * Refentry --> + <xsl:call-template name="write.text.chunk"> + <xsl:with-param name="filename"> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:with-param> + <xsl:with-param name="quiet" select="1"/> + <xsl:with-param name="message-prolog">Note: </xsl:with-param> + <xsl:with-param name="message-epilog"> (manifest file)</xsl:with-param> + <xsl:with-param name="content"> + <xsl:value-of select="$filelist"/> + </xsl:with-param> + </xsl:call-template> + <xsl:if test="$man.output.quietly = 0"> + <xsl:message><xsl:text> </xsl:text></xsl:message> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/param.xsl b/docs/xsl-generic/manpages/param.xsl new file mode 100644 index 00000000..fcdc3bc6 --- /dev/null +++ b/docs/xsl-generic/manpages/param.xsl @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="ASCII"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> + +<!-- This file is generated from param.xweb --> + +<!-- ******************************************************************** + $Id: param.xweb 7112 2007-07-22 12:19:19Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:param name="man.authors.section.enabled">1</xsl:param> +<xsl:param name="man.break.after.slash">0</xsl:param> +<xsl:param name="man.charmap.enabled" select="1"/> +<xsl:param name="man.charmap.subset.profile"> +@*[local-name() = 'block'] = 'Miscellaneous Technical' or +(@*[local-name() = 'block'] = 'C1 Controls And Latin-1 Supplement (Latin-1 Supplement)' and + @*[local-name() = 'class'] = 'symbols' +) or +(@*[local-name() = 'block'] = 'General Punctuation' and + (@*[local-name() = 'class'] = 'spaces' or + @*[local-name() = 'class'] = 'dashes' or + @*[local-name() = 'class'] = 'quotes' or + @*[local-name() = 'class'] = 'bullets' + ) +) or +@*[local-name() = 'name'] = 'HORIZONTAL ELLIPSIS' or +@*[local-name() = 'name'] = 'WORD JOINER' or +@*[local-name() = 'name'] = 'SERVICE MARK' or +@*[local-name() = 'name'] = 'TRADE MARK SIGN' or +@*[local-name() = 'name'] = 'ZERO WIDTH NO-BREAK SPACE' +</xsl:param> +<xsl:param name="man.charmap.uri"/> +<xsl:param name="man.charmap.use.subset" select="1"/> +<xsl:param name="man.copyright.section.enabled">1</xsl:param> +<xsl:param name="man.endnotes.are.numbered">1</xsl:param> +<xsl:param name="man.endnotes.list.enabled">1</xsl:param> +<xsl:param name="man.endnotes.list.heading"/> + <xsl:param name="man.font.funcprototype">BI</xsl:param> + <xsl:param name="man.font.funcsynopsisinfo">B</xsl:param> + <xsl:param name="man.font.table.headings">B</xsl:param> + <xsl:param name="man.font.table.title">B</xsl:param> +<xsl:param name="man.hyphenate.computer.inlines">0</xsl:param> +<xsl:param name="man.hyphenate.filenames">0</xsl:param> +<xsl:param name="man.hyphenate">0</xsl:param> +<xsl:param name="man.hyphenate.urls">0</xsl:param> +<xsl:param name="man.indent.blurbs" select="1"/> +<xsl:param name="man.indent.lists" select="1"/> +<xsl:param name="man.indent.refsect" select="0"/> +<xsl:param name="man.indent.verbatims" select="1"/> +<xsl:param name="man.indent.width">4</xsl:param> +<xsl:param name="man.justify">0</xsl:param> +<xsl:param name="man.links.are.underlined">1</xsl:param> +<xsl:param name="man.output.base.dir">man/</xsl:param> +<xsl:param name="man.output.encoding">UTF-8</xsl:param> +<xsl:param name="man.output.in.separate.dir" select="0"/> +<xsl:param name="man.output.lang.in.name.enabled" select="0"/> +<xsl:param name="man.output.manifest.enabled" select="0"/> +<xsl:param name="man.output.manifest.filename">MAN.MANIFEST</xsl:param> +<xsl:param name="man.output.quietly" select="0"/> +<xsl:param name="man.output.subdirs.enabled" select="1"/> +<xsl:param name="man.segtitle.suppress" select="0"/> +<xsl:param name="man.string.subst.map"> + + <!-- * remove no-break marker at beginning of line (stylesheet artifact) --> + <substitution oldstring="▒▀" newstring="▒"/> + <!-- * replace U+2580 no-break marker (stylesheet-added) w/ no-break space --> + <substitution oldstring="▀" newstring="\ "/> + + <!-- ==================================================================== --> + + <!-- * squeeze multiple newlines before a roff request --> + <substitution oldstring=" ." newstring=" ."/> + <!-- * remove any .sp instances that directly precede a .PP --> + <substitution oldstring=".sp .PP" newstring=".PP"/> + <!-- * remove any .sp instances that directly follow a .PP --> + <substitution oldstring=".PP .sp" newstring=".PP"/> + <!-- * squeeze multiple newlines after start of no-fill (verbatim) env. --> + <substitution oldstring=".nf " newstring=".nf "/> + <!-- * squeeze multiple newlines after REstoring margin --> + <substitution oldstring=".RE " newstring=".RE "/> + <!-- * U+2591 is a marker we add before and after every Parameter in --> + <!-- * Funcprototype output --> + <substitution oldstring="░" newstring=" "/> + <!-- * U+2592 is a marker we add for the newline before output of <sbr>; --> + <substitution oldstring="▒" newstring=" "/> + <!-- * --> + <!-- * Now deal with some other characters that are added by the --> + <!-- * stylesheets during processing. --> + <!-- * --> + <!-- * bullet --> + <substitution oldstring="•" newstring="\(bu"/> + <!-- * left double quote --> + <substitution oldstring="“" newstring="\(lq"/> + <!-- * right double quote --> + <substitution oldstring="”" newstring="\(rq"/> + <!-- * left single quote --> + <substitution oldstring="‘" newstring="\(oq"/> + <!-- * right single quote --> + <substitution oldstring="’" newstring="\(cq"/> + <!-- * copyright sign --> + <substitution oldstring="©" newstring="\(co"/> + <!-- * registered sign --> + <substitution oldstring="®" newstring="\(rg"/> + <!-- * ...servicemark... --> + <!-- * There is no groff equivalent for it. --> + <substitution oldstring="℠" newstring="(SM)"/> + <!-- * ...trademark... --> + <!-- * We don't do "\(tm" because for console output, --> + <!-- * groff just renders that as "tm"; that is: --> + <!-- * --> + <!-- * Product™ -> Producttm --> + <!-- * --> + <!-- * So we just make it to "(TM)" instead; thus: --> + <!-- * --> + <!-- * Product™ -> Product(TM) --> + <substitution oldstring="™" newstring="(TM)"/> + +</xsl:param> +<xsl:param name="man.string.subst.map.local.post"/> + <xsl:param name="man.string.subst.map.local.pre"/> +<xsl:param name="man.subheading.divider.enabled">0</xsl:param> +<xsl:param name="man.subheading.divider">========================================================================</xsl:param> +<xsl:param name="man.table.footnotes.divider">----</xsl:param> +<xsl:param name="man.th.extra1.suppress">0</xsl:param> +<xsl:param name="man.th.extra2.max.length">30</xsl:param> +<xsl:param name="man.th.extra2.suppress">0</xsl:param> +<xsl:param name="man.th.extra3.max.length">30</xsl:param> +<xsl:param name="man.th.extra3.suppress">0</xsl:param> +<xsl:param name="man.th.title.max.length">20</xsl:param> +<xsl:param name="refentry.date.profile.enabled">0</xsl:param> +<xsl:param name="refentry.date.profile"> + (($info[//date])[last()]/date)[1]| + (($info[//pubdate])[last()]/pubdate)[1] +</xsl:param> +<xsl:param name="refentry.manual.fallback.profile"> +refmeta/refmiscinfo[1]/node()</xsl:param> +<xsl:param name="refentry.manual.profile.enabled">0</xsl:param> +<xsl:param name="refentry.manual.profile"> + (($info[//title])[last()]/title)[1]| + ../title/node() +</xsl:param> +<xsl:param name="refentry.meta.get.quietly" select="0"/> +<xsl:param name="refentry.source.fallback.profile"> +refmeta/refmiscinfo[1]/node()</xsl:param> +<xsl:param name="refentry.source.name.profile.enabled">0</xsl:param> +<xsl:param name="refentry.source.name.profile"> + (($info[//productname])[last()]/productname)[1]| + (($info[//corpname])[last()]/corpname)[1]| + (($info[//corpcredit])[last()]/corpcredit)[1]| + (($info[//corpauthor])[last()]/corpauthor)[1]| + (($info[//orgname])[last()]/orgname)[1]| + (($info[//publishername])[last()]/publishername)[1] +</xsl:param> +<xsl:param name="refentry.source.name.suppress">0</xsl:param> +<xsl:param name="refentry.version.profile.enabled">0</xsl:param> +<xsl:param name="refentry.version.profile"> + (($info[//productnumber])[last()]/productnumber)[1]| + (($info[//edition])[last()]/edition)[1]| + (($info[//releaseinfo])[last()]/releaseinfo)[1] +</xsl:param> +<xsl:param name="refentry.version.suppress">0</xsl:param> +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/profile-docbook.xsl b/docs/xsl-generic/manpages/profile-docbook.xsl new file mode 100644 index 00000000..ec4759e0 --- /dev/null +++ b/docs/xsl-generic/manpages/profile-docbook.xsl @@ -0,0 +1,259 @@ +<?xml version="1.0" encoding="US-ASCII"?> +<!--This file was created automatically by xsl2profile--> +<!--from the DocBook XSL stylesheets.--> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" xmlns:ng="http://docbook.org/docbook-ng" xmlns:db="http://docbook.org/ns/docbook" xmlns:exslt="http://exslt.org/common" exslt:dummy="dummy" ng:dummy="dummy" db:dummy="dummy" extension-element-prefixes="exslt" exclude-result-prefixes="exsl exslt" version="1.0"> + + <xsl:import href="../html/docbook.xsl"/> + <xsl:import href="../html/manifest.xsl"/> + <!-- * html-synop.xsl file is generated by build --> + <xsl:import href="html-synop.xsl"/> + <xsl:output method="text" encoding="UTF-8" indent="no"/> + <!-- ******************************************************************** + $Id: docbook.xsl 7153 2007-07-26 14:08:55Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + + <!-- ==================================================================== --> + + <xsl:include href="../common/refentry.xsl"/> + <xsl:include href="../common/charmap.xsl"/> + <xsl:include href="param.xsl"/> + <xsl:include href="utility.xsl"/> + <xsl:include href="info.xsl"/> + <xsl:include href="other.xsl"/> + <xsl:include href="refentry.xsl"/> + <xsl:include href="block.xsl"/> + <xsl:include href="inline.xsl"/> + <xsl:include href="synop.xsl"/> + <xsl:include href="lists.xsl"/> + <xsl:include href="endnotes.xsl"/> + <xsl:include href="table.xsl"/> + + <!-- * we rename the following just to avoid using params with "man" --> + <!-- * prefixes in the table.xsl stylesheet (because that stylesheet --> + <!-- * can potentially be reused for more than just man output) --> + <xsl:param name="tbl.font.headings" select="$man.font.table.headings"/> + <xsl:param name="tbl.font.title" select="$man.font.table.title"/> + + <!-- ==================================================================== --> + + <xslo:include xmlns:xslo="http://www.w3.org/1999/XSL/Transform" href="../profiling/profile-mode.xsl"/><xslo:variable xmlns:xslo="http://www.w3.org/1999/XSL/Transform" name="profiled-content"><xslo:choose><xslo:when test="*/self::ng:* or */self::db:*"><xslo:message>Note: namesp. cut : stripped namespace before processing</xslo:message><xslo:variable name="stripped-content"><xslo:apply-templates select="/" mode="stripNS"/></xslo:variable><xslo:message>Note: namesp. cut : processing stripped document</xslo:message><xslo:apply-templates select="exslt:node-set($stripped-content)" mode="profile"/></xslo:when><xslo:otherwise><xslo:apply-templates select="/" mode="profile"/></xslo:otherwise></xslo:choose></xslo:variable><xslo:variable xmlns:xslo="http://www.w3.org/1999/XSL/Transform" name="profiled-nodes" select="exslt:node-set($profiled-content)"/><xsl:template match="/"> + <!-- * Get a title for current doc so that we let the user --> + <!-- * know what document we are processing at this point. --> + <xsl:variable name="doc.title"> + <xsl:call-template name="get.doc.title"/> + </xsl:variable> + <xsl:choose> + <!-- * when we find a namespaced document, strip the --> + <!-- * namespace and then continue processing it. --> + <xsl:when test="false()"/> + <xsl:when test="//*[local-name() = 'refentry']"> + <!-- * Check to see if we have any refentry children in this --> + <!-- * document; if so, process them. The reason we use --> + <!-- * local-name()=refentry (instead of just //refentry) to to --> + <!-- * check for refentry children is because this stylsheet is --> + <!-- * also post-processed by the stylesheet build to create the --> + <!-- * manpages/profile-docbook.xsl, and the refentry child check --> + <!-- * in the profile-docbook.xsl stylesheet won't work if we do --> + <!-- * a simple //refentry check. --> + <xsl:apply-templates select="$profiled-nodes//refentry"/> + <!-- * if $man.output.manifest.enabled is non-zero, --> + <!-- * generate a manifest file --> + <xsl:if test="not($man.output.manifest.enabled = 0)"> + <xsl:call-template name="generate.manifest"> + <xsl:with-param name="filename"> + <xsl:choose> + <xsl:when test="not($man.output.manifest.filename = '')"> + <!-- * If a name for the manifest file is specified, --> + <!-- * use that name. --> + <xsl:value-of select="$man.output.manifest.filename"/> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, if user has unset --> + <!-- * $man.output.manifest.filename, default to --> + <!-- * using "MAN.MANIFEST" as the filename. Because --> + <!-- * $man.output.manifest.enabled is non-zero and --> + <!-- * so we must have a filename in order to --> + <!-- * generate the manifest. --> + <xsl:text>MAN.MANIFEST</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, the document does not contain any --> + <!-- * refentry elements, so log/emit message and stop. --> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Erro</xsl:with-param> + <xsl:with-param name="source" select="$doc.title"/> + <xsl:with-param name="context-desc"> + <xsl:text> no refentry</xsl:text> + </xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>No refentry elements found</xsl:text> + <xsl:if test="$doc.title != ''"> + <xsl:text> in "</xsl:text> + <xsl:choose> + <xsl:when test="string-length($doc.title) > 30"> + <xsl:value-of select="substring($doc.title,1,30)"/> + <xsl:text>...</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$doc.title"/> + </xsl:otherwise> + </xsl:choose> + <xsl:text>"</xsl:text> + </xsl:if> + <xsl:text>.</xsl:text> + </xsl:with-param> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- ============================================================== --> + + <xsl:template match="refentry"> + <xsl:param name="lang"> + <xsl:call-template name="l10n.language"/> + </xsl:param> + <!-- * Just use the first refname found as the "name" of the man --> + <!-- * page (which may different from the "title"...) --> + <xsl:variable name="first.refname" select="refnamediv[1]/refname[1]"/> + + <xsl:call-template name="root.messages"> + <xsl:with-param name="refname" select="$first.refname"/> + </xsl:call-template> + + <!-- * Because there are several times when we need to check *info of --> + <!-- * each refentry and its ancestors, we get those and store the --> + <!-- * data from them as a node-set in memory. --> + + <!-- * Make a node-set with contents of *info --> + <xsl:variable name="get.info" select="ancestor-or-self::*/*[substring(local-name(), string-length(local-name()) - 3) = 'info']"/> + <xsl:variable name="info" select="exsl:node-set($get.info)"/> + + <!-- * The get.refentry.metadata template is in --> + <!-- * ../common/refentry.xsl. It looks for metadata in $info --> + <!-- * and in various other places and then puts it into a form --> + <!-- * that's easier for us to digest. --> + <xsl:variable name="get.refentry.metadata"> + <xsl:call-template name="get.refentry.metadata"> + <xsl:with-param name="refname" select="$first.refname"/> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="prefs" select="$refentry.metadata.prefs"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="refentry.metadata" select="exsl:node-set($get.refentry.metadata)"/> + + <!-- * Assemble the various parts into a complete page, then store into --> + <!-- * $manpage.contents so that we can manipluate them further. --> + <xsl:variable name="manpage.contents"> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * top.comment = commented-out section at top of roff source --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="top.comment"> + <xsl:with-param name="info" select="$info"/> + <xsl:with-param name="date" select="$refentry.metadata/date"/> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="manual" select="$refentry.metadata/manual"/> + <xsl:with-param name="source" select="$refentry.metadata/source"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * TH.title.line = title line in header/footer of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="TH.title.line"> + <!-- * .TH TITLE section extra1 extra2 extra3 --> + <!-- * --> + <!-- * According to the man(7) man page: --> + <!-- * --> + <!-- * extra1 = date, "the date of the last revision" --> + <!-- * extra2 = source, "the source of the command" --> + <!-- * extra3 = manual, "the title of the manual --> + <!-- * (e.g., Linux Programmer's Manual)" --> + <!-- * --> + <!-- * So, we end up with: --> + <!-- * --> + <!-- * .TH TITLE section date source manual --> + <!-- * --> + <xsl:with-param name="title" select="$refentry.metadata/title"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="extra1" select="$refentry.metadata/date"/> + <xsl:with-param name="extra2" select="$refentry.metadata/source"/> + <xsl:with-param name="extra3" select="$refentry.metadata/manual"/> + </xsl:call-template> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Set default hyphenation, justification, indentation, and --> + <!-- * line-breaking --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:call-template name="set.default.formatting"/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * Main body of man page --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:apply-templates/> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * AUTHOR section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.authors.section.enabled = 0)"> + <xsl:call-template name="author.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * COPYRIGHT section --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="not($man.copyright.section.enabled = 0)"> + <xsl:call-template name="copyright.section"> + <xsl:with-param name="info" select="$info"/> + </xsl:call-template> + </xsl:if> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <!-- * NOTES list (only if user wants endnotes numbered and/or listed) --> + <!-- * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> + <xsl:if test="$man.endnotes.list.enabled != 0 or $man.endnotes.are.numbered != 0"> + <xsl:call-template name="endnotes.list"/> + </xsl:if> + </xsl:variable> <!-- * end of manpage.contents --> + + <!-- * Prepare the page contents for final output, then store in --> + <!-- * $manpage.contents.prepared so the we can pass it on to the --> + <!-- * write.text.chunk() function --> + <xsl:variable name="manpage.contents.prepared"> + <!-- * "Preparing" the page contents involves, at a minimum, --> + <!-- * doubling any backslashes found (so they aren't interpreted --> + <!-- * as roff escapes). --> + <!-- * --> + <!-- * If $charmap.enabled is true, "preparing" the page contents also --> + <!-- * involves applying a character map to convert Unicode symbols and --> + <!-- * special characters into corresponding roff escape sequences. --> + <xsl:call-template name="prepare.manpage.contents"> + <xsl:with-param name="content" select="$manpage.contents"/> + </xsl:call-template> + </xsl:variable> + + <!-- * Write the prepared page contents to disk to create --> + <!-- * the final man page. --> + <xsl:call-template name="write.man.file"> + <xsl:with-param name="name" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + <xsl:with-param name="content" select="$manpage.contents.prepared"/> + </xsl:call-template> + + <!-- * Generate "stub" (alias) pages (if any needed) --> + <xsl:call-template name="write.stubs"> + <xsl:with-param name="first.refname" select="$first.refname"/> + <xsl:with-param name="section" select="$refentry.metadata/section"/> + <xsl:with-param name="lang" select="$lang"/> + </xsl:call-template> + + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/refentry.xsl b/docs/xsl-generic/manpages/refentry.xsl new file mode 100644 index 00000000..9cb7005f --- /dev/null +++ b/docs/xsl-generic/manpages/refentry.xsl @@ -0,0 +1,256 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + version='1.0'> + +<!-- ******************************************************************** + $Id: refentry.xsl 6657 2007-02-26 20:04:25Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + + <xsl:template match="refnamediv"> + <xsl:choose> + <xsl:when test="preceding-sibling::refnamediv"> + <!-- * No title on secondary refnamedivs! --> + <!-- * Just put a single line break instead --> + <xsl:text>.br </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:apply-templates select="." mode="title.markup"/> + <xsl:text>"</xsl:text> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:call-template name="mark.subheading"/> + <!-- * if we have multiple Refname instances, separate the names --> + <!-- * with commas --> + <xsl:for-each select="refname"> + <xsl:if test="position()>1"> + <xsl:text>, </xsl:text> + </xsl:if> + <xsl:value-of select="."/> + </xsl:for-each> + <!-- * The man(7) man pages says: --> + <!-- * --> + <!-- * The only required heading is NAME, which should be the --> + <!-- * first section and be followed on the next line by a one --> + <!-- * line description of the program: --> + <!-- * --> + <!-- * .SH NAME chess \- the game of chess --> + <!-- * --> + <!-- * It is extremely important that this format is followed, --> + <!-- * and that there is a backslash before the single dash --> + <!-- * which follows the command name. This syntax is used by --> + <!-- * the makewhatis(8) program to create a database of short --> + <!-- * command descriptions for the whatis(1) and apropos(1) --> + <!-- * commands. --> + <!-- * --> + <!-- * So why don't we precede the hyphen with a backslash here? --> + <!-- * Well, because it's added later, by the apply-string-subst-map --> + <!-- * template, before we generate final output --> + <xsl:if test="refpurpose/node()"> + <xsl:text> - </xsl:text> + <xsl:value-of select="normalize-space(refpurpose)"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:template> + + <xsl:template match="refsynopsisdiv"> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:apply-templates select="." mode="title.markup"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="refsect1|refentry/refsection"> + <xsl:variable name="title"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:variable> + <xsl:call-template name="mark.subheading"/> + <xsl:text>.SH "</xsl:text> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="refsect2|refentry/refsection/refsection"> + <xsl:call-template name="mark.subheading"/> + <xsl:variable name="title"> + <xsl:apply-templates + select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]/node()"/> + + </xsl:variable> + <xsl:text>.SS "</xsl:text> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text>" </xsl:text> + <xsl:call-template name="mark.subheading"/> + <xsl:choose> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect2 --> + <xsl:when test="not($man.indent.refsect = 0)"> + <xsl:text>.RS </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If default-indentation adjustment is on, then do not --> + <!-- * indent the child content of thie Refsect2, because --> + <!-- * the title is already "sticking out to the left" --> + <!-- * (as the groff_man(7) man page describes it), which --> + <!-- * actually means the title is indented by the value of --> + <!-- * the SN register, which appears by default to be --> + <!-- * about half of the default indentation value --> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="refsect3|refentry/refsection/refsection/refsection"> + <xsl:variable name="title"> + <xsl:value-of select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + </xsl:variable> + <xsl:choose> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect3 or Refsection. --> + <xsl:when test="not($man.indent.refsect != 0)"> + <xsl:call-template name="nested-section-title"/> + <xsl:text>.RS </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If default-indentation adjustment is on, then do not --> + <!-- * indent the child content of thie Refsect2, because --> + <!-- * the title is already "sticking out to the left" --> + <!-- * (as the groff_man(7) man page describes it), which --> + <!-- * actually means the title is indented by the value of --> + <!-- * the SN register, which appears by default to be --> + <!-- * about half of the default indentation value --> + <xsl:text>.ti (\n(SNu * 5u / 3u) </xsl:text> + <xsl:call-template name="nested-section-title"/> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="refsection"> + <!-- * This template is used for a nested Refsection that is --> + <!-- * is a child of a Refsect3-level section (The numberd --> + <!-- * Refsect hierarchy in DocBook ends with Refsect3, so --> + <!-- * there is not actually a Refsect4 element.) --> + <xsl:variable name="title"> + <xsl:value-of select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + </xsl:variable> + <xsl:variable name="indent-width"> + <xsl:if test="not($man.indent.refsect = 0)"> + <!-- * If default-indentation adjustment is on, then indent the --> + <!-- * child content of this Refsect3 or Refsection. --> + <xsl:text>(\n(SNu) </xsl:text> + </xsl:if> + </xsl:variable> + <xsl:call-template name="nested-section-title"/> + <xsl:text>.RS (\n(SNu) </xsl:text> + <xsl:apply-templates/> + <xsl:text>.RE </xsl:text> + </xsl:template> + + <!-- ==================================================================== --> + + <!-- * Use uppercase to render titles of all instances of Refsect1 or --> + <!-- * top-level Refsection, including in cross-references --> + <xsl:template match="refsect1|refentry/refsection" + mode="title.markup"> + <xsl:variable name="title" select="(info/title + |refsectioninfo/title + |refsect1info/title + |title)[1]"/> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:apply-templates select="$title" mode="title.markup"/> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Output of Titles from Xref with Endterm needs to be handled --> + <!-- * separately from output for Endterm-less Xref --> + <xsl:template match="refsect1/title + |refentry/refsection/title + |refsynopsisdiv/title" + mode="endterm"> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:apply-templates/> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Use uppercase to render titles of all instances of Refsynopsisdiv, --> + <!-- * including in cross-references --> + <xsl:template match="refsynopsisdiv" mode="title.markup"> + <xsl:param name="allow-anchors" select="0"/> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:choose> + <xsl:when test="info/title + |refsynopsisdivinfo/title + |title"> + <xsl:apply-templates + select="(info/title + |refsynopsisdivinfo/title + |title)[1]" mode="title.markup"> + <xsl:with-param name="allow-anchors" select="$allow-anchors"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RefSynopsisDiv'"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <!-- * Use uppercase to render titles of all instances of Refnamediv, --> + <!-- * including in cross-references --> + <xsl:template match="refnamediv" mode="title.markup"> + <xsl:call-template name="string.upper"> + <xsl:with-param name="string"> + <xsl:call-template name="gentext"> + <xsl:with-param name="key" select="'RefName'"/> + </xsl:call-template> + </xsl:with-param> + </xsl:call-template> + </xsl:template> + + <xsl:template match="refnamediv" mode="xref-to"> + <xsl:apply-templates select="." mode="title.markup"/> + </xsl:template> + + <!-- ==================================================================== --> + + <!-- * suppress any title we don't otherwise process elsewhere --> + + <xsl:template match="title"/> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/synop.xsl b/docs/xsl-generic/manpages/synop.xsl new file mode 100644 index 00000000..cbca36c7 --- /dev/null +++ b/docs/xsl-generic/manpages/synop.xsl @@ -0,0 +1,305 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + +<!-- ******************************************************************** + $Id: synop.xsl 7235 2007-08-13 11:13:37Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<xsl:variable name="arg.or.sep"> |</xsl:variable> + +<!-- * Note: If you're looking for the *Synopsis* element, you won't --> +<!-- * find any code here for handling it. It's a "verbatim" --> +<!-- * environment; see the block.xsl file instead. --> + +<xsl:template match="synopfragmentref"> + <xsl:variable name="target" select="key('id',@linkend)"/> + <xsl:variable name="snum"> + <xsl:apply-templates select="$target" mode="synopfragment.number"/> + </xsl:variable> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + <xsl:text>▀</xsl:text> + <xsl:call-template name="italic"> + <xsl:with-param name="node" select="exsl:node-set(normalize-space(.))"/> + <xsl:with-param name="context" select="."/> + </xsl:call-template> +</xsl:template> + +<xsl:template match="synopfragment" mode="synopfragment.number"> + <xsl:number format="1"/> +</xsl:template> + +<xsl:template match="synopfragment"> + <xsl:variable name="snum"> + <xsl:apply-templates select="." mode="synopfragment.number"/> + </xsl:variable> + <xsl:text> </xsl:text> + <!-- * If we have a group of Synopgfragments, we only want to output a --> + <!-- * line of space before the first; so when we find a Synopfragment --> + <!-- * whose first preceding sibling is another Synopfragment, we back --> + <!-- * up one line vertically to negate the line of vertical space --> + <!-- * that's added by the .HP macro --> + <xsl:if test="preceding-sibling::*[1][self::synopfragment]"> + <xsl:text>.sp -1n </xsl:text> + </xsl:if> + <xsl:text>.HP </xsl:text> + <!-- * For each Synopfragment, make a hanging paragraph, with the --> + <!-- * indent calculated from the length of the generated number --> + <!-- * used as a reference + pluse 3 characters (for the open and --> + <!-- * close parens around the number, plus a space). --> + <xsl:value-of select="string-length (normalize-space ($snum)) + 3"/> + <xsl:text> </xsl:text> + <xsl:text>(</xsl:text> + <xsl:value-of select="$snum"/> + <xsl:text>)</xsl:text> + <xsl:text> </xsl:text> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="group|arg" name="group-or-arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:variable name="sepchar"> + <xsl:choose> + <xsl:when test="ancestor-or-self::*/@sepchar"> + <xsl:value-of select="ancestor-or-self::*/@sepchar"/> + </xsl:when> + <xsl:otherwise> + <xsl:text> </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:if test="position()>1 and + not(preceding-sibling::*[1][self::sbr])" + ><xsl:value-of select="$sepchar"/></xsl:if> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <!-- * do nothing --> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.open.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.open.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.open.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:variable name="arg"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:choose> + <xsl:when test="local-name(.) = 'arg' and not(ancestor::arg)"> + <!-- * Prevent arg contents from getting wrapped and broken up --> + <xsl:variable name="arg.wrapper"> + <Arg><xsl:value-of select="normalize-space($arg)"/></Arg> + </xsl:variable> + <xsl:apply-templates mode="prevent.line.breaking" + select="exsl:node-set($arg.wrapper)"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$rep='repeat'"> + <xsl:value-of select="$arg.rep.repeat.str"/> + </xsl:when> + <xsl:when test="$rep='norepeat'"> + <xsl:value-of select="$arg.rep.norepeat.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.rep.def.str"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="$choice='plain'"> + <xsl:if test='arg'> + <xsl:value-of select="$arg.choice.plain.close.str"/> + </xsl:if> + </xsl:when> + <xsl:when test="$choice='req'"> + <xsl:value-of select="$arg.choice.req.close.str"/> + </xsl:when> + <xsl:when test="$choice='opt'"> + <xsl:value-of select="$arg.choice.opt.close.str"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$arg.choice.def.close.str"/> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="group/arg"> + <xsl:variable name="choice" select="@choice"/> + <xsl:variable name="rep" select="@rep"/> + <xsl:if test="position()>1"><xsl:value-of select="$arg.or.sep"/></xsl:if> + <xsl:call-template name="group-or-arg"/> +</xsl:template> + +<xsl:template match="sbr"> + <xsl:text>▒</xsl:text> + <xsl:text>.br▒</xsl:text> +</xsl:template> + +<xsl:template match="cmdsynopsis"> + <!-- * if justification is enabled by default, turn it off temporarily --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it off temporarily --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy 0 </xsl:text> + </xsl:if> + <xsl:text>.HP </xsl:text> + <xsl:value-of select="string-length (normalize-space (command)) + 1"/> + <xsl:text> </xsl:text> + <xsl:apply-templates/> + <xsl:text> </xsl:text> + <!-- * if justification is enabled by default, turn it back on --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it back on --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy </xsl:text> + </xsl:if> +</xsl:template> + +<!-- ==================================================================== --> +<!-- * Funcsynopis hierarchy starts here --> +<!-- ==================================================================== --> + +<!-- * Note: If you're looking for the *Funcsynopsisinfo* element, --> +<!-- * you won't find any code here for handling it. It's a "verbatim" --> +<!-- * environment; see the block.xsl file instead. --> + +<!-- * Within funcsynopis output, disable hyphenation, and use --> +<!-- * left-aligned filling for the duration of the synopsis, so that --> +<!-- * line breaks only occur between separate paramdefs. --> +<xsl:template match="funcsynopsis"> + <!-- * if justification is enabled by default, turn it off temporarily --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad l </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it off temporarily --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy 0 </xsl:text> + </xsl:if> + <xsl:apply-templates/> + <!-- * if justification is enabled by default, turn it back on --> + <xsl:if test="$man.justify != 0"> + <xsl:text>.ad </xsl:text> + </xsl:if> + <!-- * if hyphenation is enabled by default, turn it back on --> + <xsl:if test="$man.hyphenate != 0"> + <xsl:text>.hy </xsl:text> + </xsl:if> +</xsl:template> + +<!-- * All Funcprototype content is by default rendered in bold, --> +<!-- * because the man(7) man page says this: --> +<!-- * --> +<!-- * For functions, the arguments are always specified using --> +<!-- * italics, even in the SYNOPSIS section, where the rest of --> +<!-- * the function is specified in bold --> +<!-- * --> +<!-- * Look through the contents of the man/man2 and man3 directories --> +<!-- * on your system, and you'll see that most existing pages do follow --> +<!-- * this "bold everything in function synopsis" rule. --> +<!-- * --> +<!-- * Users who don't want the bold output can choose to adjust the --> +<!-- * man.font.funcprototype parameter on their own. So even if you --> +<!-- * don't personally like the way it looks, please don't change the --> +<!-- * default to be non-bold - because it's a convention that's --> +<!-- * followed is the vast majority of existing man pages that document --> +<!-- * functions, and we need to follow it by default, like it or no. --> +<xsl:template match="funcprototype"> + <xsl:variable name="funcprototype.string.value"> + <xsl:value-of select="funcdef"/> + </xsl:variable> + <xsl:variable name="funcprototype"> + <xsl:apply-templates select="funcdef"/> + </xsl:variable> + <xsl:text>.HP </xsl:text> + <!-- * Hang Paragraph by length of string value of <funcdef> + 1 --> + <!-- * (because funcdef is always followed by one open paren char) --> + <xsl:value-of select="string-length (normalize-space ($funcprototype.string.value)) + 1"/> + <xsl:text> </xsl:text> + <xsl:text>.</xsl:text> + <xsl:value-of select="$man.font.funcprototype"/> + <xsl:text> </xsl:text> + <!-- * The following quotation mark (and the one further below) are --> + <!-- * needed to properly delimit the parts of the Funcprototype that --> + <!-- * should be rendered in the prevailing font (either Bold or Roman) --> + <!-- * from Parameter output that needs to be alternately rendered in --> + <!-- * italic. --> + <xsl:text>"</xsl:text> + <xsl:value-of select="normalize-space($funcprototype)"/> + <xsl:text>(</xsl:text> + <xsl:apply-templates select="*[local-name() != 'funcdef']"/> + <xsl:text>"</xsl:text> + <xsl:text> </xsl:text> +</xsl:template> + +<xsl:template match="funcdef"> + <xsl:apply-templates mode="prevent.line.breaking"/> +</xsl:template> + +<xsl:template match="funcdef/function"> + <xsl:apply-templates/> +</xsl:template> + +<xsl:template match="void"> + <xsl:text>void);</xsl:text> +</xsl:template> + +<xsl:template match="varargs"> + <xsl:text>...);</xsl:text> +</xsl:template> + +<xsl:template match="paramdef"> + <xsl:apply-templates mode="prevent.line.breaking" select="."/> + <xsl:choose> + <xsl:when test="following-sibling::*"> + <xsl:text>, </xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>);</xsl:text> + </xsl:otherwise> + </xsl:choose> +</xsl:template> + +<xsl:template match="paramdef/parameter"> + <!-- * We use U+2591 here in place of a normal space, because if we --> + <!-- * were to just use a normal space, it would get replaced with a --> + <!-- * non-breaking space when we run the whole Paramdef through the --> + <!-- * prevent.line.breaking template. And as far as why we're --> + <!-- * inserting the space and quotation marks around each Parameter --> + <!-- * to begin with, the reason is that we need to because we are --> + <!-- * outputting Funcsynopsis in either the "BI" or "RI" font, and --> + <!-- * the space and quotation marks delimit the text as the --> + <!-- * "alternate" or "I" text that needs to be rendered in italic. --> + <xsl:text>"░"</xsl:text> + <xsl:apply-templates/> + <xsl:text>"░"</xsl:text> +</xsl:template> + +<xsl:template match="funcparams"> + <xsl:text>(</xsl:text> + <xsl:apply-templates/> + <xsl:text>)</xsl:text> +</xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/table.xsl b/docs/xsl-generic/manpages/table.xsl new file mode 100644 index 00000000..b6e2fe2e --- /dev/null +++ b/docs/xsl-generic/manpages/table.xsl @@ -0,0 +1,633 @@ +<?xml version="1.0"?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + exclude-result-prefixes="exsl" + version='1.0'> + + <!-- ******************************************************************** + $Id: table.xsl 7177 2007-08-06 10:18:36Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + <!-- + <xsl:import href="http://docbook.sourceforge.net/release/xsl/current/html/docbook.xsl"/> + <xsl:param name="tbl.font.title">B</xsl:param> + <xsl:param name="tbl.font.headings">B</xsl:param> + --> + <xsl:param name="tbl.running.header.from.thead" select="0"/> + <xsl:param name="tbl.column.separator.char">:</xsl:param> + + <!-- ==================================================================== --> + + <!-- * This stylesheet transforms DocBook and HTML table source into --> + <!-- * tbl(1) markup. --> + <!-- * --> + <!-- * For details on tbl(1) and its markup syntaxt, see M. E. Lesk,--> + <!-- * "Tbl - A Program to Format Tables": --> + <!-- * --> + <!-- * http://cm.bell-labs.com/7thEdMan/vol2/tbl --> + <!-- * http://cm.bell-labs.com/cm/cs/doc/76/tbl.ps.gz --> + <!-- * http://www.snake.net/software/troffcvt/tbl.html --> + + <xsl:template match="table|informaltable" mode="to.tbl"> + <!--* the "source" param is an optional param; it can be any --> + <!--* string you want to use that gives some indication of the --> + <!--* source context for a table; it gets passed down to the named --> + <!--* templates that do the actual table processing; this --> + <!--* stylesheet currently uses the "source" information for --> + <!--* logging purposes --> + <xsl:param name="source"/> + <xsl:param name="title"> + <xsl:if test="local-name(.) = 'table'"> + <xsl:apply-templates select="." mode="object.title.markup.textonly"/> + </xsl:if> + </xsl:param> + <!-- * ============================================================== --> + <!-- * Set global table parameters --> + <!-- * ============================================================== --> + <!-- * First, set a few parameters based on attributes specified in --> + <!-- * the table source. --> + <xsl:param name="allbox"> + <xsl:if test="not(@frame = 'none') and not(@border = '0')"> + <!-- * By default, put a box around table and between all cells, --> + <!-- * unless frame="none" or border="0" --> + <xsl:text>allbox </xsl:text> + </xsl:if> + </xsl:param> + <xsl:param name="center"> + <!-- * If align="center", center the table. Otherwise, tbl(1) --> + <!-- * left-aligns it by default; note that there is no support --> + <!-- * in tbl(1) for specifying right alignment. --> + <xsl:if test="@align = 'center' or tgroup/@align = 'center'"> + <xsl:text>center </xsl:text> + </xsl:if> + </xsl:param> + <xsl:param name="expand"> + <!-- * If pgwide="1" or width="100%", then "expand" the table by --> + <!-- * making it "as wide as the current line length" (to quote --> + <!-- * the tbl(1) guide). --> + <xsl:if test="@pgwide = '1' or @width = '100%'"> + <xsl:text>expand </xsl:text> + </xsl:if> + </xsl:param> + + <!-- * ============================================================== --> + <!-- * Convert table to HTML --> + <!-- * ============================================================== --> + <!-- * Process the table by applying the HTML templates from the --> + <!-- * DocBook XSL stylesheets to the whole thing; because we don't --> + <!-- * override any of the <row>, <entry>, <tr>, <td>, etc. templates, --> + <!-- * the templates in the HTML stylesheets (which we import) are --> + <!-- * used to process those. --> + <xsl:param name="html-table-output"> + <xsl:choose> + <xsl:when test=".//tr"> + <!-- * If this table has a TR child, it means that it's an --> + <!-- * HTML table in the DocBook source, instead of a CALS --> + <!-- * table. So we just copy it as-is, while wrapping it --> + <!-- * in an element with same name as its original parent. --> + <xsl:for-each select="descendant-or-self::table|descendant-or-self::informaltable"> + <xsl:element name="{local-name(..)}"> + <table> + <xsl:copy-of select="*"/> + </table> + </xsl:element> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, this is a CALS table in the DocBook source, --> + <!-- * so we need to apply the templates in the HTML --> + <!-- * stylesheets to transform it into HTML before we do --> + <!-- * any further processing of it. --> + <xsl:apply-templates/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:param name="contents" select="exsl:node-set($html-table-output)"/> + + <!-- ==================================================================== --> + <!-- * Output the table --> + <!-- ==================================================================== --> + <!-- * --> + <!-- * This is the "driver" part of the code; it calls a series of named + * templates (further below) to generate the actual tbl(1) markup, --> + <!-- * including the optional "options line", required "format section", --> + <!-- * and then the actual contents of the table. --> + <!-- * --> + <!-- ==================================================================== --> + + <xsl:for-each select="$contents//table"> + <!-- * ============================================================== --> + <!-- * Output table title --> + <!-- * ============================================================== --> + <xsl:if test="$title != '' or parent::td"> + <xsl:text>.PP </xsl:text> + <xsl:text>.</xsl:text> + <xsl:value-of select="$tbl.font.title"/> + <xsl:text> </xsl:text> + <xsl:if test="parent::td"> + <xsl:text>*[nested▀table]</xsl:text> + </xsl:if> + <xsl:value-of select="normalize-space($title)"/> + <xsl:text> </xsl:text> + <xsl:text>.sp -1n </xsl:text> + </xsl:if> + + <!-- * mark the start of the table --> + <!-- * "TS" = "table start" --> + <xsl:text>.TS</xsl:text> + <xsl:if test="thead and $tbl.running.header.from.thead"> + <!-- * H = "has header" --> + <xsl:text> H</xsl:text> + </xsl:if> + <xsl:text> </xsl:text> + + <!-- * ============================================================== --> + <!-- * Output "options line" --> + <!-- * ============================================================== --> + <xsl:variable name="options-line"> + <xsl:value-of select="$allbox"/> + <xsl:value-of select="$center"/> + <xsl:value-of select="$expand"/> + <xsl:text>tab(</xsl:text> + <xsl:value-of select="$tbl.column.separator.char"/> + <xsl:text>)</xsl:text> + </xsl:variable> + <xsl:if test="normalize-space($options-line) != ''"> + <xsl:value-of select="normalize-space($options-line)"/> + <xsl:text>; </xsl:text> + </xsl:if> + + <!-- * ============================================================== --> + <!-- * Output table header rows --> + <!-- * ============================================================== --> + <xsl:if test="thead"> + <xsl:call-template name="output.rows"> + <xsl:with-param name="rows" select="thead/tr"/> + </xsl:call-template> + <xsl:text> </xsl:text> + + <!-- * mark the end of table-header rows --> + <xsl:choose> + <xsl:when test="$tbl.running.header.from.thead"> + <!-- * "TH" = "table header end" --> + <xsl:text>.TH </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * "T&" = "table continuation" and is meant just as a kind --> + <!-- * of convenience macro and is sorta equivalent to a "TE" --> + <!-- * (table end) followed immediately by a "TS" (table start); --> + <!-- * in this case, it marks the end of a table "subsection" --> + <!-- * with header rows, and the start of a subsection with body --> + <!-- * rows. It's necessary to output it here because the "TH" --> + <!-- * macro is not being output, so there's otherwise no way --> + <!-- * for tbl(1) to know we have the table "sectioned". --> + <xsl:text>.T& </xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + + <!-- * ============================================================== --> + <!-- * Output table body rows --> + <!-- * ============================================================== --> + <!-- * First create node set with all non-thead rows (tbody+tfoot), --> + <!-- * but reordered with the tfoot rows at the end of the node set --> + <xsl:variable name="rows-set"> + <xsl:copy-of select="tbody/tr|tr"/> + <xsl:copy-of select="tfoot/tr"/> + </xsl:variable> + <xsl:call-template name="output.rows"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="rows" select="exsl:node-set($rows-set)"/> + </xsl:call-template> + + <!-- * mark the end of the table --> + <xsl:text> </xsl:text> + <!-- * .TE = "Table End" --> + <xsl:text>.TE </xsl:text> + <!-- * put a blank line of space below the table --> + <xsl:text>.sp </xsl:text> + </xsl:for-each> + </xsl:template> + + <!-- ==================================================================== --> + <!-- * named templates --> + <!-- ==================================================================== --> + <!-- * --> + <!-- * All of the following are named templates that get called directly --> + <!-- * or indirectly by the main "driver" part of the code (above) --> + <!-- * --> + <!-- ==================================================================== --> + + <xsl:template name="output.rows"> + <xsl:param name="source"/> + <xsl:param name="rows"/> + <!-- * ============================================================== --> + <!-- * Flatten row set into simple list of cells --> + <!-- * ============================================================== --> + <!-- * Now we flatten the structure further into just a set of --> + <!-- * cells without the row parents. This basically creates a --> + <!-- * copy of the entire contents of the original table, but --> + <!-- * restructured in such a way that we can more easily generate --> + <!-- * the corresponding tbl(1) markup we need to output. --> + <xsl:variable name="cells-list"> + <xsl:call-template name="build.cell.list"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="rows" select="$rows"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="cells" select="exsl:node-set($cells-list)"/> + + <!-- * Output the table "format section", which tells tbl(1) how to --> + <!-- * format each row and column --> + <xsl:call-template name="create.table.format"> + <xsl:with-param name="cells" select="$cells"/> + </xsl:call-template> + + <!--* Output the formatted contents of each cell. --> + <xsl:for-each select="$cells/cell"> + <xsl:call-template name="output.cell"/> + </xsl:for-each> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Output the tbl(1)-formatted contents of each cell. --> + <!-- * ============================================================== --> + <xsl:template name="output.cell"> + <xsl:choose> + <xsl:when test="preceding-sibling::cell[1]/@row != @row or + not(preceding-sibling::cell)"> + <!-- * If the value of the "row" attribute on this cell is --> + <!-- * different from the value of that on the previous cell, it --> + <!-- * means we have a new row. So output a line break (as long --> + <!-- * as this isn't the first cell in the table) --> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise we are not at the start of a new row, so we --> + <!-- * output a tab character to delimit the contents of this --> + <!-- * cell from the contents of the next one. --> + <xsl:value-of select="$tbl.column.separator.char"/> + </xsl:otherwise> + </xsl:choose> + <xsl:choose> + <xsl:when test="@type = '^'"> + <!-- * If this is a dummy cell resulting from the presence of --> + <!-- * rowpan attribute in the source, it has no contents, so --> + <!-- * we need to handle it differently. --> + <xsl:if test="@colspan and @colspan > 1"> + <!-- * If there is a colspan attribute on this dummy row, then --> + <!-- * we need to output a tab character for each column that --> + <!-- * it spans. --> + <xsl:call-template name="copy-string"> + <xsl:with-param name="string" select="$tbl.column.separator.char"/> + <xsl:with-param name="count"> + <xsl:value-of select="@colspan - 1"/> + </xsl:with-param> + </xsl:call-template> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * Otherwise, we have a "real" cell (not a dummy one) with --> + <!-- * contents that we need to output, --> + <!-- * --> + <!-- * The "T{" and "T}" stuff are delimiters to tell tbl(1) that --> + <!-- * the delimited contents are "text blocks" that roff --> + <!-- * needs to process --> + <xsl:text>T{ </xsl:text> + <xsl:copy-of select="."/> + <xsl:text> T}</xsl:text> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Build a restructured "cell list" copy of the entire table --> + <!-- * ============================================================== --> + <xsl:template name="build.cell.list"> + <xsl:param name="source"/> + <xsl:param name="rows"/> + <xsl:param name="cell-data-unsorted"> + <!-- * This param collects all the "real" cells from the table, --> + <!-- * along with "dummy" rows that we generate for keeping --> + <!-- * track of Rowspan instances. --> + <xsl:apply-templates select="$rows" mode="cell.list"> + <xsl:with-param name="source" select="$source"/> + </xsl:apply-templates> + </xsl:param> + <xsl:param name="cell-data-sorted"> + <!-- * Sort the cells so that the dummy cells get put where we --> + <!-- * need them in the structure. Note that we need to specify --> + <!-- * data-type="number" here because the default sorting method --> + <!-- * for xsl:sort is "text" (alphabetical). --> + <xsl:for-each select="exsl:node-set($cell-data-unsorted)/cell"> + <xsl:sort select="@row" data-type="number"/> + <xsl:sort select="@slot" data-type="number"/> + <xsl:copy-of select="."/> + </xsl:for-each> + </xsl:param> + <!-- * Return the sorted cell list --> + <xsl:copy-of select="$cell-data-sorted"/> + </xsl:template> + + <xsl:template match="tr" mode="cell.list"> + <xsl:param name="source"/> + <xsl:variable name="row"> + <xsl:value-of select="count(preceding-sibling::tr) + 1"/> + </xsl:variable> + <xsl:for-each select="td|th"> + <xsl:call-template name="cell"> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="row" select="$row"/> + <!-- * pass on the element name so we can select the appropriate --> + <!-- * roff font for styling the cell contents --> + <xsl:with-param name="class" select="name(.)"/> + </xsl:call-template> + </xsl:for-each> + </xsl:template> + + <xsl:template name="cell"> + <xsl:param name="source"/> + <xsl:param name="row"/> + <xsl:param name="class"/> + <xsl:param name="slot"> + <!-- * The "slot" is the horizontal position of this cell (usually --> + <!-- * just the same as its column, but not so when it is preceded --> + <!-- * by cells that have colspans or cells in preceding rows that --> + <!-- * that have rowspans). --> + <xsl:value-of select="position()"/> + </xsl:param> + <!-- * For each real TD cell, create a Cell instance; contents will --> + <!-- * be the roff-formatted contents of its original table cell. --> + <cell type="" + row="{$row}" + slot="{$slot}" + class="{$class}" + colspan="{@colspan}" + align="{@align}" + valign="{@valign}" + > + <xsl:choose> + <xsl:when test=".//tr"> + <xsl:call-template name="log.message"> + <xsl:with-param name="level">Warn</xsl:with-param> + <xsl:with-param name="source" select="$source"/> + <xsl:with-param name="context-desc">tbl convert</xsl:with-param> + <xsl:with-param name="message"> + <xsl:text>Extracted a nested table</xsl:text> + </xsl:with-param> + </xsl:call-template> + <xsl:text>[\fInested▀table\fR]* </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Apply templates to the child contents of this cell, to --> + <!-- * transform them into marked-up roff. --> + <xsl:variable name="contents"> + <xsl:apply-templates/> + </xsl:variable> + <!-- * We now have the contents in roff (plain-text) form, --> + <!-- * but we may also still have unnecessary whitespace at --> + <!-- * the beginning and/or end of it, so trim it off. --> + <xsl:call-template name="trim.text"> + <xsl:with-param name="contents" select="$contents"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </cell> + + <!-- * For each instance of a rowspan attribute found, we create N --> + <!-- * dummy cells, where N is equal to the value of the rowspan. --> + <xsl:if test="@rowspan and @rowspan > 0"> + <!-- * If this cell is preceded in the same row by cells that --> + <!-- * have colspan attributes, then we need to calculate the --> + <!-- * "offset" caused by those colspan instances; the formula --> + <!-- * is to (1) check for all the preceding cells that have --> + <!-- * colspan attributes that are not empty and which have a --> + <!-- * value greater than 1, then (2) take the sum of the values --> + <!-- * of all those colspan attributes, and subtract from that --> + <!-- * the number of such colspan instances found. --> + <xsl:variable name="colspan-offset"> + <xsl:value-of + select="sum(preceding-sibling::td[@colspan != '' + and @colspan > 1]/@colspan) - + count(preceding-sibling::td[@colspan != '' + and @colspan > 1]/@colspan)"/> + </xsl:variable> + <xsl:call-template name="create.dummy.cells"> + <xsl:with-param name="row" select="$row + 1"/> + <!-- * The slot value on each dummy cell must be offset by the --> + <!-- * value of $colspan-offset to adjust for preceding colpans --> + <xsl:with-param name="slot" select="$slot + $colspan-offset"/> + <xsl:with-param name="colspan" select="@colspan"/> + <xsl:with-param name="rowspan" select="@rowspan"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <xsl:template name="create.dummy.cells"> + <xsl:param name="row"/> + <xsl:param name="slot"/> + <xsl:param name="colspan"/> + <xsl:param name="rowspan"/> + <xsl:choose> + <xsl:when test="$rowspan > 1"> + <!-- * Tail recurse until we have no more rowspans, creating --> + <!-- * an empty dummy cell each time. The type value, '^' --> + <!-- * is the marker that tbl(1) uses to indicate a --> + <!-- * "vertically spanned heading". --> + <cell row="{$row}" slot="{$slot}" type="^" colspan="{@colspan}"/> + <xsl:call-template name="create.dummy.cells"> + <xsl:with-param name="row" select="$row + 1"/> + <xsl:with-param name="slot" select="$slot"/> + <xsl:with-param name="colspan" select="$colspan"/> + <xsl:with-param name="rowspan" select="$rowspan - 1"/> + </xsl:call-template> + </xsl:when> + </xsl:choose> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * Build the "format section" for the table --> + <!-- * ============================================================== --> + <!-- * Description from the tbl(1) guide: --> + <!-- * --> + <!-- * "The format section of the table specifies the layout of the --> + <!-- * columns. Each line in this section corresponds to one line of --> + <!-- * the table... and each line contains a key-letter for each --> + <!-- * column of the table." --> + <xsl:template name="create.table.format"> + <xsl:param name="cells"/> + <xsl:apply-templates mode="table.format" select="$cells"/> + <!-- * last line of table format section must end with a dot --> + <xsl:text>.</xsl:text> + </xsl:template> + + <xsl:template match="cell" mode="table.format"> + <xsl:choose> + <xsl:when test="preceding-sibling::cell[1]/@row != @row"> + <!-- * If the value of the row attribute on this cell is --> + <!-- * different from the value of that on the previous cell, it --> + <!-- * means we have a new row. So output a line break. --> + <xsl:text> </xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * If this isn't the first cell, output a space before it to --> + <!-- * separate it from the preceding key letter. --> + <xsl:if test="position() != 1"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + <!-- * Select an appropriate "alignment" key letter based on this --> + <!-- * cell's attributes. --> + <xsl:choose> + <xsl:when test="@type = '^'"> + <xsl:text>^</xsl:text> + </xsl:when> + <xsl:when test="@align = 'center'"> + <xsl:text>c</xsl:text> + </xsl:when> + <xsl:when test="@align = 'right'"> + <xsl:text>r</xsl:text> + </xsl:when> + <xsl:when test="@align = 'char'"> + <xsl:text>n</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * Default to left alignment. --> + <xsl:text>l</xsl:text> + </xsl:otherwise> + </xsl:choose> + <!-- * By default, tbl(1) vertically centers cell contents within --> + <!-- * their cells; the "t" key latter tells it to top-align the --> + <!-- * contents instead. Note that tbl(1) has no options for --> + <!-- * bottom or baseline alignment. --> + <xsl:if test="@valign = 'top'"> + <xsl:text>t</xsl:text> + </xsl:if> + <xsl:if test="@class = 'th'"> + <!-- * If this is a heading row, generate a font indicator (B or I), --> + <!-- * or if the value of $tbl.font.headings is empty, nothing. --> + <xsl:value-of select="$tbl.font.headings"/> + </xsl:if> + <!-- * We only need to deal with colspans whose value is greater --> + <!-- * than one (a colspan="1" is the same as having no colspan --> + <!-- * attribute at all). --> + <xsl:if test="@colspan > 1"> + <xsl:call-template name="process.colspan"> + <xsl:with-param name="colspan" select="@colspan - 1"/> + <xsl:with-param name="type" select="@type"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <xsl:template name="process.colspan"> + <xsl:param name="colspan"/> + <xsl:param name="type"/> + <!-- * Output a space to separate this key letter from preceding one. --> + <xsl:text> </xsl:text> + <xsl:choose> + <xsl:when test="$type = '^'"> + <!-- * A '^' ("vertically spanned heading" marker) indicates --> + <!-- * that the "parent" of this spanned cell is a dummy cell; --> + <!-- * in this case, we need to generate a '^' instead of the --> + <!-- * normal 's'. --> + <xsl:text>^</xsl:text> + </xsl:when> + <xsl:otherwise> + <!-- * s = 'spanned heading' --> + <xsl:text>s</xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:if test="$colspan > 1"> + <!-- * Tail recurse until we have no more colspans, outputting --> + <!-- * another marker each time. --> + <xsl:call-template name="process.colspan"> + <xsl:with-param name="colspan" select="$colspan - 1"/> + <xsl:with-param name="type" select="$type"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <!-- * ============================================================== --> + <!-- * colgroup and col --> + <!-- * ============================================================== --> + <!-- * We currently don't do anything with colgroup. Not sure if it --> + <!-- * is widely used enough to bother adding support for it --> + <xsl:template match="colgroup"/> + <xsl:template match="col"/> + + <!-- * ============================================================== --> + <!-- * table footnotes --> + <!-- * ============================================================== --> + <xsl:template match="footnote" mode="table.footnote.mode"> + <xsl:variable name="footnotes" select=".//footnote"/> + <xsl:variable name="table.footnotes" + select=".//tgroup//footnote"/> + <xsl:value-of select="$man.table.footnotes.divider"/> + <xsl:text> </xsl:text> + <xsl:text>.br </xsl:text> + <xsl:apply-templates select="*[1]" mode="footnote.body.number"/> + <xsl:apply-templates select="*[position() > 1]"/> + </xsl:template> + + <!-- * The following template for footnote.body.number mode was just --> + <!-- * lifted from the HTML stylesheets with some minor adjustments --> + <xsl:template match="*" mode="footnote.body.number"> + <xsl:variable name="name"> + <xsl:text>ftn.</xsl:text> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="ancestor::footnote"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="href"> + <xsl:text>#</xsl:text> + <xsl:call-template name="object.id"> + <xsl:with-param name="object" select="ancestor::footnote"/> + </xsl:call-template> + </xsl:variable> + <xsl:variable name="footnote.mark"> + <xsl:text>[</xsl:text> + <xsl:apply-templates select="ancestor::footnote" + mode="footnote.number"/> + <xsl:text>] </xsl:text> + </xsl:variable> + <xsl:variable name="html"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:choose> + <xsl:when test="function-available('exsl:node-set')"> + <xsl:variable name="html-nodes" select="exsl:node-set($html)"/> + <xsl:choose> + <xsl:when test="$html-nodes//p"> + <xsl:apply-templates select="$html-nodes" mode="insert.html.p"> + <xsl:with-param name="mark" select="$footnote.mark"/> + </xsl:apply-templates> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$html-nodes" mode="insert.html.text"> + <xsl:with-param name="mark" select="$footnote.mark"/> + </xsl:apply-templates> + </xsl:otherwise> + </xsl:choose> + </xsl:when> + <xsl:otherwise> + <xsl:copy-of select="$html"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- * The HTML stylesheets output <sup><a>...</a></sup> around --> + <!-- * footnote markers in tables --> + <xsl:template match="th/sup"> + <xsl:apply-templates/> + </xsl:template> + <xsl:template match="a"> + <xsl:apply-templates/> + </xsl:template> + +</xsl:stylesheet> diff --git a/docs/xsl-generic/manpages/utility.xsl b/docs/xsl-generic/manpages/utility.xsl new file mode 100644 index 00000000..cf72479b --- /dev/null +++ b/docs/xsl-generic/manpages/utility.xsl @@ -0,0 +1,452 @@ +<?xml version='1.0'?> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:exsl="http://exslt.org/common" + xmlns:dyn="http://exslt.org/dynamic" + xmlns:saxon="http://icl.com/saxon" + exclude-result-prefixes="exsl dyn saxon" + version='1.0'> + +<!-- ******************************************************************** + $Id: utility.xsl 6843 2007-06-20 12:21:13Z xmldoc $ + ******************************************************************** + + This file is part of the XSL DocBook Stylesheet distribution. + See ../README or http://docbook.sf.net/release/xsl/current/ for + copyright and other information. + + ******************************************************************** --> + +<!-- ==================================================================== --> + +<!-- * This file contains "utility" templates that are called multiple --> +<!-- * times per each Refentry. --> + +<!-- ==================================================================== --> + + <!-- * NOTE TO DEVELOPERS: For ease of maintenance, the current --> + <!-- * manpages stylesheets use the "bold" and "italic" named --> + <!-- * templates for anything and everything that needs to get --> + <!-- * boldfaced or italicized. --> + <!-- * --> + <!-- * So if you add anything that needs bold or italic character --> + <!-- * formatting, try to apply these templates to it rather than --> + <!-- * writing separate code to format it. This can be a little odd if --> + <!-- * the content you want to format is not element content; in those --> + <!-- * cases, you need to turn it into element content before applying --> + <!-- * the template; see examples of this in the existing code. --> + + <xsl:template name="bold"> + <xsl:param name="node"/> + <xsl:param name="context"/> + <xsl:choose> + <xsl:when test="not($context[ancestor::title])"> + <xsl:for-each select="$node/node()"> + <xsl:text>\fB</xsl:text> + <xsl:apply-templates select="."/> + <xsl:text>\fR</xsl:text> + </xsl:for-each> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="$node/node()"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template name="italic"> + <xsl:param name="node"/> + <xsl:param name="context"/> + <xsl:for-each select="$node/node()"> + <xsl:text>\fI</xsl:text> + <xsl:apply-templates select="."/> + <xsl:text>\fR</xsl:text> + </xsl:for-each> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * NOTE TO DEVELOPERS: For ease of maintenance, the current --> + <!-- * manpages stylesheets use the mode="prevent.line.breaking" --> + <!-- * templates for anything and everything that needs to have --> + <!-- * embedded spaces turned into no-break spaces in output - in --> + <!-- * order to prevent that output from getting broken across lines --> + <!-- * --> + <!-- * So if you add anything that whose output, try to apply this --> + <!-- * template to it rather than writing separate code to format --> + <!-- * it. This can be a little odd if the content you want to --> + <!-- * format is not element content; in those cases, you need to --> + <!-- * turn it into element content before applying the template; --> + <!-- * see examples of this in the existing code. --> + <!-- * --> + <!-- * This template is currently called by the funcdef and paramdef --> + <!-- * and group/arg templates. --> + <xsl:template mode="prevent.line.breaking" match="*"> + <xsl:variable name="rcontent"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:variable name="content"> + <xsl:value-of select="normalize-space($rcontent)"/> + </xsl:variable> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="' '"/> + <!-- * U+2580 is a "UPPER HALF BLOCK"; we use it here because --> + <!-- * if we were to just use a normal space, it would get --> + <!-- * replaced when normalization is done. We replace it --> + <!-- * later with the groff markup for non-breaking space. --> + <xsl:with-param name="replacement" select="'▀'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="suppress.hyphenation"> + <!-- * we need to suppress hyphenation inline only if hyphenation is --> + <!-- * actually on, and even then only outside of Cmdsynopsis and --> + <!-- * Funcsynopsis, where it is already always turned off --> + <xsl:if test="$man.hyphenate != 0 and + not(ancestor::cmdsynopsis) and + not(ancestor::funcsynopsis)"> + <xsl:text>\%</xsl:text> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The replace.dots.and.dashes template is used to cause real --> + <!-- * dots and dashes to be output in the top comment (instead of --> + <!-- * escaped ones as in the source for the text displayed in the --> + <!-- * body of the page) --> + <xsl:template name="replace.dots.and.dashes"> + <xsl:param name="content"> + <xsl:apply-templates/> + </xsl:param> + <xsl:variable name="dot-content"> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$content"/> + <xsl:with-param name="target" select="'\.'"/> + <xsl:with-param name="replacement" select="'.'"/> + </xsl:call-template> + </xsl:variable> + <xsl:call-template name="string.subst"> + <xsl:with-param name="string" select="$dot-content"/> + <xsl:with-param name="target" select="'\-'"/> + <xsl:with-param name="replacement" select="'-'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The nested-section-title template is called for refsect3, and any --> + <!-- * refsection nested more than 2 levels deep. --> + <xsl:template name="nested-section-title"> + <!-- * The next few lines are some arcane roff code to control line --> + <!-- * spacing after headings. --> + <xsl:text>.sp </xsl:text> + <xsl:text>.it 1 an-trap </xsl:text> + <xsl:text>.nr an-no-space-flag 1 </xsl:text> + <xsl:text>.nr an-break-flag 1 </xsl:text> + <xsl:text>.br </xsl:text> + <!-- * make title wrapper so that we can use mode="bold" template to --> + <!-- * apply character formatting to it --> + <xsl:variable name="title.wrapper"> + <bold><xsl:choose> + <xsl:when test="title"> + <xsl:value-of select="normalize-space(title[1])"/> + </xsl:when> + <xsl:otherwise> + <xsl:apply-templates select="." mode="object.title.markup.textonly"/> + </xsl:otherwise> + </xsl:choose></bold> + </xsl:variable> + <xsl:call-template name="mark.subheading"/> + <xsl:apply-templates mode="bold" select="exsl:node-set($title.wrapper)"/> + <xsl:text> </xsl:text> + <xsl:call-template name="mark.subheading"/> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The mixed-block template jumps through a few hoops to deal with --> + <!-- * mixed-content blocks, so that we don't end up munging verbatim --> + <!-- * environments or lists and so that we don't gobble up whitespace --> + <!-- * when we shouldn't --> + <xsl:template name="mixed-block"> + <xsl:for-each select="node()"> + <xsl:choose> + <!-- * Check to see if this node is a verbatim environment. --> + <!-- * If so, put a line of space before it. --> + <!-- * --> + <!-- * Yes, address and synopsis are vertabim environments. --> + <!-- * --> + <!-- * The code here previously also treated informaltable as a --> + <!-- * verbatim, presumably to support some kludge; I removed it --> + <xsl:when test="self::address|self::literallayout|self::programlisting| + self::screen|self::synopsis"> + <xsl:text> </xsl:text> + <xsl:text>.sp </xsl:text> + <xsl:call-template name="mark.up.block.start"/> + <xsl:apply-templates select="."/> + </xsl:when> + <!-- * Check to see if this node is a list; if it is, we don't --> + <!-- * want to normalize-space(), so we just apply-templates. --> + <!-- * Do same for all admonitions --> + <xsl:when test="(self::itemizedlist|self::orderedlist| + self::variablelist|self::glosslist| + self::simplelist[@type !='inline']| + self::segmentedlist| + self::caution|self::important| + self::note|self::tip|self::warning| + self::table|self::informaltable)"> + <xsl:call-template name="mark.up.block.start"/> + <xsl:apply-templates select="."/> + </xsl:when> + <xsl:when test="self::text()"> + <!-- * Check to see if this is a text node. --> + <!-- * --> + <!-- * If so, replace all whitespace at the beginning or end of it --> + <!-- * with a single linebreak. --> + <!-- * --> + <xsl:variable name="content"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:if + test="starts-with(translate(.,' ',' '), ' ') + and preceding-sibling::node()[1][name(.)!=''] + and normalize-space($content) != '' + and not( + preceding-sibling::*[1][ + self::caution or + self::important or + self::note or + self::tip or + self::warning or + self::variablelist or + self::glosslist or + self::itemizedlist or + self::orderedlist or + self::segmentedlist or + self::procedure or + self::address or + self::literallayout or + self::programlisting or + self::screen or + self::table or + self::informaltable + ] + ) + "> + <xsl:text> </xsl:text> + </xsl:if> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if + test="(translate(substring(., string-length(.), 1),' ',' ') = ' ' + and following-sibling::node()[1][name(.)!='']) + or following-sibling::node()[1][self::comment()] + or following-sibling::node()[1][self::processing-instruction()] + "> + <xsl:if test="normalize-space($content) != '' + or concat(normalize-space($content), ' ') = ' '"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <!-- * At this point, we know that this node is not a verbatim --> + <!-- * environment, list, admonition, or text node; so we can --> + <!-- * safely normalize-space() it. --> + <xsl:variable name="content"> + <xsl:apply-templates select="."/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + <xsl:call-template name="mark.up.block.end"/> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * Footnote and annotation contents are displayed using a hanging --> + <!-- * indent out to $man.indent.width If a paragraph-level block --> + <!-- * element (verbatim, list, or admonition) is the first block --> + <!-- * element nested at its same level within the same footnote or --> + <!-- * annotation, then we push it over by the same indent width. --> + <!-- * --> + <!-- * We don't reset the indent for each following sibling, but --> + <!-- * instead do it after for-eaching over all block siblings at --> + <!-- * the same level. So the effect is that if there are any --> + <!-- * following-sibling blocks after the block that starts this --> + <!-- * indent, then they just retain the indent that was already set --> + + <xsl:template name="mark.up.block.start"> + <xsl:choose> + <xsl:when test="(ancestor::footnote + or ancestor::annotation)"> + <xsl:if test="not(preceding-sibling::address| + preceding-sibling::literallayout| + preceding-sibling::programlisting| + preceding-sibling::screen| + preceding-sibling::synopsis| + preceding-sibling::itemizedlist| + preceding-sibling::orderedlist| + preceding-sibling::variablelist| + preceding-sibling::glosslist| + preceding-sibling::simplelist[@type !='inline']| + preceding-sibling::segmentedlist| + preceding-sibling::caution| + preceding-sibling::important| + preceding-sibling::note| + preceding-sibling::tip| + preceding-sibling::warning| + preceding-sibling::table| + preceding-sibling::informaltable + )"> + <xsl:text>.RS</xsl:text> + <xsl:if test="not($list-indent = '')"> + <xsl:text> </xsl:text> + <xsl:value-of select="$list-indent"/> + </xsl:if> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:when> + </xsl:choose> + </xsl:template> + + <!-- * Check to see if we were called from a block within a footnote or --> + <!-- * annotation; if so, and the block contains any nested block --> + <!-- * content, then we know the mark.up.block.end template was already --> + <!-- * called to generate a .RS macro to indent that nested block --> + <!-- * content; so we need to generate a .RE to set the margin back to --> + <!-- * where it was prior to the .RS call. --> + <xsl:template name="mark.up.block.end"> + <xsl:if test="(ancestor::footnote + or ancestor::annotation)"> + <xsl:if test="address| + literallayout| + programlisting| + screen| + synopsis| + itemizedlist| + orderedlist| + variablelist| + glosslist| + simplelist[@type !='inline']| + segmentedlist| + caution| + important| + note| + tip| + warning| + table| + informaltable"> + <xsl:text> </xsl:text> + <xsl:text>.RE</xsl:text> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:if> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * The person.name template in the HTML stylesheets outputs extra --> + <!-- * spaces that we need to strip out for manpages output. This --> + <!-- * template calls person.name, then tries to do some smart --> + <!-- * normalization of the result tree fragment from that. --> + <xsl:template name="person.name.normalized"> + <xsl:variable name="contents"> + <xsl:call-template name="person.name"/> + </xsl:variable> + <!-- * We put the output of person.name into a node-set and then we --> + <!-- * check it node-by-node and strip out space only where needed. --> + <xsl:variable name="contents.tree" select="exsl:node-set($contents)"/> + <xsl:for-each select="$contents.tree/node()"> + <xsl:choose> + <!-- * We don't want to monkey with single spaces or commas/periods --> + <!-- * followed by spaces, because those are bits of text that are --> + <!-- * actually generated by the person.name template itself (that --> + <!-- * is, they're not in the source). So, we preserve them. --> + <xsl:when test=". = ' ' or . = ', ' or . = '. '"> + <xsl:value-of select="."/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="normalize-space(.)"/> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </xsl:template> + + <!-- ================================================================== --> + + <xsl:template name="make.adjusted.man.filename"> + <xsl:param name="name"/> + <xsl:param name="lang"/> + <xsl:param name="name.with.lang"> + <xsl:choose> + <xsl:when test="$lang != 'en' + and not($man.output.lang.in.name.enabled = 0) + and ($man.output.subdirs.enabled = 0 or + $man.output.in.separate.dir = 0)"> + <!-- * $lang is not en (English) --> + <!-- * AND user has specified man.output.lang.in.name.enabled --> + <!-- * AND doesn't want output going into separate dirs, --> + <!-- * SO... we include the $lang value in the filename; e.g., --> + <!-- * foo.ja.1 --> + <xsl:value-of select="concat($name, '.', $lang)"/> + </xsl:when> + <xsl:otherwise> + <!-- * user either has man.output.lang.in.name.enabled unset --> + <!-- * or has set it but also has man.output.subdirs.enabled --> + <!-- * set (in which case the $lang value is used to add a --> + <!-- * $lang subdir in the pathname); in either case, we don't --> + <!-- * want to include the $lang in the filename --> + <xsl:value-of select="$name"/> + </xsl:otherwise> + </xsl:choose> + </xsl:param> + <xsl:param name="section"/> + <xsl:param name="dirname"> + <xsl:if test="not($man.output.in.separate.dir = 0)"> + <xsl:choose> + <xsl:when test="not($man.output.subdirs.enabled = 0)"> + <xsl:variable name="lang.subdir"> + <xsl:if test="not($man.output.lang.in.name.enabled = 0)"> + <!-- * user has man.output.lang.in.name.enabled set, so --> + <!-- * we need to add a $lang subdir --> + <xsl:value-of select="concat($lang, '/')"/> + </xsl:if> + </xsl:variable> + <xsl:value-of + select="concat($man.output.base.dir, $lang.subdir, + 'man', normalize-space($section), '/')"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$man.output.base.dir"/> + </xsl:otherwise> + </xsl:choose> + </xsl:if> + </xsl:param> + <xsl:call-template name="string.subst"> + <!-- * To create the man filename, replace any spaces in filename with --> + <!-- * underscores and then append a dot plus a section number. --> + <xsl:with-param name="string" + select="concat($dirname, + normalize-space($name.with.lang), + '.', normalize-space($section))"/> + <xsl:with-param name="target" select="' '"/> + <xsl:with-param name="replacement" select="'_'"/> + </xsl:call-template> + </xsl:template> + + <!-- ================================================================== --> + + <!-- * Put a horizontal rule or other divider around section titles --> + <!-- * in roff source (just to make things easier to read). --> + <xsl:template name="mark.subheading"> + <xsl:if test="$man.subheading.divider.enabled != 0"> + <xsl:text>.\" </xsl:text> + <xsl:value-of select="$man.subheading.divider"/> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/infrastructure/BoxPlatform.pm.in b/infrastructure/BoxPlatform.pm.in new file mode 100644 index 00000000..8f9daa81 --- /dev/null +++ b/infrastructure/BoxPlatform.pm.in @@ -0,0 +1,158 @@ +package BoxPlatform; +use Exporter; +@ISA = qw/Exporter/; +@EXPORT = qw/$build_os $build_os_ver $ac_target $ac_target_cpu $ac_target_vendor $ac_target_os $make_command $bsd_make $platform_define $platform_cpu $gcc_v3 $product_version $product_name $install_into_dir $sub_make_options $platform_compile_line_extra $platform_link_line_extra $platform_lib_files $platform_exe_ext $target_windows $target_msvc/; + +BEGIN +{ + # which OS are we building under? + $ac_target = '@target@'; + $ac_target_cpu = '@target_cpu@'; + $ac_target_vendor = '@target_vendor@'; + $ac_target_os = '@target_os@'; + $target_windows = 0; + + if ($^O eq "MSWin32" and not -x "/usr/bin/uname") + { + $target_windows = 1; + $target_msvc = 1; + $build_os = "winnt"; + eval "use Win32"; + $build_os_ver = Win32::GetOSName(); + } + else + { + $target_windows = 1 if $ac_target_os =~ m'^mingw32' + or $ac_target_os eq "winnt"; + $target_msvc = 0; + $build_os = `uname`; + $build_os_ver = `uname -r`; + chomp $build_os; + chomp $build_os_ver; + } + + # 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/; + $build_os = 'MINGW32' if $build_os =~ m/MINGW32/; + + if ($build_os eq 'Darwin') { + $xcode_ver = `xcodebuild -version | awk '/^Xcode/ {print \$2}'` + } + + if ($build_os eq 'Darwin' and $xcode_ver < 4) + { + $make_command = 'bsdmake'; + $bsd_make = 1; + } + elsif ($build_os eq 'SunOS') + { + if ($build_os_ver <= 5.10) + { + $make_command = 'gmake'; + $bsd_make = 0; + } + else + { + $make_command = 'bmake'; + $bsd_make = 1; + } + } + else + { + $make_command = 'make'; + $bsd_make = ($build_os ne 'Linux' && $build_os ne 'CYGWIN' && + $build_os ne "MINGW32" && $build_os ne 'GNU/kFreeBSD' && + $build_os ne 'Darwin'); + } + + # blank extra flags by default + $platform_compile_line_extra = ''; + $platform_link_line_extra = ''; + $platform_lib_files = '@LIBS@'; + $platform_exe_ext = '@EXEEXT@'; + + # get version + my $version_file = "VERSION.txt"; + if (not -r $version_file) { $version_file = "../../$version_file" } + die "missing version file: $version_file" unless $version_file; + + open VERSION, $version_file or die "$version_file: $!"; + $product_version = <VERSION>; + chomp $product_version; + $product_name = <VERSION>; + chomp $product_name; + close VERSION; + + if($product_version =~ /USE_SVN_VERSION/) + { + # for developers, use Git version (SVN is no more): + my $gitversion = `git rev-parse HEAD`; + chomp $gitversion; + $product_version =~ s/USE_SVN_VERSION/git_$gitversion/; + } + + # where to put the files + $install_into_dir = '@sbindir_expanded@'; + + # 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') + { + # test for fink installation + if(-d '/sw/include' && -d '/sw/lib') + { + print STDERR "Fink installation detected, will use headers and libraries\n"; + $platform_compile_line_extra = '-I/sw/include '; + $platform_link_line_extra = '-L/sw/lib '; + } + } +} + +sub make_flag +{ + if($bsd_make) + { + return "-D $_[0]" + } + return $_[0].'=1'; +} + +sub parcel_root +{ + my $tos = $_[1] || $ac_target; + return $product_name.'-'.$product_version.'-'.$_[0].'-'.$tos; +} + +sub parcel_dir +{ + 'parcels/'.parcel_root($_[0], $_[1]) +} + +sub parcel_target +{ + parcel_dir($_[0]).'.tgz' +} + +1; + diff --git a/infrastructure/buildenv-testmain-template.cpp b/infrastructure/buildenv-testmain-template.cpp new file mode 100644 index 00000000..d946c25d --- /dev/null +++ b/infrastructure/buildenv-testmain-template.cpp @@ -0,0 +1,438 @@ +// +// AUTOMATICALLY GENERATED FILE +// do not edit +// +// Note that infrastructure/buildenv-testmain-template.cpp is NOT +// auto-generated, but test/*/_main.cpp are generated from it. +// + + +// -------------------------------------------------------------------------- +// +// File +// Name: testmain.template.h +// Purpose: Template file for running tests +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdlib.h> +#include <stdio.h> + +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif + +#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 + +#include <exception> +#include <iostream> +#include <list> +#include <string> + +#include "box_getopt.h" +#include "depot.h" +#include "Logging.h" +#include "Test.h" +#include "Timer.h" + +#include "MemLeakFindOn.h" + +int test(int argc, const char *argv[]); + +#ifdef BOX_RELEASE_BUILD + #define MODE_TEXT "release" +#else + #define MODE_TEXT "debug" +#endif + +#ifdef WIN32 + #define QUIET_PROCESS "-Q" +#else + #define QUIET_PROCESS "" +#endif + +std::string bbackupd_args = QUIET_PROCESS, + bbstored_args = QUIET_PROCESS, + bbackupquery_args, + test_args; + +bool filedes_initialised = false; + +#ifdef WIN32 + +// any way to check for open file descriptors on Win32? +inline bool check_filedes(bool x) { return true; } +inline bool checkfilesleftopen() { return true; } + +#else // !WIN32 + +#define FILEDES_MAX 256 + +typedef enum +{ + OPEN, + CLOSED, + SYSLOG, + STILLOPEN, + LEAKED, +} +filedes_t; + +filedes_t filedes_open[FILEDES_MAX]; + +bool check_filedes(bool report) +{ + bool allOk = true; + + // See how many file descriptors there are with values < 256. + // In order to avoid disturbing things, we scan the file descriptors + // first, marking the ones that were OPEN at startup (report == FALSE) + // as STILLOPEN and the ones that were not as LEAKED. Then we run + // through again and print the results. + for(int d = 0; d < FILEDES_MAX; ++d) + { + if(::fcntl(d, F_GETFD) != -1) + { + // File descriptor obviously exists, but is it /dev/log? + // Mark it as OPEN for now, and we'll find out later. + if(report) + { + if(filedes_open[d] == OPEN) + { + filedes_open[d] = STILLOPEN; + } + else + { + filedes_open[d] = LEAKED; + } + } + else + { + filedes_open[d] = OPEN; + } + } + else + { + filedes_open[d] = CLOSED; + } + } + + if(!report) + { + filedes_initialised = true; + return true; + } + + // Now loop again, reporting differences. + for(int d = 0; d < FILEDES_MAX; ++d) + { + if(filedes_open[d] != LEAKED) + { + continue; + } + + bool stat_success = false; + struct stat st; + if(fstat(d, &st) == 0) + { + stat_success = true; + + if(st.st_mode & S_IFSOCK) + { + char buffer[256]; + socklen_t addrlen = sizeof(buffer); + +#ifdef HAVE_GETPEERNAME + if(getpeername(d, (sockaddr*)buffer, &addrlen) != 0) + { + BOX_LOG_SYS_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")) + { + // it's a syslog socket, ignore it + filedes_open[d] = SYSLOG; + } + } +#endif // HAVE_GETPEERNAME + } + } + + 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) + { + int m = st.st_mode; + #define flag(x) ((m & x) ? #x " " : "") + BOX_FATAL("File descriptor " << d << + " left open (type == " << + flag(S_IFIFO) << + flag(S_IFCHR) << + flag(S_IFDIR) << + flag(S_IFBLK) << + flag(S_IFREG) << + flag(S_IFLNK) << + flag(S_IFSOCK) << + " or " << m << ")"); + allOk = false; + } + else + { + BOX_FATAL("File descriptor " << d << + " left open (and stat failed)"); + allOk = false; + } + } + + if (!report && allOk) + { + filedes_initialised = true; + } + + return allOk; +} + +bool checkfilesleftopen() +{ + if(!filedes_initialised) + { + // Not used correctly, pretend that there were things + // left open so this gets investigated + BOX_FATAL("File descriptor test was not initialised"); + return false; + } + + // Count the file descriptors open + return check_filedes(true); +} + +#endif + +int Usage(const std::string& ProgramName) +{ + std::cout << + "(built with QDBM " << dpversion << ")\n" + "\n" + "Usage: " << ProgramName << " [options]\n" + "\n" + "Options:\n" + " -c/--bbackupd-args <args> Arguments to pass to bbackupd/BackupDaemon\n" + " -s/--bbstored-args <args> Arguments to pass to bbstored/BackupStoreDaemon\n" + " -d/--test-daemon-args <args> Arguments to pass to TestDaemon\n" + " -e/--execute-only <test> Execute only specific named test, can repeat\n" + " -h/--help Show this command-line help\n" + << Logging::OptionParser::GetUsageString(); + return 0; +} + +int main(int argc, char * const * argv) +{ + // Start memory leak testing + MEMLEAKFINDER_START + + Logging::SetProgramName(BOX_MODULE); + + struct option longopts[] = + { + { "bbackupd-args", required_argument, NULL, 'c' }, + { "bbstored-args", required_argument, NULL, 's' }, + { "test-daemon-args", required_argument, NULL, 'd' }, + { "execute-only", required_argument, NULL, 'e' }, + { "help", no_argument, NULL, 'h' }, + { NULL, 0, NULL, 0 } + }; + + int c; + std::string options("c:d:e:hs:"); + options += Logging::OptionParser::GetOptionString(); + Logging::OptionParser LogLevel; + + while ((c = getopt_long(argc, argv, options.c_str(), longopts, NULL)) + != -1) + { + switch(c) + { + case 'c': + { + if (bbackupd_args.length() > 0) + { + bbackupd_args += " "; + } + bbackupd_args += optarg; + } + break; + + case 'd': + { + if (test_args.length() > 0) + { + test_args += " "; + } + test_args += optarg; + } + break; + + case 'e': + { + run_only_named_tests.push_back(optarg); + } + break; + + case 'h': + { + return Usage(argv[0]); + } + break; + + case 's': + { + bbstored_args += " "; + bbstored_args += optarg; + } + break; + + default: + { + int ret = LogLevel.ProcessOption(c); + if(ret != 0) + { + fprintf(stderr, "Unknown option code " + "'%c'\n", c); + exit(2); + } + } + } + } + + Logging::FilterSyslog(Log::NOTHING); + Logging::FilterConsole(LogLevel.GetCurrentLevel()); + + argc -= optind - 1; + argv += optind - 1; + + // If there is more than one argument, then the test is doing something advanced, so leave it alone + bool fulltestmode = (argc == 1); + + if(fulltestmode) + { + // banner + BOX_NOTICE("Running test TEST_NAME in " MODE_TEXT " mode..."); + + // Count open file descriptors for a very crude "files left open" test + Logging::GetSyslog().Shutdown(); + + // On NetBSD, gethostbyname() appears to open a kqueue socket + // and it's not clear how to close it again. So let's just do + // it once, before counting fds for the first time, so that it's + // already open and doesn't count as a leak. + ::gethostbyname("nonexistent"); + + check_filedes(false); + + #ifdef WIN32 + // Under win32 we must initialise the Winsock library + // before using sockets + + WSADATA info; + TEST_THAT(WSAStartup(0x0101, &info) != SOCKET_ERROR) + #endif + } + + try + { + #ifdef BOX_MEMORY_LEAK_TESTING + memleakfinder_init(); + #endif + + Timers::Init(); + int returncode = test(argc, (const char **)argv); + Timers::Cleanup(false); + + fflush(stdout); + fflush(stderr); + + // check for memory leaks, if enabled + #ifdef BOX_MEMORY_LEAK_TESTING + if(memleakfinder_numleaks() != 0) + { + num_failures++; + printf("FAILURE: Memory leaks detected in test code\n"); + printf("==== MEMORY LEAKS =================================\n"); + memleakfinder_reportleaks(); + printf("===================================================\n"); + } + #endif + + if(fulltestmode) + { + Logging::GetSyslog().Shutdown(); + + bool filesleftopen = !checkfilesleftopen(); + + fflush(stdout); + fflush(stderr); + + if(filesleftopen) + { + num_failures++; + printf("IMPLICIT TEST FAILED: Something left files open\n"); + } + if(num_failures > 0) + { + printf("FAILED: %d tests failed (first at " + "%s:%d)\n", num_failures, + first_fail_file.c_str(), + first_fail_line); + } + else + { + printf("PASSED\n"); + } + } + + return returncode; + } + catch(std::exception &e) + { + printf("FAILED: Exception caught: %s\n", e.what()); + return 1; + } + catch(...) + { + printf("FAILED: Unknown exception caught\n"); + return 1; + } + if(fulltestmode) + { + if(!checkfilesleftopen()) + { + printf("WARNING: Files were left open\n"); + } + } +} + diff --git a/infrastructure/cmake/.gitignore b/infrastructure/cmake/.gitignore new file mode 100644 index 00000000..b27d6b9f --- /dev/null +++ b/infrastructure/cmake/.gitignore @@ -0,0 +1,6 @@ +*.vcxproj +*.vcxproj.filters +BoxBackup.sln +CMakeCache.txt +CMakeFiles +cmake_install.cmake diff --git a/infrastructure/cmake/CMakeLists.txt b/infrastructure/cmake/CMakeLists.txt new file mode 100644 index 00000000..a9bd549f --- /dev/null +++ b/infrastructure/cmake/CMakeLists.txt @@ -0,0 +1,300 @@ +cmake_minimum_required(VERSION 2.6) +project(BoxBackup) +enable_testing() + +set(base_dir ${CMAKE_SOURCE_DIR}/../..) + +macro(cmake_to_native_path cmake_path native_path) + if(WIN32) + string(REPLACE "/" "\\" ${native_path} "${cmake_path}") + else() + set(${native_path} "${cmake_path}") + endif() +endmacro() + +set(files_to_configure + bin/bbackupd/bbackupd-config + bin/bbstored/bbstored-certs + bin/bbstored/bbstored-config + contrib/mac_osx/org.boxbackup.bbackupd.plist + contrib/mac_osx/org.boxbackup.bbstored.plist + contrib/solaris/bbackupd-manifest.xml + contrib/solaris/bbstored-manifest.xml + contrib/debian/bbackupd + contrib/debian/bbstored + contrib/redhat/bbackupd + contrib/redhat/bbstored + contrib/suse/bbackupd + contrib/suse/bbstored + contrib/solaris/bbackupd-smf-method + contrib/solaris/bbstored-smf-method + contrib/windows/installer/boxbackup.mpi + infrastructure/BoxPlatform.pm + infrastructure/makebuildenv.pl + infrastructure/makeparcels.pl + infrastructure/makedistribution.pl + lib/bbackupquery/makedocumentation.pl + lib/common/BoxPortsAndFiles.h + lib/common/makeexception.pl + lib/raidfile/raidfile-config + lib/server/makeprotocol.pl + runtest.pl + test/backupstorefix/testfiles/testbackupstorefix.pl + test/bbackupd/testfiles/bbackupd.conf + test/bbackupd/testfiles/bbackupd-exclude.conf + test/bbackupd/testfiles/bbackupd-snapshot.conf + test/bbackupd/testfiles/bbackupd-symlink.conf + test/bbackupd/testfiles/bbackupd-temploc.conf + test/bbackupd/testfiles/extcheck1.pl + test/bbackupd/testfiles/extcheck2.pl + test/bbackupd/testfiles/notifyscript.pl + test/bbackupd/testfiles/syncallowscript.pl +) + +# We need to substitute TARGET_PERL in test/bbackupd/testfiles/bbackupd.conf, so define it +# as a variable before running configure_file(). +include(FindPerl) +set(TARGET_PERL ${PERL_EXECUTABLE}) + +foreach(file_to_configure ${files_to_configure}) + configure_file("${base_dir}/${file_to_configure}.in" "${base_dir}/${file_to_configure}" @ONLY) +endforeach() + +file(READ "${base_dir}/infrastructure/buildenv-testmain-template.cpp" test_template) + + +execute_process( + COMMAND ${PERL_EXECUTABLE} ${base_dir}/infrastructure/msvc/getversion.pl + RESULT_VARIABLE status + OUTPUT_VARIABLE command_output + ERROR_VARIABLE command_output) +if(NOT status EQUAL 0) + message(FATAL_ERROR "Failed to execute: " + "${PERL_EXECUTABLE} ${base_dir}/infrastructure/msvc/getversion.pl: " + "status ${status}: ${command_output}") +endif() + +# Parsing Makefile.extra files in CMake script is a pain, so the relevant rules for +# code-generating Perl scripts are hard-coded here. + +set(exception_files + lib/backupclient/ClientException.txt + lib/backupstore/BackupStoreException.txt + lib/common/CommonException.txt + lib/common/ConversionException.txt + lib/compress/CompressException.txt + lib/crypto/CipherException.txt + lib/httpserver/HTTPException.txt + lib/raidfile/RaidFileException.txt + lib/server/ServerException.txt + lib/server/ConnectionException.txt +) + +foreach(exception_file ${exception_files}) + string(REGEX MATCH "(.*)/(.*).txt" valid_exception_file ${exception_file}) + if(NOT valid_exception_file) + message(FATAL_ERROR "invalid exception file: '${exception_file}'") + endif() + + set(output_file "${base_dir}/${CMAKE_MATCH_1}/autogen_${CMAKE_MATCH_2}.cpp") + add_custom_command(OUTPUT "${output_file}" + MAIN_DEPENDENCY "${base_dir}/${exception_file}" + COMMAND ${PERL_EXECUTABLE} "${base_dir}/lib/common/makeexception.pl" "${CMAKE_MATCH_2}.txt" + WORKING_DIRECTORY "${base_dir}/${CMAKE_MATCH_1}") + + string(REPLACE "/" "_" module_name ${CMAKE_MATCH_1}) + set(${module_name}_extra_files ${${module_name}_extra_files} ${output_file}) +endforeach() + +set(protocol_files + lib/backupstore/backupprotocol.txt + test/basicserver/testprotocol.txt +) + +foreach(protocol_file ${protocol_files}) + string(REGEX MATCH "(.*)/(.*).txt" valid_protocol_file ${protocol_file}) + if(NOT valid_protocol_file) + message(FATAL_ERROR "invalid protocol file: '${protocol_file}'") + endif() + + set(output_file "${base_dir}/${CMAKE_MATCH_1}/autogen_${CMAKE_MATCH_2}.cpp") + add_custom_command(OUTPUT "${output_file}" + MAIN_DEPENDENCY "${base_dir}/${protocol_file}" + COMMAND ${PERL_EXECUTABLE} "${base_dir}/lib/server/makeprotocol.pl" "${CMAKE_MATCH_2}.txt" + WORKING_DIRECTORY "${base_dir}/${CMAKE_MATCH_1}") + + string(REPLACE "/" "_" module_name ${CMAKE_MATCH_1}) + set(${module_name}_extra_files ${${module_name}_extra_files} ${output_file}) +endforeach() + +set(documentation_files + lib/bbackupquery/documentation.txt +) + +foreach(documentation_file ${documentation_files}) + string(REGEX MATCH "(.*)/(.*).txt" valid_documentation_file ${documentation_file}) + if(NOT valid_documentation_file) + message(FATAL_ERROR "invalid documentation file: '${documentation_file}'") + endif() + + set(output_file "${base_dir}/${CMAKE_MATCH_1}/autogen_${CMAKE_MATCH_2}.cpp") + add_custom_command(OUTPUT "${output_file}" + MAIN_DEPENDENCY "${base_dir}/${documentation_file}" + COMMAND ${PERL_EXECUTABLE} "${base_dir}/lib/bbackupquery/makedocumentation.pl" + WORKING_DIRECTORY "${base_dir}/${CMAKE_MATCH_1}") + + string(REPLACE "/" "_" module_name ${CMAKE_MATCH_1}) + set(${module_name}_extra_files ${${module_name}_extra_files} ${output_file}) +endforeach() + +file(STRINGS ${base_dir}/modules.txt module_deps REGEX "^[^#]") +# qdbm, lib/common and lib/win32 aren't listed in modules.txt, so hard-code them. +foreach(module_dep + "qdbm" + "lib/win32" + "lib/common qdbm lib/win32" + ${module_deps}) + + string(REGEX MATCH "([^ ]+)[ ]*(.*)" valid_module_line ${module_dep}) + if(valid_module_line) + if(DEBUG) + message(STATUS "found module: ${CMAKE_MATCH_1} -> ${CMAKE_MATCH_2}") + endif() + + set(module_dir ${CMAKE_MATCH_1}) + set(module_path ${base_dir}/${module_dir}) + string(REPLACE "/" "_" module_name ${CMAKE_MATCH_1}) + string(REPLACE "/" "_" dependencies "${CMAKE_MATCH_2}") + file(GLOB module_files ${module_path}/*.c ${module_path}/*.cpp + ${module_path}/*.h) + set(module_files ${module_files} ${${module_name}_extra_files}) + + # everything except qdbm, lib/common and lib/win32 implicitly depend on + # lib/common, so express that dependency here. + if(module_name MATCHES "^(qdbm|lib_(common|win32))$") + else() + set(dependencies "${dependencies} lib_common") + endif() + string(REGEX REPLACE "^ " "" dependencies "${dependencies}") + string(REGEX REPLACE " $" "" dependencies "${dependencies}") + + if(module_name MATCHES "^bin_") + string(REGEX MATCH "^bin_(.*)" valid_exe ${module_name}) + set(bin_name ${CMAKE_MATCH_1}) + if(DEBUG) + message(STATUS "add executable '${module_name}': '${module_files}'") + endif() + add_executable(${module_name} ${module_files}) + elseif(module_name MATCHES "^test_") + string(REGEX MATCH "^test_(.*)" valid_test ${module_name}) + set(test_name ${CMAKE_MATCH_1}) + set(bin_name ${module_name}) + + if(DEBUG) + message(STATUS "add test '${module_name}': '${module_files}'") + endif() + + string(REPLACE "TEST_NAME" ${test_name} test_main "${test_template}") + file(WRITE "${module_path}/_main.cpp" "${test_main}") + add_executable(${module_name} ${module_files} + "${module_path}/_main.cpp") + add_test(NAME ${test_name} + COMMAND ${PERL_EXECUTABLE} ${base_dir}/runtest.pl.in ${test_name} + $<CONFIG> WORKING_DIRECTORY ${base_dir}) + elseif(module_name MATCHES "^(lib_.*|qdbm)$") + if(DEBUG) + message(STATUS "add library '${module_name}': '${module_files}'") + endif() + add_library(${module_name} STATIC ${module_files}) + else() + message(FATAL_ERROR "Unsupported module type: " ${module_name}) + endif() + + if(module_name MATCHES "^(bin|test)_") + # We need to install binaries in specific places so that test + # runner can find them: + install(FILES "$<TARGET_FILE:${module_name}>" + CONFIGURATIONS Debug + DESTINATION "${base_dir}/debug/${module_dir}" + RENAME "${bin_name}${CMAKE_EXECUTABLE_SUFFIX}") + install(FILES "$<TARGET_FILE:${module_name}>" + CONFIGURATIONS Release + DESTINATION "${base_dir}/release/${module_dir}" + RENAME "${bin_name}${CMAKE_EXECUTABLE_SUFFIX}") + endif() + + target_compile_definitions(${module_name} PRIVATE -DBOX_MODULE="${module_name}") + + if(dependencies) + string(REGEX REPLACE "[ ]+" ";" dependency_list "${dependencies}") + + foreach(dependency ${dependency_list}) + if(DEBUG) + message(STATUS "add dependency to '${module_name}': '${dependency}'") + endif() + add_dependencies(${module_name} ${dependency}) + if(dependency MATCHES "^(lib_.*|qdbm)$") + # message(STATUS "add link library to '${module_name}': '${dependency}'") + target_link_libraries(${module_name} PUBLIC ${dependency}) + endif() + + # We can't make a binary depend on another binary, so we need to + # add the dependency's directory directly to our include path. + if(dependency MATCHES "^bin_") + get_property(dep_include_dirs + TARGET ${dependency} + PROPERTY INTERFACE_INCLUDE_DIRECTORIES) + target_include_directories(${module_name} + PUBLIC ${dep_include_dirs}) + endif() + endforeach() + endif() + + target_include_directories(${module_name} PUBLIC ${module_path}) + endif() +endforeach() + +#include(ExternalProject) +#ExternalProject_Add(pcre +# PREFIX "../pcre" +# BUILD_COMMAND "${CMAKE_EXECUTABLE + +# Tell QDBM not to build itself as a DLL, because we want to link statically to it. +target_compile_definitions(qdbm PUBLIC -DQDBM_STATIC) + +# Silence some less-useful warnings +if(MSVC) + add_definitions(/wd4996 /wd4291) + # target_link_libraries(qdbm PRIVATE /IGNORE:LNK4006) + set_property(TARGET qdbm PROPERTY CMAKE_STATIC_LINKER_FLAGS /IGNORE:LNK4006) +endif(MSVC) + +target_link_libraries(lib_common PUBLIC ws2_32 gdi32) + +# Link to ZLib +include_directories(${base_dir}/../zlib-win32/include) +find_library(zlibstaticd_lib_path zlibstaticd ${base_dir}/../zlib-win32/lib) +find_library(zlibstatic_lib_path zlibstatic ${base_dir}/../zlib-win32/lib) +target_link_libraries(lib_compress PUBLIC debug ${zlibstaticd_lib_path}) +target_link_libraries(lib_compress PUBLIC optimized ${zlibstatic_lib_path}) + +# Link to OpenSSL +include_directories(${base_dir}/../openssl-win32/include) +find_library(libeay32_lib_path libeay32 ${base_dir}/../openssl-win32/lib) +find_library(ssleay32_lib_path ssleay32 ${base_dir}/../openssl-win32/lib) +target_link_libraries(lib_crypto PUBLIC ${libeay32_lib_path} ${ssleay32_lib_path}) + +# Link to PCRE +include_directories(${base_dir}/../pcre-win32/include) +target_compile_definitions(lib_common PUBLIC -DPCRE_STATIC) +find_library(pcreposix_lib_path pcreposix ${base_dir}/../pcre-win32/lib) +find_library(pcreposixd_lib_path pcreposixd ${base_dir}/../pcre-win32/lib) +find_library(pcre_lib_path pcre ${base_dir}/../pcre-win32/lib) +find_library(pcred_lib_path pcred ${base_dir}/../pcre-win32/lib) +target_link_libraries(lib_common PUBLIC debug "${pcreposixd_lib_path}" optimized "${pcreposix_lib_path}") +target_link_libraries(lib_common PUBLIC debug "${pcred_lib_path}" optimized "${pcre_lib_path}") + +# Define the location of the Perl executable, needed by testbackupstorefix +cmake_to_native_path("${PERL_EXECUTABLE}" perl_executable_native) +string(REPLACE "\\" "\\\\" perl_path_escaped ${perl_executable_native}) +target_compile_definitions(test_backupstorefix PRIVATE -DPERL_EXECUTABLE="${perl_path_escaped}") diff --git a/infrastructure/cmake/build/bin_bbackupd.vcxproj.user b/infrastructure/cmake/build/bin_bbackupd.vcxproj.user new file mode 100755 index 00000000..51928554 --- /dev/null +++ b/infrastructure/cmake/build/bin_bbackupd.vcxproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerCommandArguments>testfiles\bbackupd.conf</LocalDebuggerCommandArguments> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\bbackupd</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/bin_bbstored.vcxproj.user b/infrastructure/cmake/build/bin_bbstored.vcxproj.user new file mode 100755 index 00000000..e48b8383 --- /dev/null +++ b/infrastructure/cmake/build/bin_bbstored.vcxproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerCommandArguments>testfiles/bbstored.conf</LocalDebuggerCommandArguments> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\backupstorefix</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_backupstore.vcxproj.user b/infrastructure/cmake/build/test_backupstore.vcxproj.user new file mode 100755 index 00000000..79ef7571 --- /dev/null +++ b/infrastructure/cmake/build/test_backupstore.vcxproj.user @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\backupstore</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + <LocalDebuggerCommandArguments> + </LocalDebuggerCommandArguments> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_backupstorefix.vcxproj.user b/infrastructure/cmake/build/test_backupstorefix.vcxproj.user new file mode 100755 index 00000000..c0895191 --- /dev/null +++ b/infrastructure/cmake/build/test_backupstorefix.vcxproj.user @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\backupstorefix</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_bbackupd.vcxproj.user b/infrastructure/cmake/build/test_bbackupd.vcxproj.user new file mode 100755 index 00000000..9ca2b5e9 --- /dev/null +++ b/infrastructure/cmake/build/test_bbackupd.vcxproj.user @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerCommandArguments>-e test_basics</LocalDebuggerCommandArguments> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\bbackupd</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_common.vcxproj.user b/infrastructure/cmake/build/test_common.vcxproj.user new file mode 100755 index 00000000..7a375f10 --- /dev/null +++ b/infrastructure/cmake/build/test_common.vcxproj.user @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\common</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_httpserver.vcxproj.user b/infrastructure/cmake/build/test_httpserver.vcxproj.user new file mode 100755 index 00000000..b2da015c --- /dev/null +++ b/infrastructure/cmake/build/test_httpserver.vcxproj.user @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\httpserver</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/cmake/build/test_raidfile.vcxproj.user b/infrastructure/cmake/build/test_raidfile.vcxproj.user new file mode 100755 index 00000000..d2c2fc34 --- /dev/null +++ b/infrastructure/cmake/build/test_raidfile.vcxproj.user @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LocalDebuggerWorkingDirectory>$(ProjectDir)\..\..\..\debug\test\raidfile</LocalDebuggerWorkingDirectory> + <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor> + </PropertyGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/config.guess b/infrastructure/config.guess new file mode 100755 index 00000000..b79252d6 --- /dev/null +++ b/infrastructure/config.guess @@ -0,0 +1,1558 @@ +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright 1992-2013 Free Software Foundation, Inc. + +timestamp='2013-06-10' + +# This file 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). +# +# Originally written by Per Bothner. +# +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +# +# Please send patches with a ChangeLog entry to config-patches@gnu.org. + + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to <config-patches@gnu.org>." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright 1992-2013 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +case "${UNAME_SYSTEM}" in +Linux|GNU|GNU/*) + # If the system lacks a compiler, then just pick glibc. + # We could probably try harder. + LIBC=gnu + + eval $set_cc_for_build + cat <<-EOF > $dummy.c + #include <features.h> + #if defined(__UCLIBC__) + LIBC=uclibc + #elif defined(__dietlibc__) + LIBC=dietlibc + #else + LIBC=gnu + #endif + EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^LIBC'` + ;; +esac + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tuples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + sh5el) machine=sh5le-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ELF__ + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:Bitrig:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/Bitrig.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-bitrig${UNAME_RELEASE} + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + *:SolidBSD:*:*) + echo ${UNAME_MACHINE}-unknown-solidbsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerpc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + # Reset EXIT trap before exiting to avoid spurious non-zero exit code. + exitcode=$? + trap '' 0 + exit $exitcode ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm*:riscos:*:*|arm*:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + s390x:SunOS:*:*) + echo ${UNAME_MACHINE}-ibm-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:AuroraUX:5.*:* | i86xen:AuroraUX:5.*:*) + echo i386-pc-auroraux${UNAME_RELEASE} + exit ;; + i86pc:SunOS:5.*:* | i86xen:SunOS:5.*:*) + eval $set_cc_for_build + SUN_ARCH="i386" + # If there is a compiler, see if it is configured for 64-bit objects. + # Note that the Sun cc does not turn __LP64__ into 1 like gcc does. + # This test works for both compilers. + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __amd64'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + SUN_ARCH="x86_64" + fi + fi + echo ${SUN_ARCH}-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include <stdio.h> /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include <sys/systemcfg.h> + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[4567]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include <stdlib.h> + #include <unistd.h> + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep -q __LP64__ + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include <unistd.h> + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + UNAME_PROCESSOR=`/usr/bin/uname -p` + case ${UNAME_PROCESSOR} in + amd64) + echo x86_64-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + *) + echo ${UNAME_PROCESSOR}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` ;; + esac + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + *:MINGW64*:*) + echo ${UNAME_MACHINE}-pc-mingw64 + exit ;; + *:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:MSYS*:*) + echo ${UNAME_MACHINE}-pc-msys + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + *:Interix*:*) + case ${UNAME_MACHINE} in + x86) + echo i586-pc-interix${UNAME_RELEASE} + exit ;; + authenticamd | genuineintel | EM64T) + echo x86_64-unknown-interix${UNAME_RELEASE} + exit ;; + IA64) + echo ia64-unknown-interix${UNAME_RELEASE} + exit ;; + esac ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + 8664:Windows_NT:*) + echo x86_64-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-${LIBC}`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-${LIBC} + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + aarch64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + aarch64_be:Linux:*:*) + UNAME_MACHINE=aarch64_be + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep -q ld.so.1 + if test "$?" = 0 ; then LIBC="gnulibc1" ; fi + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arc:Linux:*:* | arceb:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + arm*:Linux:*:*) + eval $set_cc_for_build + if echo __ARM_EABI__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_EABI__ + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + else + if echo __ARM_PCS_VFP | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep -q __ARM_PCS_VFP + then + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabi + else + echo ${UNAME_MACHINE}-unknown-linux-${LIBC}eabihf + fi + fi + exit ;; + avr32*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + cris:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + crisv32:Linux:*:*) + echo ${UNAME_MACHINE}-axis-linux-${LIBC} + exit ;; + frv:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + hexagon:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:Linux:*:*) + echo ${UNAME_MACHINE}-pc-linux-${LIBC} + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + mips:Linux:*:* | mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef ${UNAME_MACHINE} + #undef ${UNAME_MACHINE}el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=${UNAME_MACHINE}el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=${UNAME_MACHINE} + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep '^CPU'` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-${LIBC}"; exit; } + ;; + or1k:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + or32:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + padre:Linux:*:*) + echo sparc-unknown-linux-${LIBC} + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-${LIBC} ;; + PA8*) echo hppa2.0-unknown-linux-${LIBC} ;; + *) echo hppa-unknown-linux-${LIBC} ;; + esac + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-${LIBC} + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-${LIBC} + exit ;; + ppc64le:Linux:*:*) + echo powerpc64le-unknown-linux-${LIBC} + exit ;; + ppcle:Linux:*:*) + echo powerpcle-unknown-linux-${LIBC} + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux-${LIBC} + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + tile*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + vax:Linux:*:*) + echo ${UNAME_MACHINE}-dec-linux-${LIBC} + exit ;; + x86_64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + xtensa*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-${LIBC} + exit ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.[02]*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' </usr/options/cb.name` + echo ${UNAME_MACHINE}-pc-isc$UNAME_REL + elif /bin/uname -X 2>/dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i586. + # Note: whatever this is, it MUST be the same as what config.sub + # prints for the "djgpp" host, or else GDB configury will decide that + # this is a cross-build. + echo i586-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + NCR*:*:4.2:* | MPRAS*:*:4.2:*) + OS_REL='.3' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep pteron >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.[02]*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says <Richard.M.Bartel@ccMail.Census.GOV> + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes <hewes@openmarket.com>. + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + BePC:Haiku:*:*) # Haiku running on Intel PC compatible. + echo i586-pc-haiku + exit ;; + x86_64:Haiku:*:*) + echo x86_64-unknown-haiku + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + SX-7:SUPER-UX:*:*) + echo sx7-nec-superux${UNAME_RELEASE} + exit ;; + SX-8:SUPER-UX:*:*) + echo sx8-nec-superux${UNAME_RELEASE} + exit ;; + SX-8R:SUPER-UX:*:*) + echo sx8r-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + eval $set_cc_for_build + if test "$UNAME_PROCESSOR" = unknown ; then + UNAME_PROCESSOR=powerpc + fi + if [ "$CC_FOR_BUILD" != 'no_compiler_found' ]; then + if (echo '#ifdef __LP64__'; echo IS_64BIT_ARCH; echo '#endif') | \ + (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | \ + grep IS_64BIT_ARCH >/dev/null + then + case $UNAME_PROCESSOR in + i386) UNAME_PROCESSOR=x86_64 ;; + powerpc) UNAME_PROCESSOR=powerpc64 ;; + esac + fi + fi + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NEO-?:NONSTOP_KERNEL:*:*) + echo neo-tandem-nsk${UNAME_RELEASE} + exit ;; + NSE-*:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; + i*86:rdos:*:*) + echo ${UNAME_MACHINE}-pc-rdos + exit ;; + i*86:AROS:*:*) + echo ${UNAME_MACHINE}-pc-aros + exit ;; + x86_64:VMkernel:*:*) + echo ${UNAME_MACHINE}-unknown-esx + exit ;; +esac + +eval $set_cc_for_build +cat >$dummy.c <<EOF +#ifdef _SEQUENT_ +# include <sys/types.h> +# include <sys/utsname.h> +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include <sys/param.h> + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include <sys/param.h> +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 <<EOF +$0: unable to guess system type + +This script, last modified $timestamp, has failed to recognize +the operating system you are using. It is advised that you +download the most up to date version of the config scripts from + + http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.guess;hb=HEAD +and + http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD + +If the version you run ($0) is already up to date, please +send the following data and any information you think might be +pertinent to <config-patches@gnu.org> in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +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` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/infrastructure/config.sub b/infrastructure/config.sub new file mode 100755 index 00000000..8b612ab8 --- /dev/null +++ b/infrastructure/config.sub @@ -0,0 +1,1788 @@ +#! /bin/sh +# Configuration validation subroutine script. +# Copyright 1992-2013 Free Software Foundation, Inc. + +timestamp='2013-04-24' + +# This file 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <http://www.gnu.org/licenses/>. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that +# program. This Exception is an additional permission under section 7 +# of the GNU General Public License, version 3 ("GPLv3"). + + +# Please send patches with a ChangeLog entry to config-patches@gnu.org. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# You can get the latest version of this script from: +# http://git.savannah.gnu.org/gitweb/?p=config.git;a=blob_plain;f=config.sub;hb=HEAD + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to <config-patches@gnu.org>." + +version="\ +GNU config.sub ($timestamp) + +Copyright 1992-2013 Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-android* | linux-dietlibc | linux-newlib* | \ + linux-musl* | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | kfreebsd*-gnu* | \ + knetbsd*-gnu* | netbsd*-gnu* | \ + kopensolaris*-gnu* | \ + storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + android-linux) + os=-linux-android + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'`-unknown + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray | -microblaze*) + os= + basic_machine=$1 + ;; + -bluegene*) + os=-cnk + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco6) + os=-sco5v6 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco5v6*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*178) + os=-lynxos178 + ;; + -lynx*5) + os=-lynxos5 + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | aarch64 | aarch64_be \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arceb \ + | arm | arm[bl]e | arme[lb] | armv[2-8] | armv[3-8][lb] | armv7[arm] \ + | avr | avr32 \ + | be32 | be64 \ + | bfin \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | epiphany \ + | fido | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | hexagon \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | le32 | le64 \ + | lm32 \ + | m32c | m32r | m32rle | m68000 | m68k | m88k \ + | maxq | mb | microblaze | microblazeel | mcore | mep | metag \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64octeon | mips64octeonel \ + | mips64orion | mips64orionel \ + | mips64r5900 | mips64r5900el \ + | mips64vr | mips64vrel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipsr5900 | mipsr5900el \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | moxie \ + | mt \ + | msp430 \ + | nds32 | nds32le | nds32be \ + | nios | nios2 | nios2eb | nios2el \ + | ns16k | ns32k \ + | open8 \ + | or1k | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle \ + | pyramid \ + | rl78 | rx \ + | score \ + | sh | sh[1234] | sh[24]a | sh[24]aeb | sh[23]e | sh[34]eb | sheb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc64v | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b | sparcv9v \ + | spu \ + | tahoe | tic4x | tic54x | tic55x | tic6x | tic80 | tron \ + | ubicom32 \ + | v850 | v850e | v850e1 | v850e2 | v850es | v850e2v3 \ + | we32k \ + | x86 | xc16x | xstormy16 | xtensa \ + | z8k | z80) + basic_machine=$basic_machine-unknown + ;; + c54x) + basic_machine=tic54x-unknown + ;; + c55x) + basic_machine=tic55x-unknown + ;; + c6x) + basic_machine=tic6x-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12 | m68hcs12x | picochip) + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + ms1) + basic_machine=mt-unknown + ;; + + strongarm | thumb | xscale) + basic_machine=arm-unknown + ;; + xgate) + basic_machine=$basic_machine-unknown + os=-none + ;; + xscaleeb) + basic_machine=armeb-unknown + ;; + + xscaleel) + basic_machine=armel-unknown + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | aarch64-* | aarch64_be-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* | arceb-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* | avr32-* \ + | be32-* | be64-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* \ + | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fido-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | hexagon-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | le32-* | le64-* \ + | lm32-* \ + | m32c-* | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* | metag-* \ + | microblaze-* | microblazeel-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64octeon-* | mips64octeonel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64r5900-* | mips64r5900el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipsr5900-* | mipsr5900el-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | mt-* \ + | msp430-* \ + | nds32-* | nds32le-* | nds32be-* \ + | nios-* | nios2-* | nios2eb-* | nios2el-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | open8-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* \ + | pyramid-* \ + | rl78-* | romp-* | rs6000-* | rx-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[24]aeb-* | sh[23]e-* | sh[34]eb-* | sheb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc64v-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | sparcv9v-* | sv1-* | sx?-* \ + | tahoe-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tile*-* \ + | tron-* \ + | ubicom32-* \ + | v850-* | v850e-* | v850e1-* | v850es-* | v850e2-* | v850e2v3-* \ + | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xc16x-* | xps100-* \ + | xstormy16-* | xtensa*-* \ + | ymp-* \ + | z8k-* | z80-*) + ;; + # Recognize the basic CPU types without company name, with glob match. + xtensa*) + basic_machine=$basic_machine-unknown + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aros) + basic_machine=i386-pc + os=-aros + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + blackfin) + basic_machine=bfin-unknown + os=-linux + ;; + blackfin-*) + basic_machine=bfin-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + bluegene*) + basic_machine=powerpc-ibm + os=-cnk + ;; + c54x-*) + basic_machine=tic54x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c55x-*) + basic_machine=tic55x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c6x-*) + basic_machine=tic6x-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + cegcc) + basic_machine=arm-unknown + os=-cegcc + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16 | cr16-*) + basic_machine=cr16-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + dicos) + basic_machine=i686-pc + os=-dicos + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m68knommu) + basic_machine=m68k-unknown + os=-linux + ;; + m68knommu-*) + basic_machine=m68k-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + microblaze*) + basic_machine=microblaze-xilinx + ;; + mingw64) + basic_machine=x86_64-pc + os=-mingw64 + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + mingw32ce) + basic_machine=arm-unknown + os=-mingw32ce + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + ms1-*) + basic_machine=`echo $basic_machine | sed -e 's/ms1-/mt-/'` + ;; + msys) + basic_machine=i386-pc + os=-msys + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + nacl) + basic_machine=le32-unknown + os=-nacl + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + neo-tandem) + basic_machine=neo-tandem + ;; + nse-tandem) + basic_machine=nse-tandem + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + parisc) + basic_machine=hppa-unknown + os=-linux + ;; + parisc-*) + basic_machine=hppa-`echo $basic_machine | sed 's/^[^-]*-//'` + os=-linux + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pc98) + basic_machine=i386-pc + ;; + pc98-*) + basic_machine=i386-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc | ppcbe) basic_machine=powerpc-unknown + ;; + ppc-* | ppcbe-*) + basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rdos | rdos64) + basic_machine=x86_64-pc + os=-rdos + ;; + rdos32) + basic_machine=i386-pc + os=-rdos + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sde) + basic_machine=mipsisa32-sde + os=-elf + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh5el) + basic_machine=sh5le-unknown + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + strongarm-* | thumb-*) + basic_machine=arm-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tile*) + basic_machine=$basic_machine-unknown + os=-linux-gnu + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + xscale-* | xscalee[bl]-*) + basic_machine=`echo $basic_machine | sed 's/^xscale/arm/'` + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + z80-*-coff) + basic_machine=z80-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[24]aeb | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b | sparcv9v) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -auroraux) + os=-auroraux + ;; + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -cnk* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -auroraux* | -solaris* \ + | -sym* | -kopensolaris* | -plan9* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* | -aros* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* \ + | -bitrig* | -openbsd* | -solidbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* | -cegcc* \ + | -cygwin* | -msys* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -mingw64* | -linux-gnu* | -linux-android* \ + | -linux-newlib* | -linux-musl* | -linux-uclibc* \ + | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku* | -rdos* | -toppers* | -drops* | -es*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -zvmoe) + os=-zvmoe + ;; + -dicos*) + os=-dicos + ;; + -nacl*) + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + score-*) + os=-elf + ;; + spu-*) + os=-elf + ;; + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + hexagon-*) + os=-elf + ;; + tic54x-*) + os=-coff + ;; + tic55x-*) + os=-coff + ;; + tic6x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + ;; + m68*-cisco) + os=-aout + ;; + mep-*) + os=-elf + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or1k-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -cnk*|-aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: diff --git a/infrastructure/install-sh b/infrastructure/install-sh new file mode 100755 index 00000000..377bb868 --- /dev/null +++ b/infrastructure/install-sh @@ -0,0 +1,527 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2011-11-20.07; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +nl=' +' +IFS=" "" $nl" + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit=${DOITPROG-} +if test -z "$doit"; then + doit_exec=exec +else + doit_exec=$doit +fi + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_glob='?' +initialize_posix_glob=' + test "$posix_glob" != "?" || { + if (set -f) 2>/dev/null; then + posix_glob= + else + posix_glob=: + fi + } +' + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +no_target_directory= + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *' '* | *' +'* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) no_target_directory=true;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test -n "$no_target_directory"; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + # Prefer dirname, but fall back on a substitute if dirname fails. + dstdir=` + (dirname "$dst") 2>/dev/null || + expr X"$dst" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$dst" : 'X\(//\)[^/]' \| \ + X"$dst" : 'X\(//\)$' \| \ + X"$dst" : 'X\(/\)' \| . 2>/dev/null || + echo X"$dst" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q' + ` + + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/d" "$tmpdir" 2>/dev/null; exit $ret' 0 + + if (umask $mkdir_umask && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/d") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + ls_ld_tmpdir=`ls -ld "$tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/d" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + eval "$initialize_posix_glob" + + oIFS=$IFS + IFS=/ + $posix_glob set -f + set fnord $dstdir + shift + $posix_glob set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + + eval "$initialize_posix_glob" && + $posix_glob set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + $posix_glob set +f && + + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/infrastructure/m4/ac_cxx_exceptions.m4 b/infrastructure/m4/ac_cxx_exceptions.m4 new file mode 100644 index 00000000..4c3013dc --- /dev/null +++ b/infrastructure/m4/ac_cxx_exceptions.m4 @@ -0,0 +1,24 @@ +dnl @synopsis AC_CXX_EXCEPTIONS +dnl +dnl If the C++ compiler supports exceptions handling (try, throw and +dnl catch), define HAVE_EXCEPTIONS. +dnl +dnl @category Cxx +dnl @author Todd Veldhuizen +dnl @author Luc Maisonobe <luc@spaceroots.org> +dnl @version 2004-02-04 +dnl @license AllPermissive + +AC_DEFUN([AC_CXX_EXCEPTIONS], +[AC_CACHE_CHECK(whether the compiler supports exceptions, +ac_cv_cxx_exceptions, +[AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE(,[try { throw 1; } catch (int i) { return i; }], + ac_cv_cxx_exceptions=yes, ac_cv_cxx_exceptions=no) + AC_LANG_RESTORE +]) +if test "$ac_cv_cxx_exceptions" = yes; then + AC_DEFINE(HAVE_EXCEPTIONS,,[define if the compiler supports exceptions]) +fi +]) diff --git a/infrastructure/m4/ac_cxx_namespaces.m4 b/infrastructure/m4/ac_cxx_namespaces.m4 new file mode 100644 index 00000000..2f18477b --- /dev/null +++ b/infrastructure/m4/ac_cxx_namespaces.m4 @@ -0,0 +1,25 @@ +dnl @synopsis AC_CXX_NAMESPACES +dnl +dnl If the compiler can prevent names clashes using namespaces, define +dnl HAVE_NAMESPACES. +dnl +dnl @category Cxx +dnl @author Todd Veldhuizen +dnl @author Luc Maisonobe <luc@spaceroots.org> +dnl @version 2004-02-04 +dnl @license AllPermissive + +AC_DEFUN([AC_CXX_NAMESPACES], +[AC_CACHE_CHECK(whether the compiler implements namespaces, +ac_cv_cxx_namespaces, +[AC_LANG_SAVE + AC_LANG_CPLUSPLUS + AC_TRY_COMPILE([namespace Outer { namespace Inner { int i = 0; }}], + [using namespace Outer::Inner; return i;], + ac_cv_cxx_namespaces=yes, ac_cv_cxx_namespaces=no) + AC_LANG_RESTORE +]) +if test "$ac_cv_cxx_namespaces" = yes; then + AC_DEFINE(HAVE_NAMESPACES,,[define if the compiler implements namespaces]) +fi +]) diff --git a/infrastructure/m4/ax_bswap64.m4 b/infrastructure/m4/ax_bswap64.m4 new file mode 100644 index 00000000..8110743c --- /dev/null +++ b/infrastructure/m4/ax_bswap64.m4 @@ -0,0 +1,52 @@ +dnl @synopsis AX_BSWAP64 +dnl +dnl This macro will check for a built in way of endian reversing an int64_t. +dnl If one is found then HAVE_BSWAP64 is set to 1 and BSWAP64 will be defined +dnl to the name of the endian swap function. +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2006/02/02 +dnl @license AllPermissive + +AC_DEFUN([AX_BSWAP64], [ + bswap64_function="" + AC_CHECK_HEADERS([sys/endian.h asm/byteorder.h]) + if test "x$ac_cv_header_sys_endian_h" = "xyes"; then + AC_CACHE_CHECK([for htobe64], [box_cv_have_htobe64], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include <sys/endian.h> + ]], [[ + htobe64(0); + return 1; + ]])], + [box_cv_have_htobe64=yes], [box_cv_have_htobe64=no] + )]) + if test "x$box_cv_have_htobe64" = "xyes"; then + bswap64_function=htobe64 + fi + fi + if test "x$bswap64_function" = "x" && \ + test "x$ac_cv_header_asm_byteorder_h" = "xyes"; then + AC_CACHE_CHECK([for __cpu_to_be64], [box_cv_have___cpu_to_be64], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include <asm/byteorder.h> + ]], [[ + __cpu_to_be64(0); + return 1; + ]])], + [box_cv_have___cpu_to_be64=yes], [box_cv_have___cpu_to_be64=no] + )]) + if test "x$box_cv_have___cpu_to_be64" = "xyes"; then + bswap64_function=__cpu_to_be64 + fi + fi + + if test "x$bswap64_function" != "x"; then + AC_DEFINE([HAVE_BSWAP64], 1, + [Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian swapping function]) + AC_DEFINE_UNQUOTED([BSWAP64], [$bswap64_function], [Name of the 64 bit endian swapping function]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_bdb_v1.m4 b/infrastructure/m4/ax_check_bdb_v1.m4 new file mode 100644 index 00000000..fc5e0692 --- /dev/null +++ b/infrastructure/m4/ax_check_bdb_v1.m4 @@ -0,0 +1,43 @@ +dnl @synopsis AX_CHECK_BDB_V1 +dnl +dnl This macro find an installation of Berkeley DB version 1, or compatible. +dnl It will define the following macros on success: +dnl +dnl HAVE_DB - If Berkeley DB version 1 or compatible is available +dnl DB_HEADER - The relative path and filename of the header file +dnl LIBS - Updated for correct library +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_BDB_V1], [ + ac_have_bdb=no + AC_CHECK_HEADERS([db_185.h db4/db_185.h db3/db_185.h db1/db.h db.h], + [ac_bdb_header=$ac_header; break], [ac_bdb_header=""]) + if test "x$ac_bdb_header" != x; then + AC_SEARCH_LIBS([__db185_open], + [db db-4.4 db-4.3 db-4.2 db-4.1 db-4.0 db4 db-3 db3], + [ac_have_bdb=yes], + [AC_SEARCH_LIBS([dbopen], [db-1 db1 db], [ac_have_bdb=yes])]) + fi + if test "x$ac_have_bdb" = "xyes"; then + AC_MSG_CHECKING([whether found db libraries work]) + AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #include "$ac_bdb_header"]], [[ + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + return 0; + ]])], [ + AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_DB], 1, [Define to 1 if Berkeley DB is available]) + AC_DEFINE_UNQUOTED([DB_HEADER], ["$ac_bdb_header"], + [Define to the location of the Berkeley DB 1.85 header]) + ], [ + AC_MSG_RESULT([no]) + ac_have_bdb=no + ]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_define_pragma.m4 b/infrastructure/m4/ax_check_define_pragma.m4 new file mode 100644 index 00000000..e3f7fe89 --- /dev/null +++ b/infrastructure/m4/ax_check_define_pragma.m4 @@ -0,0 +1,25 @@ +dnl @synopsis AX_CHECK_DEFINE_PRAGMA([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if the compiler will accept #pragma inside a +dnl #define. HAVE_DEFINE_PRAGMA will be defined if this is the case, and +dnl ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_DEFINE_PRAGMA], [ + AC_CACHE_CHECK([for pre-processor pragma defines], [box_cv_have_define_pragma], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + #define TEST_DEFINE #pragma pack(1) + TEST_DEFINE + ]])], + [box_cv_have_define_pragma=yes], [box_cv_have_define_pragma=no] + )]) + if test "x$box_cv_have_define_pragma" = "xyes"; then + AC_DEFINE([HAVE_DEFINE_PRAGMA], 1, [Define to 1 if #define of pragmas works]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_dirent_d_type.m4 b/infrastructure/m4/ax_check_dirent_d_type.m4 new file mode 100644 index 00000000..078a39ee --- /dev/null +++ b/infrastructure/m4/ax_check_dirent_d_type.m4 @@ -0,0 +1,45 @@ +dnl @synopsis AX_CHECK_DIRENT_D_TYPE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if struct dirent.d_type is present and supported. +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_STRUCT_DIRENT_D_TYPE +dnl HAVE_VALID_DIRENT_D_TYPE +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_DIRENT_D_TYPE], [ + AC_CHECK_MEMBERS([struct dirent.d_type],,, [[#include <dirent.h>]]) + if test "x$ac_cv_member_struct_dirent_d_type" = "xyes"; then + AC_CACHE_CHECK([[whether struct dirent.d_type is valid]], [box_cv_have_valid_dirent_d_type], + [AC_TRY_RUN( + [ + $ac_includes_default + #include <dirent.h> + int main() + { + DIR* dir = opendir("."); + struct dirent* res = NULL; + if(dir) res = readdir(dir); + return res ? (res->d_type != DT_FILE && res->d_type != DT_DIR) : 1; + } + ], + [box_cv_have_valid_dirent_d_type=yes], + [box_cv_have_valid_dirent_d_type=no], + [box_cv_have_valid_dirent_d_type=cross] + )]) + if test "x$box_cv_have_valid_dirent_d_type" = "xyes"; then + AC_DEFINE([HAVE_VALID_DIRENT_D_TYPE], 1, [Define to 1 if struct dirent.d_type is valid]) + fi + fi + if test "x$ac_cv_member_struct_dirent_d_type" = "xyes" || \ + test "x$box_cv_have_valid_dirent_d_type" = "xyes" + then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_llong_minmax.m4 b/infrastructure/m4/ax_check_llong_minmax.m4 new file mode 100644 index 00000000..f3f99c53 --- /dev/null +++ b/infrastructure/m4/ax_check_llong_minmax.m4 @@ -0,0 +1,76 @@ +dnl @synopsis AX_CHECK_LLONG_MINMAX +dnl +dnl This macro will fix up LLONG_MIN and LLONG_MAX as appropriate. I'm finding +dnl it quite difficult to believe that so many hoops are necessary. The world +dnl seems to have gone quite mad. +dnl +dnl This gem is adapted from the OpenSSH configure script so here's +dnl the original copyright notice: +dnl +dnl Copyright (c) 1999-2004 Damien Miller +dnl +dnl Permission to use, copy, modify, and distribute this software for any +dnl purpose with or without fee is hereby granted, provided that the above +dnl copyright notice and this permission notice appear in all copies. +dnl +dnl @category C +dnl @author Martin Ebourne and Damien Miller +dnl @version 2005/07/07 + +AC_DEFUN([AX_CHECK_LLONG_MINMAX], [ + AC_CHECK_DECL([LLONG_MAX], [have_llong_max=1], , [[#include <limits.h>]]) + if test -z "$have_llong_max"; then + AC_MSG_CHECKING([[for max value of long long]]) + AC_RUN_IFELSE([AC_LANG_SOURCE([[ + #include <stdio.h> + /* Why is this so damn hard? */ + #undef __GNUC__ + #undef __USE_ISOC99 + #define __USE_ISOC99 + #include <limits.h> + #define DATA "conftest.llminmax" + int main(void) { + FILE *f; + long long i, llmin, llmax = 0; + + if((f = fopen(DATA,"w")) == NULL) + exit(1); + + #if defined(LLONG_MIN) && defined(LLONG_MAX) + fprintf(stderr, "Using system header for LLONG_MIN and LLONG_MAX\n"); + llmin = LLONG_MIN; + llmax = LLONG_MAX; + #else + fprintf(stderr, "Calculating LLONG_MIN and LLONG_MAX\n"); + /* This will work on one's complement and two's complement */ + for (i = 1; i > llmax; i <<= 1, i++) + llmax = i; + llmin = llmax + 1LL; /* wrap */ + #endif + + /* Sanity check */ + if (llmin + 1 < llmin || llmin - 1 < llmin || llmax + 1 > llmax || llmax - 1 > llmax) { + fprintf(f, "unknown unknown\n"); + exit(2); + } + + if (fprintf(f ,"%lld %lld", llmin, llmax) < 0) + exit(3); + + exit(0); + } + ]])], [ + read llong_min llong_max < conftest.llminmax + AC_MSG_RESULT([$llong_max]) + AC_DEFINE_UNQUOTED([LLONG_MAX], [${llong_max}LL], + [max value of long long calculated by configure]) + AC_MSG_CHECKING([[for min value of long long]]) + AC_MSG_RESULT([$llong_min]) + AC_DEFINE_UNQUOTED([LLONG_MIN], [${llong_min}LL], + [min value of long long calculated by configure]) + ], + [AC_MSG_RESULT(not found)], + [AC_MSG_WARN([[cross compiling: not checking]])] + ) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_malloc_workaround.m4 b/infrastructure/m4/ax_check_malloc_workaround.m4 new file mode 100644 index 00000000..7655c0f7 --- /dev/null +++ b/infrastructure/m4/ax_check_malloc_workaround.m4 @@ -0,0 +1,38 @@ +dnl @synopsis AX_CHECK_MALLOC_WORKAROUND +dnl +dnl This macro will see if there is a potential STL memory leak, and if we can +dnl work around it will define __USE_MALLOC as the fix. +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_MALLOC_WORKAROUND], [ + if test "x$GXX" = "xyes"; then + AC_CACHE_CHECK([for gcc version 3 or later], [box_cv_gcc_3_plus], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ + #if __GNUC__ < 3 + #error "Old GNU C" + #endif + ]])], + [box_cv_gcc_3_plus=yes], [box_cv_gcc_3_plus=no] + )]) + if test "x$box_cv_gcc_3_plus" = "xno"; then + AC_CACHE_CHECK([for malloc workaround], [box_cv_malloc_workaround], + [AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ + #define __USE_MALLOC + #include <string> + ]], [[ + std::string s; + s = "test"; + ]])], + [box_cv_malloc_workaround=yes], [box_cv_malloc_workaround=no] + )]) + if test "x$box_cv_malloc_workaround" = "xyes"; then + AC_DEFINE([__USE_MALLOC], 1, + [Define to 1 if __USE_MALLOC is required work around STL memory leaks]) + fi + fi + fi + ])dnl diff --git a/infrastructure/m4/ax_check_mount_point.m4 b/infrastructure/m4/ax_check_mount_point.m4 new file mode 100644 index 00000000..d26bf3e5 --- /dev/null +++ b/infrastructure/m4/ax_check_mount_point.m4 @@ -0,0 +1,60 @@ +dnl @synopsis AX_CHECK_MOUNT_POINT([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out how to get mount point information if possible. +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_MOUNTS +dnl HAVE_MNTENT_H +dnl HAVE_SYS_MNTTAB_H +dnl HAVE_SYS_MOUNT_H +dnl HAVE_STRUCT_MNTENT_MNT_DIR +dnl HAVE_STRUCT_MNTTAB_MNT_MOUNTP +dnl HAVE_STRUCT_STATFS_F_MNTONNAME +dnl HAVE_STRUCT_STATVFS_F_MNTONNAME +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_MOUNT_POINT], [ + AC_CHECK_FUNCS([getmntent statfs]) + AC_CHECK_HEADERS([sys/param.h]) + AC_CHECK_HEADERS([mntent.h sys/mnttab.h sys/mount.h],,, [[ + #include <stdio.h> + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + ]]) + # BSD + AC_CHECK_MEMBERS([struct statfs.f_mntonname],,, [[ + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + #include <sys/mount.h> + ]]) + # NetBSD + AC_CHECK_MEMBERS([struct statvfs.f_mntonname],,, [[ + #ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> + #endif + #include <sys/mount.h> + ]]) + # Linux + AC_CHECK_MEMBERS([struct mntent.mnt_dir],,, [[#include <mntent.h>]]) + # Solaris + AC_CHECK_MEMBERS([struct mnttab.mnt_mountp],,, [[ + #include <stdio.h> + #include <sys/mnttab.h> + ]]) + if test "x$ac_cv_member_struct_statfs_f_mntonname" = "xyes" || \ + test "x$ac_cv_member_struct_statvfs_f_mntonname" = "xyes" || \ + test "x$ac_cv_member_struct_mntent_mnt_dir" = "xyes" || \ + test "x$ac_cv_member_struct_mnttab_mnt_mountp" = "xyes" + then + AC_DEFINE([HAVE_MOUNTS], [1], [Define to 1 if this platform supports mounts]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_nonaligned_access.m4 b/infrastructure/m4/ax_check_nonaligned_access.m4 new file mode 100644 index 00000000..ab2d0b7e --- /dev/null +++ b/infrastructure/m4/ax_check_nonaligned_access.m4 @@ -0,0 +1,57 @@ +dnl @synopsis AX_CHECK_NONALIGNED_ACCESS +dnl +dnl This macro will see if non-aligned memory accesses will fail. The following +dnl defines will be made as appropriate: +dnl HAVE_ALIGNED_ONLY_INT16 +dnl HAVE_ALIGNED_ONLY_INT32 +dnl HAVE_ALIGNED_ONLY_INT64 +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/12 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_NONALIGNED_ACCESS], [ + AC_CACHE_CHECK([if non-aligned 16 bit word accesses fail], [box_cv_have_aligned_only_int16], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT16_T + #define uint16_t u_int16_t; + #endif + uint16_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint16_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int16=no], [box_cv_have_aligned_only_int16=yes] + )]) + if test "x$box_cv_have_aligned_only_int16" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT16], 1, [Define to 1 if non-aligned int16 access will fail]) + fi + AC_CACHE_CHECK([if non-aligned 32 bit word accesses fail], [box_cv_have_aligned_only_int32], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT32_T + #define uint32_t u_int32_t; + #endif + uint32_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint32_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int32=no], [box_cv_have_aligned_only_int32=yes] + )]) + if test "x$box_cv_have_aligned_only_int32" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT32], 1, [Define to 1 if non-aligned int32 access will fail]) + fi + AC_CACHE_CHECK([if non-aligned 64 bit word accesses fail], [box_cv_have_aligned_only_int64], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[$ac_includes_default]], [[ + #ifndef HAVE_UINT64_T + #define uint64_t u_int64_t; + #endif + uint64_t scratch[2]; + memset(scratch, 0, sizeof(scratch)); + return *(uint64_t*)((char*)scratch+1); + ]])], + [box_cv_have_aligned_only_int64=no], [box_cv_have_aligned_only_int64=yes] + )]) + if test "x$box_cv_have_aligned_only_int64" = "xyes"; then + AC_DEFINE([HAVE_ALIGNED_ONLY_INT64], 1, [Define to 1 if non-aligned int64 access will fail]) + fi + ])dnl diff --git a/infrastructure/m4/ax_check_ssl.m4 b/infrastructure/m4/ax_check_ssl.m4 new file mode 100644 index 00000000..78b99f7e --- /dev/null +++ b/infrastructure/m4/ax_check_ssl.m4 @@ -0,0 +1,38 @@ +dnl @synopsis AX_CHECK_SSL([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will check for OpenSSL in the standard path, allowing the user +dnl to specify a directory if it is not found. The user uses +dnl '--with-ssl-headers=/path/to/headers' or +dnl '--with-ssl-lib=/path/to/lib' as arguments to configure. +dnl +dnl If OpenSSL is found the include directory gets added to CPPFLAGS, +dnl '-lcrypto', '-lssl', and the libraries directory are added to LDFLAGS. +dnl Also HAVE_SSL is defined to 1, and ACTION-IF-TRUE and ACTION-IF-FALSE are +dnl run as appropriate +dnl +dnl @category InstalledPackages +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_SSL], [ + AC_ARG_WITH( + [ssl-headers], + [AC_HELP_STRING([--with-ssl-headers=DIR], [SSL include files location])], + [CPPFLAGS="$CPPFLAGS -I$withval"]) + AC_ARG_WITH( + [ssl-lib], + [AC_HELP_STRING([--with-ssl-lib=DIR], [SSL library location])], + [LDFLAGS="$LDFLAGS -L$withval"]) + + ax_check_ssl_found=yes + AC_CHECK_HEADERS([openssl/ssl.h],, [ax_check_ssl_found=no]) + AC_SEARCH_LIBS([HMAC_CTX_init], [crypto]) + AC_SEARCH_LIBS([SSL_read], [ssl],, [ax_check_ssl_found=no]) + + if test "x$ax_check_ssl_found" = "xyes"; then + AC_DEFINE([HAVE_SSL], 1, [Define to 1 if SSL is available]) + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_check_syscall_lseek.m4 b/infrastructure/m4/ax_check_syscall_lseek.m4 new file mode 100644 index 00000000..9fd04c81 --- /dev/null +++ b/infrastructure/m4/ax_check_syscall_lseek.m4 @@ -0,0 +1,69 @@ +dnl @synopsis AX_CHECK_SYSCALL_LSEEK([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro will find out if the lseek syscall requires a dummy middle +dnl parameter +dnl +dnl The following defines will be set as appropriate: +dnl HAVE_LSEEK_DUMMY_PARAM +dnl Also ACTION-IF-TRUE and ACTION-IF-FALSE are run as appropriate +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/03 +dnl @license AllPermissive + +AC_DEFUN([AX_CHECK_SYSCALL_LSEEK], [ + AC_REQUIRE([AX_FUNC_SYSCALL])dnl + if test "x$ac_cv_header_sys_syscall_h" = "xyes"; then + AC_CACHE_CHECK([[whether syscall lseek requires dummy parameter]], [box_cv_have_lseek_dummy_param], + [AC_TRY_RUN( + [ + $ac_includes_default + #include <fcntl.h> + #include <sys/syscall.h> + #ifdef HAVE___SYSCALL_NEED_DEFN + extern "C" off_t __syscall(quad_t number, ...); + #endif + #ifdef HAVE___SYSCALL // always use it if we have it + #undef syscall + #define syscall __syscall + #endif + int main() + { + int fh = creat("lseektest", 0600); + int res = 0; + if(fh>=0) + { + // This test tries first to seek to position 0, with NO + // "dummy argument". If lseek does actually require a dummy + // argument, then it will eat SEEK_SET for the offset and + // try to use 99 as whence, which is invalid, so res will be + // -1, the program will return zero and + // have_lseek_dummy_param=yes + // (whew! that took 1 hour to figure out) + // The "dummy argument" probably means that it takes a 64-bit + // offset, so this was probably a bug anyway, and now that + // we cast the offset to off_t, it should never be needed + // (if my reasoning is correct). + res = syscall(SYS_lseek, fh, (off_t)0, SEEK_SET, 99); + close(fh); + } + unlink("lseektest"); + return res!=-1; + } + ], + [box_cv_have_lseek_dummy_param=yes], + [box_cv_have_lseek_dummy_param=no], + [box_cv_have_lseek_dummy_param=no # assume not for cross-compiling] + )]) + if test "x$box_cv_have_lseek_dummy_param" = "xyes"; then + AC_DEFINE([HAVE_LSEEK_DUMMY_PARAM], 1, + [Define to 1 if syscall lseek requires a dummy middle parameter]) + fi + fi + if test "x$box_cv_have_lseek_dummy_param" = "xno" + then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + ])dnl diff --git a/infrastructure/m4/ax_compare_version.m4 b/infrastructure/m4/ax_compare_version.m4 new file mode 100644 index 00000000..bd6c51b2 --- /dev/null +++ b/infrastructure/m4/ax_compare_version.m4 @@ -0,0 +1,162 @@ +dnl @synopsis AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl This macro compares two version strings. It is used heavily in the +dnl macro _AX_PATH_BDB for library checking. Due to the various number +dnl of minor-version numbers that can exist, and the fact that string +dnl comparisons are not compatible with numeric comparisons, this is +dnl not necessarily trivial to do in a autoconf script. This macro +dnl makes doing these comparisons easy. +dnl +dnl The six basic comparisons are available, as well as checking +dnl equality limited to a certain number of minor-version levels. +dnl +dnl The operator OP determines what type of comparison to do, and can +dnl be one of: +dnl +dnl eq - equal (test A == B) +dnl ne - not equal (test A != B) +dnl le - less than or equal (test A <= B) +dnl ge - greater than or equal (test A >= B) +dnl lt - less than (test A < B) +dnl gt - greater than (test A > B) +dnl +dnl Additionally, the eq and ne operator can have a number after it to +dnl limit the test to that number of minor versions. +dnl +dnl eq0 - equal up to the length of the shorter version +dnl ne0 - not equal up to the length of the shorter version +dnl eqN - equal up to N sub-version levels +dnl neN - not equal up to N sub-version levels +dnl +dnl When the condition is true, shell commands ACTION-IF-TRUE are run, +dnl otherwise shell commands ACTION-IF-FALSE are run. The environment +dnl variable 'ax_compare_version' is always set to either 'true' or +dnl 'false' as well. +dnl +dnl Examples: +dnl +dnl AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +dnl AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +dnl +dnl would both be true. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +dnl AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +dnl +dnl would both be false. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +dnl +dnl would be true because it is only comparing two minor versions. +dnl +dnl AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +dnl +dnl would be true because it is only comparing the lesser number of +dnl minor versions of the two values. +dnl +dnl Note: The characters that separate the version numbers do not +dnl matter. An empty string is the same as version 0. OP is evaluated +dnl by autoconf, not configure, so must be a string, not a variable. +dnl +dnl The author would like to acknowledge Guido Draheim whose advice +dnl about the m4_case and m4_ifvaln functions make this macro only +dnl include the portions necessary to perform the specific comparison +dnl specified by the OP argument in the final configure script. +dnl +dnl @category Misc +dnl @author Tim Toolan <toolan@ele.uri.edu> +dnl @version 2004-03-01 +dnl @license GPLWithACException + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | awk '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | awk '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION diff --git a/infrastructure/m4/ax_config_scripts.m4 b/infrastructure/m4/ax_config_scripts.m4 new file mode 100644 index 00000000..8f5436ae --- /dev/null +++ b/infrastructure/m4/ax_config_scripts.m4 @@ -0,0 +1,16 @@ +dnl @synopsis AX_CONFIG_SCRIPTS(SCRIPT_FILE, ...) +dnl +dnl Run AC_CONFIG_FILES on a list of scripts while preserving execute +dnl permission. +dnl +dnl @category Automake +dnl @author Martin Ebourne <martin@zepler.org> +dnl @script +dnl @license AllPermissive + +AC_DEFUN([AX_CONFIG_SCRIPTS],[ + AC_REQUIRE([AC_CONFIG_FILES])dnl + m4_foreach([SCRIPT_FILE], + m4_quote(m4_split(m4_normalize([$1]))), + [AC_CONFIG_FILES(SCRIPT_FILE, m4_quote(chmod +x SCRIPT_FILE))])dnl +]) diff --git a/infrastructure/m4/ax_func_syscall.m4 b/infrastructure/m4/ax_func_syscall.m4 new file mode 100644 index 00000000..2429ca93 --- /dev/null +++ b/infrastructure/m4/ax_func_syscall.m4 @@ -0,0 +1,50 @@ +dnl @synopsis AX_FUNC_SYSCALL +dnl +dnl This macro will find out how to call syscall. One or more of the following +dnl defines will be made as appropriate: +dnl HAVE_UNISTD_H - If unistd.h is available +dnl HAVE_SYS_SYSCALL_H - If sys/syscall.h is available +dnl HAVE_SYSCALL - If syscall() is available and is defined in unistd.h +dnl HAVE___SYSCALL - If __syscall() is available and is defined in unistd.h +dnl HAVE___SYSCALL_NEED_DEFN - If __syscall() is available but is not defined in unistd.h +dnl +dnl @category C +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive +dnl +dnl Changed by Chris on 081026: +dnl +dnl Reversed the test for __syscall(), remove the test for syscall(), +dnl remove the definition and reverse the sense in ax_func_syscall.m4 +dnl (which checks for __syscall() needing definition). +dnl +dnl Autoconf's AC_CHECK_FUNC defines it when testing for its presence, +dnl so HAVE___SYSCALL will be true even if __syscall has no definition +dnl in the system libraries, and this is precisely the case that we +dnl want to test for, so now we test whether the test program compiles +dnl with no explicit definition (only the system headers) and if that +dnl fails, we set HAVE___SYSCALL_NEED_DEFN to 1. + +AC_DEFUN([AX_FUNC_SYSCALL], [ + AC_CHECK_HEADERS([sys/syscall.h unistd.h]) + AC_CHECK_FUNCS([syscall __syscall]) + if test "x$ac_cv_func___syscall" = "xyes"; then + AC_CACHE_CHECK([for __syscall needing definition], [box_cv_have___syscall_need_defn], + [AC_RUN_IFELSE([AC_LANG_PROGRAM([[ + $ac_includes_default + #ifdef HAVE_SYS_SYSCALL_H + #include <sys/syscall.h> + #endif + ]], [[ + __syscall(SYS_exit, 0); + return 1; + ]])], + [box_cv_have___syscall_need_defn=no], [box_cv_have___syscall_need_defn=yes] + )]) + if test "x$box_cv_have___syscall_need_defn" = "xyes"; then + AC_DEFINE([HAVE___SYSCALL_NEED_DEFN], 1, + [Define to 1 if __syscall is available but needs a definition]) + fi + fi + ])dnl diff --git a/infrastructure/m4/ax_path_bdb.m4 b/infrastructure/m4/ax_path_bdb.m4 new file mode 100644 index 00000000..1a771048 --- /dev/null +++ b/infrastructure/m4/ax_path_bdb.m4 @@ -0,0 +1,615 @@ +dnl @synopsis AX_PATH_BDB([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +dnl +dnl This macro finds the latest version of Berkeley DB on the system, +dnl and ensures that the header file and library versions match. If +dnl MINIMUM-VERSION is specified, it will ensure that the library found +dnl is at least that version. +dnl +dnl It determines the name of the library as well as the path to the +dnl header file and library. It will check both the default environment +dnl as well as the default Berkeley DB install location. When found, it +dnl sets BDB_LIBS, BDB_CPPFLAGS, and BDB_LDFLAGS to the necessary +dnl values to add to LIBS, CPPFLAGS, and LDFLAGS, as well as setting +dnl BDB_VERSION to the version found. HAVE_DB_H is defined also. +dnl +dnl The options --with-bdb-headers=DIR and --with-bdb-lib=DIR can be +dnl used to specify a specific Berkeley DB installation to use. +dnl +dnl An example of its use is: +dnl +dnl AX_PATH_BDB([3],[ +dnl LIBS="$BDB_LIBS $LIBS" +dnl LDFLAGS="$BDB_LDFLAGS $LDFLAGS" +dnl CPPFLAGS="$CPPFLAGS $BDB_CPPFLAGS" +dnl ]) +dnl +dnl which will locate the latest version of Berkeley DB on the system, +dnl and ensure that it is version 3.0 or higher. +dnl +dnl Details: This macro does not use either AC_CHECK_HEADERS or +dnl AC_CHECK_LIB because, first, the functions inside the library are +dnl sometimes renamed to contain a version code that is only available +dnl from the db.h on the system, and second, because it is common to +dnl have multiple db.h and libdb files on a system it is important to +dnl make sure the ones being used correspond to the same version. +dnl Additionally, there are many different possible names for libdb +dnl when installed by an OS distribution, and these need to be checked +dnl if db.h does not correspond to libdb. +dnl +dnl When cross compiling, only header versions are verified since it +dnl would be difficult to check the library version. Additionally the +dnl default Berkeley DB installation locations /usr/local/BerkeleyDB* +dnl are not searched for higher versions of the library. +dnl +dnl The format for the list of library names to search came from the +dnl Cyrus IMAP distribution, although they are generated dynamically +dnl here, and only for the version found in db.h. +dnl +dnl The macro AX_COMPARE_VERSION is required to use this macro, and +dnl should be available from the Autoconf Macro Archive. +dnl +dnl The author would like to acknowledge the generous and valuable +dnl feedback from Guido Draheim, without which this macro would be far +dnl less robust, and have poor and inconsistent cross compilation +dnl support. +dnl +dnl Changes: +dnl +dnl 1/5/05 applied patch from Rafa Rzepecki to eliminate compiler +dnl warning about unused variable, argv +dnl 1/7/07 Add --with-bdb-headers and --with-bdb-lib options +dnl (James O'Gorman, james@netinertia.co.uk) +dnl +dnl @category InstalledPackages +dnl @author Tim Toolan <toolan@ele.uri.edu> +dnl @version 2005-01-17 +dnl @license GPLWithACException + +dnl ######################################################################### +AC_DEFUN([AX_PATH_BDB], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_ok=no + + # Add --with-bdb-headers and --with-bdb-lib options + AC_ARG_WITH([bdb-headers], + [AC_HELP_STRING([--with-bdb-headers=DIR], + [Berkeley DB include files location])]) + + AC_ARG_WITH([bdb-lib], + [AC_HELP_STRING([--with-bdb-lib=DIR], + [Berkeley DB library location])]) + + # Check if --with-bdb-dir was specified. + if test "x$with_bdb_headers" = "x" -a "x$with_bdb_lib" = "x"; then + # No option specified, so just search the system. + AX_PATH_BDB_NO_OPTIONS([$1], [HIGHEST], [ + ax_path_bdb_ok=yes + ]) + else + ax_path_bdb_INC="$with_bdb_headers" + ax_path_bdb_LIB="$with_bdb_lib" + + dnl # Save previous environment, and modify with new stuff. + ax_path_bdb_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-I$ax_path_bdb_INC $CPPFLAGS" + + ax_path_bdb_save_LDFLAGS=$LDFLAGS + LDFLAGS="-L$ax_path_bdb_LIB $LDFLAGS" + + # Check for specific header file db.h + AC_MSG_CHECKING([db.h presence in $ax_path_bdb_INC]) + if test -f "$ax_path_bdb_INC/db.h" ; then + AC_MSG_RESULT([yes]) + # Check for library + AX_PATH_BDB_NO_OPTIONS([$1], [ENVONLY], [ + ax_path_bdb_ok=yes + BDB_CPPFLAGS="-I$ax_path_bdb_INC" + BDB_LDFLAGS="-L$ax_path_bdb_LIB" + ]) + else + AC_MSG_RESULT([no]) + AC_MSG_NOTICE([no usable Berkeley DB not found]) + fi + + dnl # Restore the environment. + CPPFLAGS="$ax_path_bdb_save_CPPFLAGS" + LDFLAGS="$ax_path_bdb_save_LDFLAGS" + + fi + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_ok" = "yes" ; then + m4_ifvaln([$2],[$2],[:])dnl + m4_ifvaln([$3],[else $3])dnl + fi + +]) dnl AX_PATH_BDB + +dnl ######################################################################### +dnl Check for berkeley DB of at least MINIMUM-VERSION on system. +dnl +dnl The OPTION argument determines how the checks occur, and can be one of: +dnl +dnl HIGHEST - Check both the environment and the default installation +dnl directories for Berkeley DB and choose the version that +dnl is highest. (default) +dnl ENVFIRST - Check the environment first, and if no satisfactory +dnl library is found there check the default installation +dnl directories for Berkeley DB which is /usr/local/BerkeleyDB* +dnl ENVONLY - Check the current environment only. +dnl +dnl Requires AX_PATH_BDB_PATH_GET_VERSION, AX_PATH_BDB_PATH_FIND_HIGHEST, +dnl AX_PATH_BDB_ENV_CONFIRM_LIB, AX_PATH_BDB_ENV_GET_VERSION, and +dnl AX_COMPARE_VERSION macros. +dnl +dnl Result: sets ax_path_bdb_no_options_ok to yes or no +dnl sets BDB_LIBS, BDB_CPPFLAGS, BDB_LDFLAGS, BDB_VERSION +dnl +dnl AX_PATH_BDB_NO_OPTIONS([MINIMUM-VERSION], [OPTION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_NO_OPTIONS], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_no_options_ok=no + + # Values to add to environment to use Berkeley DB. + BDB_VERSION='' + BDB_LIBS='' + BDB_CPPFLAGS='' + BDB_LDFLAGS='' + + # Check cross compilation here. + if test "x$cross_compiling" = "xyes" ; then + # If cross compiling, can't use AC_RUN_IFELSE so do these tests. + # The AC_PREPROC_IFELSE confirms that db.h is preprocessable, + # and extracts the version number from it. + AC_MSG_CHECKING([for db.h]) + + AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_no_options_HEADER_VERSION])dnl + HEADER_VERSION='' + AC_PREPROC_IFELSE([ + AC_LANG_SOURCE([[ +#include <db.h> +#ifdef DB_VERSION_MAJOR +AX_PATH_BDB_STUFF DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH +#else +AX_PATH_BDB_STUFF 1,0,0 +#endif + ]]) + ],[ + # Extract version from preprocessor output. + HEADER_VERSION=`eval "$ac_cpp conftest.$ac_ext" 2> /dev/null \ + | grep AX_PATH_BDB_STUFF | sed 's/[[^0-9,]]//g;s/,/./g;1q'` + ],[]) + + if test "x$HEADER_VERSION" = "x" ; then + AC_MSG_RESULT([no]) + else + AC_MSG_RESULT([$HEADER_VERSION]) + + # Check that version is high enough. + AX_COMPARE_VERSION([$HEADER_VERSION],[ge],[$1],[ + # get major and minor version numbers + AS_VAR_PUSHDEF([MAJ],[ax_path_bdb_no_options_MAJOR])dnl + MAJ=`echo $HEADER_VERSION | sed 's,\..*,,'` + AS_VAR_PUSHDEF([MIN],[ax_path_bdb_no_options_MINOR])dnl + MIN=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` + + dnl # Save LIBS. + ax_path_bdb_no_options_save_LIBS="$LIBS" + + # Check that we can link with the library. + AC_SEARCH_LIBS([db_version], + [db db-$MAJ.$MIN db$MAJ.$MIN db$MAJ$MIN db-$MAJ db$MAJ],[ + # Sucessfully found library. + ax_path_bdb_no_options_ok=yes + BDB_VERSION=$HEADER_VERSION + + # Extract library from LIBS + ax_path_bdb_no_options_LEN=` \ + echo "x$ax_path_bdb_no_options_save_LIBS" \ + | awk '{print(length)}'` + BDB_LIBS=`echo "x$LIBS " \ + | sed "s/.\{$ax_path_bdb_no_options_LEN\}\$//;s/^x//;s/ //g"` + ],[]) + + dnl # Restore LIBS + LIBS="$ax_path_bdb_no_options_save_LIBS" + + AS_VAR_POPDEF([MAJ])dnl + AS_VAR_POPDEF([MIN])dnl + ]) + fi + + AS_VAR_POPDEF([HEADER_VERSION])dnl + else + # Not cross compiling. + # Check version of Berkeley DB in the current environment. + AX_PATH_BDB_ENV_GET_VERSION([ + AX_COMPARE_VERSION([$ax_path_bdb_env_get_version_VERSION],[ge],[$1],[ + # Found acceptable version in current environment. + ax_path_bdb_no_options_ok=yes + BDB_VERSION="$ax_path_bdb_env_get_version_VERSION" + BDB_LIBS="$ax_path_bdb_env_get_version_LIBS" + ]) + ]) + + # Determine if we need to search /usr/local/BerkeleyDB* + ax_path_bdb_no_options_DONE=no + if test "x$2" = "xENVONLY" ; then + ax_path_bdb_no_options_DONE=yes + elif test "x$2" = "xENVFIRST" ; then + ax_path_bdb_no_options_DONE=$ax_path_bdb_no_options_ok + fi + + if test "$ax_path_bdb_no_options_DONE" = "no" ; then + ax_compare_version=false + # Check for highest in /usr/local/BerkeleyDB* + AX_PATH_BDB_PATH_FIND_HIGHEST([ + if test "$ax_path_bdb_no_options_ok" = "yes" ; then + # If we already have an acceptable version use this if higher. + AX_COMPARE_VERSION( + [$ax_path_bdb_path_find_highest_VERSION],[gt],[$BDB_VERSION]) + else + # Since we didn't have an acceptable version check if this one is. + AX_COMPARE_VERSION( + [$ax_path_bdb_path_find_highest_VERSION],[ge],[$1]) + fi + ]) + + dnl # If result from _AX_COMPARE_VERSION is true we want this version. + if test "$ax_compare_version" = "true" ; then + ax_path_bdb_no_options_ok=yes + BDB_LIBS="-ldb" + if test "x$ax_path_bdb_path_find_highest_DIR" != x ; then + BDB_CPPFLAGS="-I$ax_path_bdb_path_find_highest_DIR/include" + BDB_LDFLAGS="-L$ax_path_bdb_path_find_highest_DIR/lib" + fi + BDB_VERSION="$ax_path_bdb_path_find_highest_VERSION" + fi + fi + fi + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_no_options_ok" = "yes" ; then + AC_MSG_NOTICE([using Berkeley DB version $BDB_VERSION]) + AC_DEFINE([HAVE_DB_H],[1], + [Define to 1 if you have the <db.h> header file.]) + m4_ifvaln([$3],[$3])dnl + else + AC_MSG_NOTICE([no Berkeley DB version $1 or higher found]) + m4_ifvaln([$4],[$4])dnl + fi +]) dnl AX_PATH_BDB_NO_OPTIONS + +dnl ######################################################################### +dnl Check the default installation directory for Berkeley DB which is +dnl of the form /usr/local/BerkeleyDB* for the highest version. +dnl +dnl Result: sets ax_path_bdb_path_find_highest_ok to yes or no, +dnl sets ax_path_bdb_path_find_highest_VERSION to version, +dnl sets ax_path_bdb_path_find_highest_DIR to directory. +dnl +dnl AX_PATH_BDB_PATH_FIND_HIGHEST([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_PATH_FIND_HIGHEST], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_path_find_highest_ok=no + + AS_VAR_PUSHDEF([VERSION],[ax_path_bdb_path_find_highest_VERSION])dnl + VERSION='' + + ax_path_bdb_path_find_highest_DIR='' + + # find highest verison in default install directory for Berkeley DB + AS_VAR_PUSHDEF([CURDIR],[ax_path_bdb_path_find_highest_CURDIR])dnl + AS_VAR_PUSHDEF([CUR_VERSION],[ax_path_bdb_path_get_version_VERSION])dnl + + for CURDIR in `ls -d /usr/local/BerkeleyDB* 2> /dev/null` + do + AX_PATH_BDB_PATH_GET_VERSION([$CURDIR],[ + AX_COMPARE_VERSION([$CUR_VERSION],[gt],[$VERSION],[ + ax_path_bdb_path_find_highest_ok=yes + ax_path_bdb_path_find_highest_DIR="$CURDIR" + VERSION="$CUR_VERSION" + ]) + ]) + done + + AS_VAR_POPDEF([VERSION])dnl + AS_VAR_POPDEF([CUR_VERSION])dnl + AS_VAR_POPDEF([CURDIR])dnl + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_path_find_highest_ok" = "yes" ; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + +]) dnl AX_PATH_BDB_PATH_FIND_HIGHEST + +dnl ######################################################################### +dnl Checks for Berkeley DB in specified directory's lib and include +dnl subdirectories. +dnl +dnl Result: sets ax_path_bdb_path_get_version_ok to yes or no, +dnl sets ax_path_bdb_path_get_version_VERSION to version. +dnl +dnl AX_PATH_BDB_PATH_GET_VERSION(BDB-DIR, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_PATH_GET_VERSION], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_path_get_version_ok=no + + # Indicate status of checking for Berkeley DB header. + AC_MSG_CHECKING([in $1/include for db.h]) + ax_path_bdb_path_get_version_got_header=no + test -f "$1/include/db.h" && ax_path_bdb_path_get_version_got_header=yes + AC_MSG_RESULT([$ax_path_bdb_path_get_version_got_header]) + + # Indicate status of checking for Berkeley DB library. + AC_MSG_CHECKING([in $1/lib for library -ldb]) + + ax_path_bdb_path_get_version_VERSION='' + + if test -d "$1/include" && test -d "$1/lib" && + test "$ax_path_bdb_path_get_version_got_header" = "yes" ; then + dnl # save and modify environment + ax_path_bdb_path_get_version_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="-I$1/include $CPPFLAGS" + + ax_path_bdb_path_get_version_save_LIBS="$LIBS" + LIBS="$LIBS -ldb" + + ax_path_bdb_path_get_version_save_LDFLAGS="$LDFLAGS" + LDFLAGS="-L$1/lib $LDFLAGS" + + # Compile and run a program that compares the version defined in + # the header file with a version defined in the library function + # db_version. + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; +#ifdef DB_VERSION_MAJOR + int major,minor,patch; + db_version(&major,&minor,&patch); + if (argc > 1) + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); + if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && + DB_VERSION_PATCH == patch) + return 0; + else + return 1; +#else + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + if (argc > 1) + printf("1.0.0\n"); + if (dbp) + return 0; + else + return 1; +#endif +} + ]]) + ],[ + # Program compiled and ran, so get version by adding argument. + ax_path_bdb_path_get_version_VERSION=`./conftest$ac_exeext x` + ax_path_bdb_path_get_version_ok=yes + ],[],[]) + + dnl # restore environment + CPPFLAGS="$ax_path_bdb_path_get_version_save_CPPFLAGS" + LIBS="$ax_path_bdb_path_get_version_save_LIBS" + LDFLAGS="$ax_path_bdb_path_get_version_save_LDFLAGS" + fi + + dnl # Finally, execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_path_get_version_ok" = "yes" ; then + AC_MSG_RESULT([$ax_path_bdb_path_get_version_VERSION]) + m4_ifvaln([$2],[$2])dnl + else + AC_MSG_RESULT([no]) + m4_ifvaln([$3],[$3])dnl + fi +]) dnl AX_PATH_BDB_PATH_GET_VERSION + +############################################################################# +dnl Checks if version of library and header match specified version. +dnl Only meant to be used by AX_PATH_BDB_ENV_GET_VERSION macro. +dnl +dnl Requires AX_COMPARE_VERSION macro. +dnl +dnl Result: sets ax_path_bdb_env_confirm_lib_ok to yes or no. +dnl +dnl AX_PATH_BDB_ENV_CONFIRM_LIB(VERSION, [LIBNAME]) +AC_DEFUN([AX_PATH_BDB_ENV_CONFIRM_LIB], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_env_confirm_lib_ok=no + + dnl # save and modify environment to link with library LIBNAME + ax_path_bdb_env_confirm_lib_save_LIBS="$LIBS" + LIBS="$LIBS $2" + + # Compile and run a program that compares the version defined in + # the header file with a version defined in the library function + # db_version. + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; +#ifdef DB_VERSION_MAJOR + int major,minor,patch; + db_version(&major,&minor,&patch); + if (argc > 1) + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); + if (DB_VERSION_MAJOR == major && DB_VERSION_MINOR == minor && + DB_VERSION_PATCH == patch) + return 0; + else + return 1; +#else + DB *dbp = dbopen(0, 0, 0, DB_HASH, 0); + if(dbp) dbp->close(dbp); + if (argc > 1) + printf("1.0.0\n"); + if (dbp) + return 0; + else + return 1; +#endif +} + ]]) + ],[ + # Program compiled and ran, so get version by giving an argument, + # which will tell the program to print the output. + ax_path_bdb_env_confirm_lib_VERSION=`./conftest$ac_exeext x` + + # If the versions all match up, indicate success. + AX_COMPARE_VERSION([$ax_path_bdb_env_confirm_lib_VERSION],[eq],[$1],[ + ax_path_bdb_env_confirm_lib_ok=yes + ]) + ],[],[]) + + dnl # restore environment + LIBS="$ax_path_bdb_env_confirm_lib_save_LIBS" + +]) dnl AX_PATH_BDB_ENV_CONFIRM_LIB + +############################################################################# +dnl Finds the version and library name for Berkeley DB in the +dnl current environment. Tries many different names for library. +dnl +dnl Requires AX_PATH_BDB_ENV_CONFIRM_LIB macro. +dnl +dnl Result: set ax_path_bdb_env_get_version_ok to yes or no, +dnl set ax_path_bdb_env_get_version_VERSION to the version found, +dnl and ax_path_bdb_env_get_version_LIBNAME to the library name. +dnl +dnl AX_PATH_BDB_ENV_GET_VERSION([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +AC_DEFUN([AX_PATH_BDB_ENV_GET_VERSION], [ + dnl # Used to indicate success or failure of this function. + ax_path_bdb_env_get_version_ok=no + + ax_path_bdb_env_get_version_VERSION='' + ax_path_bdb_env_get_version_LIBS='' + + AS_VAR_PUSHDEF([HEADER_VERSION],[ax_path_bdb_env_get_version_HEADER_VERSION])dnl + AS_VAR_PUSHDEF([TEST_LIBNAME],[ax_path_bdb_env_get_version_TEST_LIBNAME])dnl + + # Indicate status of checking for Berkeley DB library. + AC_MSG_CHECKING([for db.h]) + + # Compile and run a program that determines the Berkeley DB version + # in the header file db.h. + HEADER_VERSION='' + AC_RUN_IFELSE([ + AC_LANG_SOURCE([[ +#include <stdio.h> +#include <db.h> +int main(int argc,char **argv) +{ + (void) argv; + if (argc > 1) +#ifdef DB_VERSION_MAJOR + printf("%d.%d.%d\n",DB_VERSION_MAJOR,DB_VERSION_MINOR,DB_VERSION_PATCH); +#else + printf("1.0.0\n"); +#endif + return 0; +} + ]]) + ],[ + # Program compiled and ran, so get version by adding an argument. + HEADER_VERSION=`./conftest$ac_exeext x` + AC_MSG_RESULT([$HEADER_VERSION]) + ],[AC_MSG_RESULT([no])],[AC_MSG_RESULT([no])]) + + # Have header version, so try to find corresponding library. + # Looks for library names in the order: + # nothing, db, db-X.Y, dbX.Y, dbXY, db-X, dbX + # and stops when it finds the first one that matches the version + # of the header file. + if test "x$HEADER_VERSION" != "x" ; then + AC_MSG_CHECKING([for library containing Berkeley DB $HEADER_VERSION]) + + AS_VAR_PUSHDEF([MAJOR],[ax_path_bdb_env_get_version_MAJOR])dnl + AS_VAR_PUSHDEF([MINOR],[ax_path_bdb_env_get_version_MINOR])dnl + + # get major and minor version numbers + MAJOR=`echo $HEADER_VERSION | sed 's,\..*,,'` + MINOR=`echo $HEADER_VERSION | sed 's,^[[0-9]]*\.,,;s,\.[[0-9]]*$,,'` + + # see if it is already specified in LIBS + TEST_LIBNAME='' + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db" + TEST_LIBNAME='-ldb' + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db-X.Y" + TEST_LIBNAME="-ldb-${MAJOR}.$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbX.Y" + TEST_LIBNAME="-ldb${MAJOR}.$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbXY" + TEST_LIBNAME="-ldb$MAJOR$MINOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "db-X" + TEST_LIBNAME="-ldb-$MAJOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + if test "$ax_path_bdb_env_confirm_lib_ok" = "no" ; then + # try format "dbX" + TEST_LIBNAME="-ldb$MAJOR" + AX_PATH_BDB_ENV_CONFIRM_LIB([$HEADER_VERSION], [$TEST_LIBNAME]) + fi + + dnl # Found a valid library. + if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then + if test "x$TEST_LIBNAME" = "x" ; then + AC_MSG_RESULT([none required]) + else + AC_MSG_RESULT([$TEST_LIBNAME]) + fi + ax_path_bdb_env_get_version_VERSION="$HEADER_VERSION" + ax_path_bdb_env_get_version_LIBS="$TEST_LIBNAME" + ax_path_bdb_env_get_version_ok=yes + else + AC_MSG_RESULT([no]) + fi + + AS_VAR_POPDEF([MAJOR])dnl + AS_VAR_POPDEF([MINOR])dnl + fi + + AS_VAR_POPDEF([HEADER_VERSION])dnl + AS_VAR_POPDEF([TEST_LIBNAME])dnl + + dnl # Execute ACTION-IF-FOUND / ACTION-IF-NOT-FOUND. + if test "$ax_path_bdb_env_confirm_lib_ok" = "yes" ; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi + +]) dnl BDB_ENV_GET_VERSION + +############################################################################# diff --git a/infrastructure/m4/ax_random_device.m4 b/infrastructure/m4/ax_random_device.m4 new file mode 100644 index 00000000..ab9b56fd --- /dev/null +++ b/infrastructure/m4/ax_random_device.m4 @@ -0,0 +1,31 @@ +dnl @synopsis AX_RANDOM_DEVICE +dnl +dnl This macro will check for a random device, allowing the user to explicitly +dnl set the path. The user uses '--with-random=FILE' as an argument to +dnl configure. +dnl +dnl If A random device is found then HAVE_RANDOM_DEVICE is set to 1 and +dnl RANDOM_DEVICE contains the path. +dnl +dnl @category Miscellaneous +dnl @author Martin Ebourne +dnl @version 2005/07/01 +dnl @license AllPermissive + +AC_DEFUN([AX_RANDOM_DEVICE], [ + AC_ARG_WITH([random], + [AC_HELP_STRING([--with-random=FILE], [Use FILE as random number seed [auto-detected]])], + [RANDOM_DEVICE="$withval"], + [AC_CHECK_FILE("/dev/urandom", [RANDOM_DEVICE="/dev/urandom";], + [AC_CHECK_FILE("/dev/arandom", [RANDOM_DEVICE="/dev/arandom";], + [AC_CHECK_FILE("/dev/random", [RANDOM_DEVICE="/dev/random";])] + )]) + ]) + if test "x$RANDOM_DEVICE" != "x" ; then + AC_DEFINE([HAVE_RANDOM_DEVICE], 1, + [Define to 1 (and set RANDOM_DEVICE) if a random device is available]) + AC_SUBST([RANDOM_DEVICE]) + AC_DEFINE_UNQUOTED([RANDOM_DEVICE], ["$RANDOM_DEVICE"], + [Define to the filename of the random device (and set HAVE_RANDOM_DEVICE)]) + fi + ])dnl diff --git a/infrastructure/m4/ax_split_version.m4 b/infrastructure/m4/ax_split_version.m4 new file mode 100644 index 00000000..20b353df --- /dev/null +++ b/infrastructure/m4/ax_split_version.m4 @@ -0,0 +1,19 @@ +dnl @synopsis AX_SPLIT_VERSION(DEFINE, VERSION) +dnl +dnl Splits a version number in the format MAJOR.MINOR.POINT into it's +dnl separate components and AC_DEFINES <DEFINE>_MAJOR etc with the values. +dnl +dnl @category Automake +dnl @author Martin Ebourne <martin@zepler.org> +dnl @version +dnl @license AllPermissive + +AC_DEFUN([AX_SPLIT_VERSION],[ + ax_major_version=`echo "$2" | sed 's/\([[^.]][[^.]]*\).*/\1/'` + ax_minor_version=`echo "$2" | sed 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'` + ax_point_version=`echo "$2" | sed 's/[[^.]][[^.]]*.[[^.]][[^.]]*.\(.*\)/\1/'` + + AC_DEFINE_UNQUOTED([$1_MAJOR], [$ax_major_version], [Define to major version for $1]) + AC_DEFINE_UNQUOTED([$1_MINOR], [$ax_minor_version], [Define to minor version for $1]) + AC_DEFINE_UNQUOTED([$1_POINT], [$ax_point_version], [Define to point version for $1]) +]) diff --git a/infrastructure/m4/boxbackup_tests.m4 b/infrastructure/m4/boxbackup_tests.m4 new file mode 100644 index 00000000..09d73e04 --- /dev/null +++ b/infrastructure/m4/boxbackup_tests.m4 @@ -0,0 +1,348 @@ +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 -Werror=return-type']) + + # 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 + save_LDFLAGS=$LDFLAGS + 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 + LDFLAGS=$save_LDFLAGS + ;; + 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 netdb.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/poll.h sys/socket.h sys/time.h]) +AC_CHECK_HEADERS([sys/types.h sys/uio.h sys/un.h sys/wait.h sys/xattr.h]) +AC_CHECK_HEADERS([sys/ucred.h],,, [ + #ifdef HAVE_SYS_PARAM_H + # include <sys/param.h> + #endif + ]) +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_atim]) +AC_CHECK_MEMBERS([struct stat.st_atimespec]) +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([O_BINARY]) + +AC_CHECK_DECLS([ENOTSUP],,, [[#include <sys/errno.h>]]) +AC_CHECK_DECLS([INFTIM],,, [[#include <poll.h>]]) +AC_CHECK_DECLS([SO_PEERCRED],,, [[#include <sys/socket.h>]]) +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_MEMBERS([struct ucred.uid, struct ucred.cr_uid],,, + [[ + #ifdef HAVE_UCRED_H + # include <ucred.h> + #endif + + #ifdef HAVE_SYS_PARAM_H + # include <sys/param.h> + #endif + + #ifdef HAVE_SYS_UCRED_H + # include <sys/ucred.h> + #endif + + #ifdef HAVE_SYS_SOCKET_H + # include <sys/socket.h> + #endif + ]]) + +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([ftruncate getpeereid getpeername getpid gettimeofday lchown]) +AC_CHECK_FUNCS([setproctitle utimensat]) +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. The main +# thing to fix is that ServerStream needs to put a pointer into WaitForEvent, +# which wants to store it in struct kevent.udata, but on NetBSD that's an +# intptr_t instead of a void *, and it doesn't like accepting pointers. +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 new file mode 100644 index 00000000..e9cb72b8 --- /dev/null +++ b/infrastructure/m4/vl_lib_readline.m4 @@ -0,0 +1,168 @@ +dnl @synopsis VL_LIB_READLINE([ACTION-IF-TRUE], [ACTION-IF-FALSE]) +dnl +dnl Searches for a readline compatible library. If found, defines +dnl `HAVE_LIBREADLINE'. If the found library has the `add_history' +dnl function, sets also `HAVE_READLINE_HISTORY'. Also checks for the +dnl locations of the necessary include files and sets `HAVE_READLINE_H' +dnl or `HAVE_READLINE_READLINE_H' and `HAVE_READLINE_HISTORY_H' or +dnl 'HAVE_HISTORY_H' if the corresponding include files exists. +dnl +dnl The libraries that may be readline compatible are `libedit', +dnl `libeditline' and `libreadline'. Sometimes we need to link a +dnl termcap library for readline to work, this macro tests these cases +dnl too by trying to link with `libtermcap', `libcurses' or +dnl `libncurses' before giving up. +dnl +dnl Here is an example of how to use the information provided by this +dnl macro to perform the necessary includes or declarations in a C +dnl file: +dnl +dnl #ifdef HAVE_LIBREADLINE +dnl # if defined(HAVE_READLINE_READLINE_H) +dnl # include <readline/readline.h> +dnl # elif defined(HAVE_READLINE_H) +dnl # include <readline.h> +dnl # else /* !defined(HAVE_READLINE_H) */ +dnl extern char *readline (); +dnl # endif /* !defined(HAVE_READLINE_H) */ +dnl char *cmdline = NULL; +dnl #else /* !defined(HAVE_READLINE_READLINE_H) */ +dnl /* no readline */ +dnl #endif /* HAVE_LIBREADLINE */ +dnl +dnl #ifdef HAVE_READLINE_HISTORY +dnl # if defined(HAVE_READLINE_HISTORY_H) +dnl # include <readline/history.h> +dnl # elif defined(HAVE_HISTORY_H) +dnl # include <history.h> +dnl # else /* !defined(HAVE_HISTORY_H) */ +dnl extern void add_history (); +dnl extern int write_history (); +dnl extern int read_history (); +dnl # endif /* defined(HAVE_READLINE_HISTORY_H) */ +dnl /* no history */ +dnl #endif /* HAVE_READLINE_HISTORY */ +dnl +dnl Modifications to add --enable-gnu-readline to work around licensing +dnl problems between the traditional BSD licence and the GPL. +dnl Martin Ebourne, 2005/7/11 +dnl Rewrite to match headers with libraries and be more selective. +dnl Martin Ebourne, 2006/1/4 +dnl +dnl @category InstalledPackages +dnl @author Ville Laurikari <vl@iki.fi> +dnl @version 2002-04-04 +dnl @license AllPermissive + +AC_DEFUN([VL_LIB_READLINE], [ + AC_ARG_ENABLE( + [gnu-readline], + AC_HELP_STRING([--enable-gnu-readline], + [Use GNU readline if present (may violate GNU licence)]) + ) + vl_cv_lib_readline_compat_found=no + if test "x$enable_gnu_readline" = "xyes"; then + VL_LIB_READLINE_CHECK([readline], + [readline], + [readline/readline.h readline.h], + [readline/history.h history.h]) + fi + if test "x$vl_cv_lib_readline_compat_found" = "xno"; then + VL_LIB_READLINE_CHECK([editline], + [edit editline], + [editline/readline.h], + [editline/readline.h]) + fi + if test "x$vl_cv_lib_readline_compat_found" = "xyes"; then + m4_ifvaln([$1],[$1],[:])dnl + m4_ifvaln([$2],[else $2])dnl + fi +]) + +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 + if test -z "$termcap_lib"; then + TRY_LIB="-l$readline_lib" + else + TRY_LIB="-l$readline_lib -l$termcap_lib" + fi + LIBS="$ORIG_LIBS $TRY_LIB" + AC_TRY_LINK_FUNC([readline], [vl_cv_lib_$1="$TRY_LIB"]) + if test -n "$vl_cv_lib_$1"; then + break + fi + done + if test -n "$vl_cv_lib_$1"; then + break + fi + done + if test -z "$vl_cv_lib_$1"; then + vl_cv_lib_$1=no + LIBS="$ORIG_LIBS" + fi + ]) + + 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]) + + AC_DEFINE([HAVE_LIBREADLINE], 1, + [Define if you have a readline compatible library]) + + AC_CACHE_CHECK([whether $1 supports history], + [vl_cv_lib_$1_history], [ + vl_cv_lib_$1_history=no + AC_TRY_LINK_FUNC([add_history], [vl_cv_lib_$1_history=yes]) + ]) + if test "x$vl_cv_lib_$1_history" = "xyes"; then + vl_cv_lib_$1_history=no + AC_CHECK_HEADERS( + [$4], + [AC_DEFINE([HAVE_READLINE_HISTORY], [1], + [Define if your readline library has add_history])]) + fi + else + LIBS="$ORIG_LIBS" + fi +])dnl diff --git a/infrastructure/makebuildenv.pl.in b/infrastructure/makebuildenv.pl.in new file mode 100755 index 00000000..5f1b0618 --- /dev/null +++ b/infrastructure/makebuildenv.pl.in @@ -0,0 +1,1031 @@ +#!@PERL@ +use strict; +use Symbol; + +my @modules; +my %module_dependency; +my %module_library_link_opts; +my %header_dependency; + +$|=1; + + +# note: Mac OS X resource forks and .DS_Store files are explicity ignored + +print "Box build environment setup.\n"; + +my @implicit_deps = ('lib/common'); + +# work out platform variables +use lib 'infrastructure'; +use BoxPlatform; + +print "Building on '$build_os $build_os_ver' using ". + ($bsd_make ? "BSD" : "GNU")." $make_command.\n\n"; + +# keep copy of command line args +my $makebuildenv_args = join(' ',@ARGV); + +# do command line arguments +my $compile_line_extra = $platform_compile_line_extra; +my $link_line_extra = $platform_link_line_extra; + +# make sure local files directory exists +unless(-d 'local') +{ + mkdir 'local',0755; +} + + +# flags about the environment +my %env_flags; + +$module_dependency{"lib/common"} = ["lib/win32"]; +push @implicit_deps, "lib/win32"; + +# print "Flag: $_\n" for(keys %env_flags); + +# seed autogen code +print "Seeding autogen code...\n"; +open FINDAUTOGEN,"find . -follow -name Makefile.extra |" or die "Can't use find for locating files"; +while(<FINDAUTOGEN>) +{ + chomp; + my $file = $_; + $file =~ m~\A(.+)/[^/]+\Z~; + my $dir = $1; + open FL,$file or die "Can't open $_ for reading"; + my %vars; + $vars{_PERL} = "@PERL@"; + my $do_cmds = 0; + while(<FL>) + { + chomp; + if(m/\A(.+)\s+=\s+(.+)\Z/) + { + # is a variable + $vars{$1} = $2; + next; + } + next unless m/\S/; + if(m/AUTOGEN SEEDING/) + { + $do_cmds = 1; + } + elsif(m/\A\S/) + { + $do_cmds = 0 if $do_cmds == 2; + } + else + { + # command, run it? + if($do_cmds) + { + $do_cmds = 2; # flag something has been done + + # subsitute variables, repeatedly + my $c = $_; + $c =~ s/\A\s+//; + while(1) + { + my $did_subst = 0; + + for my $k (keys %vars) + { + $did_subst = 1 if $c =~ s/\$\($k\)/$vars{$k}/g; + } + + last unless $did_subst; + } + + # run command + unless (0 == system("(cd $dir; $c)")) + { + die "Couldn't run command $c " . + "(in $dir) for $file"; + } + } + } + } + close FL; +} +close FINDAUTOGEN; +print "done\n\n"; + + +# open test mail program template file +my $test_template_file = 'infrastructure/buildenv-testmain-template.cpp'; +open FL,$test_template_file or die "Can't open test template file\n"; +my $test_template; +read FL,$test_template,-s $test_template_file; +close FL; + + +# extra platform defines +my $extra_platform_defines = ''; + +# read in module definitions file, and any files it includes +my @modules_files; +sub read_modules_file +{ + my ($mf) = @_; + my $f = gensym; + open $f,$mf or die "Can't open modules file '$mf'\n"; + while(<$f>) + { + if(m/\AINCLUDE\s+(\S+)\Z/) + { + # include another file + read_modules_file($1) + } + else + { + push @modules_files,$_ + } + } + close $f; +} +read_modules_file('modules.txt'); + +# prepare directories... +mkdir "release",0755; +mkdir "debug",0755; + +# is the library code in another directory? +my $external_lib = readlink('lib'); +if($external_lib ne '') +{ + # adjust to root of the library distribution + $external_lib =~ s!/lib\Z!!; + $external_lib = '../'.$external_lib; + # make symlinks + make_obj_symlink('debug'); + make_obj_symlink('release'); +} +sub make_obj_symlink +{ + my $m = $_[0]; + my $target = $external_lib."/$m/lib/"; + my $link = "$m/lib"; + # check link + if(-e $link) + { + if(-l $link) + { + if(readlink($link) ne $target) + { + print "Warning: replacing $link with new link to $target\n"; + unlink $link; + } + } + else + { + die "$link already exists, but it isn't a symbolic link" + } + } + if(!-e $link) + { + symlink $target,$link or die "Can't make $m/lib symlink"; + } +} + +print "Scanning code...\n"; + +my $modules_omitted = 0; +my $modules_omitting = 0; + +# process lines in flattened modules files +for(@modules_files) +{ + # clean up line + chomp; s/\A\s+//; s/#.*\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + # omit bits on some platforms? + if(m/\AEND-OMIT/) + { + $modules_omitting = 0; + next; + } + + next if $modules_omitting; + + if(m/\AOMIT:(.+)/) + { + if($1 eq $build_os or $1 eq $ac_target_os) + { + $modules_omitted = 1; + $modules_omitting = 1; + } + next; + } + + # split up... + my ($mod, @deps_i) = split / /; + + # ignore this module? + next if ignore_module($mod); + + # deps for this platform + my @deps; + for(@deps_i) + { + my ($dep,$exclude_from) = split /!/; + # generic library translation + $dep = $env_flags{'LIBTRANS_'.$dep} if exists($env_flags{'LIBTRANS_'.$dep}); + next if $dep eq ''; + if($exclude_from =~ m/\A\+(.+)\Z/) + { + $exclude_from = $1; + my $inc = 0; + for(split /,/,$exclude_from) + { + $inc = 1 if $_ eq $build_os + } + push @deps,$dep if $inc + } + else + { + my $inc = 1; + for(split /,/,$exclude_from) + { + $inc = 0 if $_ eq $build_os + } + push @deps,$dep if $inc + } + } + + # check directory exists + die "Module $mod can't be found\n" unless -d $mod; + + # and put in lists + push @modules,$mod; + my @md; # module dependencies + my @lo; # link line options + for (@deps) + { + if(/\A-l/) + { + push @lo,$_ + } + else + { + push @md,$_ unless ignore_module($_) + } + } + $module_dependency{$mod} = [@implicit_deps,@md]; + $module_library_link_opts{$mod} = [@lo]; + + # make directories, but not if we're using an external library and this a library module + my ($s,$d) = split /\//,$mod; + if ($s ne 'lib' or $external_lib eq '') + { + mkdir "release/$s",0755; + mkdir "release/$s/$d",0755; + mkdir "debug/$s",0755; + mkdir "debug/$s/$d",0755; + } +} + +# make dirs for implicit dep +foreach my $dep (@implicit_deps) +{ + mkdir "release/$dep",0755; + mkdir "debug/$dep",0755; +} + +# write a list of all the modules we've configured to use +open CONFIGURED_MODS,'>local/modules.h' or die "Can't write configured modules list"; +print CONFIGURED_MODS <<__E; +// automatically generated file, do not edit +#ifndef _CONFIGURED_MODULES__H +#define _CONFIGURED_MODULES__H +__E +for(@implicit_deps,@modules) +{ + my $m = $_; + $m =~ s~/~_~; + print CONFIGURED_MODS "#define MODULE_$m\n"; +} +print CONFIGURED_MODS <<__E; +#endif // _CONFIGURED_MODULES__H +__E +close CONFIGURED_MODS; + + +# now make a list of all the .h files we can find, recording which module they're in +my %hfiles; +for my $mod (@modules, @implicit_deps) +{ + opendir DIR,$mod; + my @items = readdir DIR; + closedir DIR; + + # add in items from autogen directories, and create output directories + { + my @autogen_items; + + for my $di (@items) + { + if($di =~ m/\Aautogen/ && -d "$mod/$di") + { + # Read items + my $d = "$mod/$di"; + opendir DIR,$d; + my @i = readdir DIR; + closedir DIR; + for(@i) + { + next if m/\A\./; + push @autogen_items,"$di/$_" + } + } + } + @items = (@items, @autogen_items); + } + + for(grep /\.h\Z/i, @items) + { + next if /\A\._/; # Temp Mac OS Resource hack + die "Header file $_ already used in module ".$hfiles{$_}. + ", cannot add to $mod\n" if exists $hfiles{$_}; + $hfiles{$_} = $mod + } +} + +for my $mod (@modules, @implicit_deps) +{ + opendir DIR,$mod; + for my $h (grep /\.h\Z/i, readdir DIR) + { + next if $h =~ /\A\./; # Ignore Mac resource forks, autosaves, etc + + open FL,"$mod/$h" or die "can't open $mod/$h"; + my $f; + read FL,$f,-s "$mod/$h"; + close FL; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + my $i = $1; + # ignore autogen exceptions + next if $i =~ m/\Aautogen_.+?Exception.h\Z/; + # record dependency + ${$header_dependency{$h}}{$i} = 1 if exists $hfiles{$i}; + } + } + closedir DIR; +} + +# Then write a makefile for each module +print "done\n\nGenerating Makefiles...\n"; + +my %module_resources_win32; + +for my $mod (@implicit_deps, @modules) +{ + print $mod,"\n"; + + my ($type,$name) = split /\//,$mod; + + # add additional files for tests + if($type eq 'test') + { + my $testmain = $test_template; + $testmain =~ s/TEST_NAME/$name/g; + open TESTMAIN,">$mod/_main.cpp" or die "Can't open test main file for $mod for writing\n"; + print TESTMAIN $testmain; + close TESTMAIN; + + # test file... + sub writetestfile + { + my ($filename,$runcmd,$module) = @_; + + open TESTFILE,">$filename" or die "Can't open " . + "test script file for $module for writing\n"; + print TESTFILE "#!/bin/sh\necho TEST: $module\n"; + + if ($target_windows) + { + print TESTFILE <<__E; +kill_process() +{ + if test -r testfiles/\$1.pid; then + /bin/kill -0 -f `cat testfiles/\$1.pid` \\ + && /bin/kill -f `cat testfiles/\$1.pid` + rm testfiles/\$1.pid + fi +} +__E + } + else + { + print TESTFILE <<__E; +kill_process() +{ + test -r testfiles/\$1.pid \\ + && kill -0 `cat testfiles/\$1.pid` \\ + && kill `cat testfiles/\$1.pid` +} +__E + } + + if (-d "$module/testfiles") + { + print TESTFILE <<__E; +kill_daemons() +{ + kill_process bbackupd + kill_process bbstored + kill_process httpserver + kill_process s3simulator +} + +echo Killing any running daemons... +kill_daemons +__E + } + + print TESTFILE <<__E; +echo Removing old test files... +chmod -R a+rwx testfiles +rm -rf testfiles + +echo Copying new test files... +cp -p -R ../../../$module/testfiles . + +__E + + if (-e "$module/testextra") + { + open FL,"$module/testextra" or die + "Can't open $module/testextra"; + while(<FL>) {print TESTFILE} + close FL; + } + + print TESTFILE "$runcmd\nexit_status=\$?\n"; + + if (-d "$module/testfiles") + { + print TESTFILE <<__E; +kill_daemons +__E + } + + print TESTFILE "exit \$exit_status\n"; + close TESTFILE; + } + + writetestfile("$mod/_t", "GLIBCXX_FORCE_NEW=1 ". + './_test' . $platform_exe_ext . ' "$@"', $mod); + writetestfile("$mod/_t-gdb", "GLIBCXX_FORCE_NEW=1 ". + 'gdb ./_test' . $platform_exe_ext . ' "$@"', $mod); + + } + + my @all_deps_for_module; + + { + # work out what dependencies need to be run + my @deps_raw; + sub add_mod_deps + { + my ($arr_r,$nm) = @_; + if($#{$module_dependency{$nm}} >= 0) + { + push @$arr_r,@{$module_dependency{$nm}}; + for(@{$module_dependency{$nm}}) + { + add_mod_deps($arr_r,$_) + } + } + } + add_mod_deps(\@deps_raw, $mod); + # and then dedup and reorder them + my %d_done; + foreach my $dep (reverse @deps_raw) + { + if(!exists $d_done{$dep}) + { + # insert + push @all_deps_for_module, $dep; + # mark as done + $d_done{$dep} = 1; + } + } + } + + + # make include path + my $include_paths = join(' ',map {'-I../../'.$_} @all_deps_for_module); + + # is target a library? + my $target_is_library = ($type ne 'bin' && $type ne 'test'); + + # make target name + my $end_target = $name; + + if ($target_is_library) + { + $end_target .= '.a'; + } + else + { + $end_target .= $platform_exe_ext; + } + + $end_target = '_test'.$platform_exe_ext if $type eq 'test'; + + # adjust for outdir + $end_target = '$(OUTDIR)/' . $end_target; + + # start the makefile + my $mk_name_extra = ($bsd_make)?'':'X'; + open MAKE,">$mod/Makefile".$mk_name_extra or die "Can't open Makefile for $mod\n"; + my $debug_link_extra = ($target_is_library)?'':'../../debug/lib/debug/debug.a'; + + my $default_cxxflags = '@CXXFLAGS@'; + $default_cxxflags =~ s/ -O2//g; + + my $release_flags = "-O2"; + if ($target_windows) + { + $release_flags = "-O0 -g"; + } + + print MAKE <<__E; +# +# AUTOMATICALLY GENERATED FILE +# do not edit! +# +# +CXX = @CXX@ +AR = @AR@ +RANLIB = @RANLIB@ +PERL = @PERL@ +WINDRES = @WINDRES@ + +DEFAULT_CXXFLAGS = @CPPFLAGS@ $default_cxxflags @CXXFLAGS_STRICT@ \\ + $include_paths $extra_platform_defines \\ + -DBOX_VERSION="\\"$product_version\\"" +LDFLAGS += @LDFLAGS@ @LDADD_RDYNAMIC@ + +.ifdef RELEASE +CXXFLAGS += \$(DEFAULT_CXXFLAGS) -DBOX_RELEASE_BUILD $release_flags +OUTBASE = ../../release +OUTDIR = ../../release/$mod +DEPENDMAKEFLAGS = -D RELEASE +VARIENT = RELEASE +.else +# http://gcc.gnu.org/onlinedocs/libstdc++/manual/debug_mode_using.html#debug_mode.using.mode +CXXFLAGS += \$(DEFAULT_CXXFLAGS) -g -O0 -D_GLIBCXX_DEBUG +OUTBASE = ../../debug +OUTDIR = ../../debug/$mod +DEPENDMAKEFLAGS = +VARIENT = DEBUG +.endif + +__E + + if ($bsd_make) + { + print MAKE <<__E; +.ifdef V +HIDE = +_CXX = \$(CXX) +_LINK = \$(CXX) +_WINDRES = \$(WINDRES) +_AR = \$(AR) +_RANLIB = \$(RANLIB) +_PERL = \$(PERL) +.else +HIDE = @ +_CXX = @ echo " [CXX] " \$(*F) && \$(CXX) +_LINK = @ echo " [LINK] " \$(*F) && \$(CXX) +_WINDRES = @ echo " [WINDRES]" \$(*F) && \$(WINDRES) +_AR = @ echo " [AR] " \$(*F) && \$(AR) +_RANLIB = @ echo " [RANLIB] " \$(*F) && \$(RANLIB) +_PERL = @ echo " [PERL] " \$(*F) && \$(PERL) >/dev/null +.endif + +__E + } + else + { + print MAKE <<__E; +HIDE = \$(if \$(V),,@) +_CXX = \$(if \$(V),\$(CXX), @ echo " [CXX] $mod/\$<" && \$(CXX)) +_LINK = \$(if \$(V),\$(CXX), @ echo " [LINK] $mod/\$@" && \$(CXX)) +_WINDRES = \$(if \$(V),\$(WINDRES), @ echo " [WINDRES] $mod/\$<" && \$(WINDRES)) +_AR = \$(if \$(V),\$(AR), @ echo " [AR] $mod/\$@" && \$(AR)) +_RANLIB = \$(if \$(V),\$(RANLIB), @ echo " [RANLIB] $mod/\$@" && \$(RANLIB)) +_PERL = \$(if \$(V),\$(PERL), @ echo " [PERL] $mod/\$@" && \$(PERL) >/dev/null) + +__E + } + + # if there is a Makefile.pre, include it now + if(-e "$mod/Makefile.pre") + { + print MAKE ".include <Makefile.pre>\n\n"; + } + + # read directory + opendir DIR,$mod; + my @items = readdir DIR; + closedir DIR; + + # add in items from autogen directories, and create output directories + { + my @autogen_items; + for my $di (@items) + { + if($di =~ m/\Aautogen/ && -d "$mod/$di") + { + # Read items + my $d = "$mod/$di"; + opendir DIR,$d; + my @i = readdir DIR; + closedir DIR; + for(@i) + { + next if m/\A\./; + push @autogen_items,"$di/$_" + } + + # output directories + mkdir "release/$mod/$di",0755; + mkdir "debug/$mod/$di",0755; + } + } + @items = (@items, @autogen_items); + } + + # first, obtain a list of dependencies within the .h files + my %headers; + for my $h (grep /\.h\Z/i, @items) + { + open FL,"$mod/$h"; + my $f; + read FL,$f,-s "$mod/$h"; + close FL; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + ${$headers{$h}}{$1} = 1 if exists $hfiles{$1}; + } + } + + # ready for the rest of the details... + my $make; + + # then... do the cpp files... + my @obj_base; + for my $file (@items) + { + my $is_cpp = $file =~ m/\A(.+)\.cpp\Z/i; + my $is_rc = $file =~ m/\A(.+)\.rc\Z/i; + my $base = $1; + + if ($target_windows) + { + next if not $is_cpp and not $is_rc; + } + else + { + next if not $is_cpp; + } + + next if $file =~ /\A\._/; # Temp Mac OS Resource hack + + # store for later + push @obj_base,$base; + + # get the file... + open FL,"$mod/$file"; + my $f; + read FL,$f,-s "$mod/$file"; + close FL; + + my %dep; + + while($f =~ m/\#include\s+"([^"]+?)"/g) + { + insert_dep($1, \%dep) if exists $hfiles{$1}; + } + + # output filename + my $out_name = '$(OUTDIR)/'.$base.'.o'; + + # write the line for this cpp file + my @dep_paths = map + { + ($hfiles{$_} eq $mod) + ? $_ + : '../../'.$hfiles{$_}."/$_" + } + keys %dep; + + $make .= $out_name.': '.join(' ',$file,@dep_paths)."\n"; + + if ($is_cpp) + { + $make .= "\t\$(_CXX) \$(CXXFLAGS) $compile_line_extra ". + "-DBOX_MODULE=\"\\\"$mod\\\"\" " . + "-c $file -o $out_name\n\n"; + } + elsif ($is_rc) + { + $make .= "\t\$(_WINDRES) $file $out_name\n\n"; + my $res_list = $module_resources_win32{$mod}; + $res_list ||= []; + push @$res_list, $base.'.o'; + $module_resources_win32{$mod} = $res_list; + } + } + + my $has_deps = ($#{$module_dependency{$mod}} >= 0); +# ----- # always has dependencies with debug library + $has_deps = 1; + $has_deps = 0 if $target_is_library; + + # Dependency stuff + my $deps_makeinfo; + if($has_deps) + { + if($bsd_make) + { + $deps_makeinfo = <<'__E'; +.BEGIN:: +.ifndef NODEPS +. if $(.TARGETS) == "" +__E + } + else + { + # gnu make + $deps_makeinfo = <<'__E'; +.PHONY: dep_modules +dep_modules: +ifndef NODEPS +ifeq ($(strip $(.TARGETS)),) +__E + } + + # run make for things we require + for my $dep (@all_deps_for_module) + { + 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 + } + + $deps_makeinfo .= ".\tendif\n.endif\n\n"; + } + print MAKE $deps_makeinfo if $bsd_make; + + # 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"; + } + } + + # need to see if the extra makefile fragments require extra object files + # or include any more makefiles + my @objs = @obj_base; + my @makefile_includes; + + additional_objects_from_make_fragment("$mod/Makefile.extra", \@objs, \@makefile_includes); + additional_objects_from_make_fragment("$mod/Makefile.extra.$build_os", \@objs, \@makefile_includes); + + my $o_file_list = join(' ',map {'$(OUTDIR)/'.$_.'.o'} sort @objs); + + if ($has_deps and not $bsd_make) + { + print MAKE ".PHONY: all\n" . + "all: dep_modules $end_target\n\n"; + } + + print MAKE $end_target,': ',$o_file_list; + print MAKE " @lib_files" unless $target_is_library; + print MAKE "\n"; + + if ($target_windows) + { + foreach my $dep (@all_deps_for_module) + { + my $res_list = $module_resources_win32{$dep}; + next unless $res_list; + $o_file_list .= ' '.join(' ', + map {'$(OUTBASE)/'.$dep."/$_"} @$res_list); + } + } + + # stuff to make the final target... + if($target_is_library) + { + # make a library archive... + print MAKE "\t\$(HIDE) (echo -n > $end_target; rm $end_target)\n"; + print MAKE "\t\$(_AR) cq $end_target $o_file_list\n"; + print MAKE "\t\$(_RANLIB) $end_target\n"; + } + else + { + # work out library options + # need to be... least used first, in absolute order they appear in the modules.txt file + my @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); + + my $lo = ''; + my %ldone; + for(@libops) + { + next if exists $ldone{$_}; + $lo .= ' '.$_; + $ldone{$_} = 1; + } + + # link line... + print MAKE "\t\$(_LINK) \$(LDFLAGS) $link_line_extra " . + "-o $end_target $o_file_list " . + "@lib_files $lo $platform_lib_files\n"; + } + + # 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"; + } + + # dependency line? + print MAKE "\n"; + + # module dependencies for GNU make? + print MAKE $deps_makeinfo if !$bsd_make; + + # print the rest of the file + print MAKE $make,"\n"; + + # and a clean target + print MAKE <<EOF; +clean: + -rm -rf \$(OUTDIR)/* +. ifndef SUBCLEAN +EOF + for my $dep (@all_deps_for_module) + { + print MAKE "\t\$(HIDE) (cd ../../$dep; \$(MAKE) \$(DEPENDMAKEFLAGS) -D SUBCLEAN clean)\n"; + } + print MAKE ".\tendif\n"; + + # include any extra stuff + print MAKE "\n\n"; + if(-e "$mod/Makefile.extra") + { + print MAKE ".include <Makefile.extra>\n\n"; + } + if(-e "$mod/Makefile.extra.$build_os") + { + print MAKE ".include <Makefile.extra.$build_os>\n\n"; + } + for(@makefile_includes) + { + print MAKE ".include <$_>\n\n"; + } + + # and finally a target for rebuilding the build system + print MAKE "\nbuildsystem:\n\t(cd ../..; perl ./infrastructure/makebuildenv.pl $makebuildenv_args)\n\n"; + + close MAKE; + + if(!$bsd_make) + { + # need to post process this into a GNU makefile + open MAKE,">$mod/Makefile"; + open MAKEB,"$mod/MakefileX"; + + while(<MAKEB>) + { + s/\A\.\s*(ifdef|else|endif|ifndef)/$1/; + s/\A\.\s*include\s+<(.+?)>/include $1/; + s/-D\s+(\w+)/$1=1/g; + print MAKE; + } + + close MAKEB; + close MAKE; + unlink "$mod/MakefileX"; + } +} + +print "\nType 'cd <module_dir>; $make_command' to build a module\n\n"; + +if($modules_omitted) +{ + print "\nNOTE: Some modules have been omitted on this platform\n\n" +} + +sub insert_dep +{ + my ($h,$dep_r) = @_; + + # stop random recusion + return if exists $$dep_r{$h}; + + # insert more depencies + insert_dep($_,$dep_r) for keys %{$header_dependency{$h}}; + + # mark this one as a dependency + $$dep_r{$h} = 1; +} + + +sub additional_objects_from_make_fragment +{ + my ($fn,$objs_r,$include_r) = @_; + + if(-e $fn) + { + open FL,$fn or die "Can't open $fn"; + + while(<FL>) + { + chomp; + if(m/link-extra:\s*(.+)\Z/) + { + my $extra = $1; + do + { + my @o = split /\s+/, $extra; + for(@o) + { + push @$objs_r,$1 if m/\A(.+)\.o\Z/; + } + last unless $extra =~ m'\\$'; + $extra = <FL>; + } + while(1); + } + elsif(m/include-makefile:\s*(\S+)/) + { + push @$include_r,$1 + } + } + + close FL; + } +} + + +sub ignore_module +{ + exists $env_flags{'IGNORE_'.$_[0]} +} diff --git a/infrastructure/makedistribution.pl.in b/infrastructure/makedistribution.pl.in new file mode 100755 index 00000000..0ccd92be --- /dev/null +++ b/infrastructure/makedistribution.pl.in @@ -0,0 +1,363 @@ +#!@PERL@ + +use strict; +use Symbol; + +# comment string for various endings +my %comment_chars = ('cpp' => '// ', 'h' => '// ', 'pl' => '# ', 'pm' => '# ', '' => '# '); + +# other extensions which need text copying, just to remove the private stuff +# .in is included here, as these could be any kind of source, but clearly +# they have text substitutions run on them by autoconf, so we can too :) +my %text_files = ('txt' => 1, 'spec' => 1, 'in' => 1); + +# files which don't get the license added +# my %file_license = (); # 'filename' => 'GPL', 'DUAL' or 'none' + +# ---------------------------------------------- + +# filled in from the manifest file +# my %dir_license = (); # 'dir' => 'GPL', 'DUAL' or 'none' +# +# most recently specified LICENSE become default until overridden +my $current_license; # 'GPL', 'DUAL' or 'none' + +# distribution name +my $distribution = $ARGV[0]; +die "No distribution name specified on the command line" if $distribution eq ''; +my $dist_root = "distribution/$distribution"; + +# check distribution exists +die "Distribution '$distribution' does not exist" unless -d $dist_root; + +# get version +open VERSION,"$dist_root/VERSION.txt" or die "Can't open $dist_root/VERSION.txt"; +my $version = <VERSION>; +chomp $version; +my $archive_name = <VERSION>; +chomp $archive_name; +close VERSION; + +# consistency check +die "Archive name '$archive_name' is not equal to the distribution name '$distribution'" + unless $archive_name eq $distribution; + +my $svnversion = `svnversion .`; +chomp $svnversion; +$svnversion =~ tr/0-9A-Za-z/_/c; + +if($version =~ /USE_SVN_VERSION/) +{ + # for developers, use SVN version + open INFO,'svn info . |'; + my $svnurl; + while(<INFO>) + { + if(m/^URL: (.+?)[\n\r]+/) + { + $svnurl = $1; + } + } + close INFO; + $svnurl =~ m'box/(.+)$'; + my $svndir = $1; + $svndir =~ tr/0-9A-Za-z/_/c; + $version =~ s/USE_SVN_VERSION/$svndir.'_'.$svnversion/e; +} + +# make initial directory +my $base_name = "$archive_name-$version"; +system "rm -rf $base_name"; +system "rm $base_name.tgz"; +mkdir $base_name,0755; + +# get license files +my %license_text; # name of license => array of lines of license text +foreach my $license ("GPL", "DUAL") +{ + my $file = "./LICENSE-$license.txt"; + open LICENSE, $file or die "Can't open $file: $!"; + my @lines = <LICENSE>; + close LICENSE; + unshift @lines, "distribution $base_name (svn version: $svnversion)\n"; + $license_text{$license} = \@lines; +} + +# copy files, make a note of all the modules included +my %modules_included; +my $private_sections_removed = 0; +my $non_distribution_sections_removed = 0; +sub copy_from_list +{ + my $list = $_[0]; + open LIST,$list or die "Can't open $list"; + + while(my $line = <LIST>) + { + next unless $line =~ m/\S/; + chomp $line; + my @words = split /\s+/, $line; + my ($src,$dst,$other) = @words; + $dst = $src if $dst eq ''; + if($src eq 'MKDIR') + { + # actually we just need to make a directory here + mkdir "$base_name/$dst",0755; + } + elsif($src eq 'LICENSE') + { + $current_license = $dst; + } + elsif($src eq 'REPLACE-VERSION-IN') + { + replace_version_in($dst); + } + elsif($src eq 'RUN') + { + my ($junk,$cmd) = split /\s+/, $line, 2; + print "Running $cmd...\n"; + if(system($cmd) != 0) + { + print "Error running $cmd. Aborting.\n"; + exit(1); + } + } + elsif(-d $src) + { + $modules_included{$line} = 1; + copy_dir($src,$dst); + } + else + { + copy_file($src,$dst); + } + } + + close LIST; +} +copy_from_list("distribution/COMMON-MANIFEST.txt"); +copy_from_list("$dist_root/DISTRIBUTION-MANIFEST.txt"); + +# Copy in the root directory and delete the DISTRIBUTION-MANIFEST file +(system("cp $dist_root/*.* $base_name/") == 0) + or die "Copy of root extra files failed"; +unlink "$base_name/DISTRIBUTION-MANIFEST.txt" + or die "Delete of DISTRIBUTION-MANIFEST.txt file failed"; +replace_version_in("VERSION.txt"); + +# produce a new modules file +my $modules = gensym; +open $modules,"modules.txt" or die "Can't open modules.txt for reading"; +open MODULES_OUT,">$base_name/modules.txt"; + +while(<$modules>) +{ + # skip lines for modules which aren't included + next if m/\A(\w+\/\w+)\s/ && !exists $modules_included{$1}; + + # skip private sections + unless(skip_non_applicable_section($_, $modules, 'modules.txt')) + { + # copy line to out files + print MODULES_OUT + } +} + +close MODULES_OUT; +close $modules; + +# report on how many private sections were removed +print "Private sections removed: $private_sections_removed\nNon-distribution sections removed: $non_distribution_sections_removed\n"; + +# tar it up +system "tar cf - $base_name | gzip -9 - > $base_name.tgz"; + +sub copy_file +{ + my ($fn,$dst_fn) = @_; + + my $ext; + $ext = $1 if $fn =~ m/\.(\w+)\Z/; + $dst_fn =~ m~\A(.+)/[^/]+?\Z~; + + # licensed or not? + if(exists $comment_chars{$ext} && $current_license ne "none") + { + # copy as text, inserting license + # print "source copy $fn to $base_name/$dst_fn\n"; + + my $in = gensym; + open $in,$fn or die "$fn: $!"; + open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; + + my $first = <$in>; + if($first =~ m/\A#!/) + { + print OUT $first; + $first = ''; + } + + # write license + my $b = $comment_chars{$ext}; + my $this_license = $license_text{$current_license}; + for (@$this_license) + { + print OUT $b, $_; + } + + if($first ne '') + { + print OUT $first; + } + + while(<$in>) + { + unless(skip_non_applicable_section($_, $in, $fn)) + { + print OUT + } + } + + close OUT; + close $in; + } + elsif(exists $text_files{$ext}) + { + # copy this as text, to remove private stuff + # print "text copy $fn to $base_name/$dst_fn\n"; + + my $in = gensym; + open $in,$fn or die "$fn: $!"; + open OUT,">$base_name/$dst_fn" or die "$base_name/$dst_fn: $!"; + + while(<$in>) + { + unless(skip_non_applicable_section($_, $in, $fn)) + { + print OUT + } + } + + close OUT; + close $in; + } + else + { + # copy as binary + # print "binary copy $fn to $base_name/$dst_fn\n"; + my $cmd = "cp -p $fn $base_name/$dst_fn"; + system($cmd) == 0 or die "copy failed: $cmd"; + } + + # copy executable bit from src + if(-x $fn) + { + system 'chmod','a+x',"$base_name/$dst_fn" + } + else + { + system 'chmod','a-x',"$base_name/$dst_fn" + } +} + +sub skip_non_applicable_section +{ + my ($l, $filehandle, $filename) = @_; + if($l =~ m/BOX_PRIVATE_BEGIN/) + { + # skip private section + print "Removing private section from $filename\n"; + $private_sections_removed++; + while(<$filehandle>) {last if m/BOX_PRIVATE_END/} + + # skipped something + return 1; + } + elsif($l =~ m/IF_DISTRIBUTION\((.+?)\)/) + { + # which distributions does this apply to? + my $applies = 0; + for(split /,/,$1) + { + $applies = 1 if $_ eq $distribution + } + unless($applies) + { + # skip section? + print "Removing distribution specific section from $filename\n"; + $non_distribution_sections_removed++; + while(<$filehandle>) {last if m/END_IF_DISTRIBUTION/} + } + # hide this line + return 1; + } + elsif($l =~ m/END_IF_DISTRIBUTION/) + { + # hide these lines + return 1; + } + else + { + # no skipping, return this line + return 0; + } +} + +sub copy_dir +{ + my ($dir,$dst_dir) = @_; + + # copy an entire directory... first make sure it exists + my @n = split /\//,$dst_dir; + my $d = $base_name; + for(@n) + { + $d .= '/'; + $d .= $_; + mkdir $d,0755; + } + + # then do each of the files within in + opendir DIR,$dir; + my @items = readdir DIR; + closedir DIR; + + for(@items) + { + next if m/\A\./; + next if m/\A_/; + next if m/\AMakefile\Z/; + next if m/\Aautogen/; + next if m/-smf-method\Z/; # copy only the .in versions + next if m/-manifest.xml\Z/; # copy only the .in versions + if($dir eq 'docs') { + next if m/.(x[sm]l|tmpl)\Z/; # don't include doc sources + next if m/generate_except_xml.pl/; + } + next if !-f "$dir/$_"; + + copy_file("$dir/$_","$dst_dir/$_"); + } +} + +sub replace_version_in +{ + my ($file) = @_; + + my $fn = $base_name . '/' . $file; + open IN,$fn or die "Can't open $fn"; + open OUT,'>'.$fn.'.new' or die "Can't open $fn.new for writing"; + + while(<IN>) + { + s/###DISTRIBUTION-VERSION-NUMBER###/$version/g; + s/.*USE_SVN_VERSION.*/$version/g; + print OUT + } + + close OUT; + close IN; + + rename($fn.'.new', $fn) or die "Can't rename in place $fn"; +} + diff --git a/infrastructure/makeparcels.pl.in b/infrastructure/makeparcels.pl.in new file mode 100755 index 00000000..56c4ca66 --- /dev/null +++ b/infrastructure/makeparcels.pl.in @@ -0,0 +1,417 @@ +#!@PERL@ + +use strict; +use lib 'infrastructure'; +use BoxPlatform; + +my @parcels; +my %parcel_contents; + +sub starts_with ($$) +{ + my ($string,$expected) = @_; + return substr($string, 0, length $expected) eq $expected; +} + +sub os_matches ($) +{ + my ($prefix_string) = @_; + my @prefixes = split m'\,', $prefix_string; + foreach my $prefix (@prefixes) + { + return 1 if starts_with($build_os, $prefix); + return 1 if starts_with($ac_target_os, $prefix); + return 1 if starts_with("$ac_target_cpu-$ac_target_os", + $prefix); + return 1 if starts_with($ac_target, $prefix); + } + return 0; +} + +my $copy_command = "cp -p"; + +if ($build_os eq 'CYGWIN') +{ + $copy_command = "cp -pu"; # faster +} + +open PARCELS,"parcels.txt" or die "Can't open parcels file"; +{ + my $cur_parcel = ''; + while(<PARCELS>) + { + chomp; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + # omit bits on some platforms? + next if m/\AEND-OMIT/; + if(m/\AOMIT:(.+)/) + { + if (os_matches($1)) + { + while(<PARCELS>) + { + last if m/\AEND-OMIT/; + } + } + next; + } + + if (m'\AONLY:(.+)') + { + if (not os_matches($1)) + { + while (<PARCELS>) + { + last if m'\AEND-ONLY'; + } + } + next; + } + next if (m'\AEND-ONLY'); + + if (m'\AEXCEPT:(.+)') + { + if (os_matches($1)) + { + while (<PARCELS>) + { + last if m'\AEND-EXCEPT'; + } + } + next; + } + next if (m'\AEND-EXCEPT'); + + # new parcel, or a new parcel definition? + if(m/\A\s+(.+)\Z/) + { + push @{$parcel_contents{$cur_parcel}},$1 + } + else + { + $cur_parcel = $_; + push @parcels,$_; + } + } +} +close PARCELS; + +# create parcels directory +mkdir "parcels",0755; +mkdir "parcels/scripts",0755; + +# write master makefile + +open MAKE,">Makefile" or die "Can't open master Makefile for writing"; + +print MAKE <<__E; +# +# AUTOMATICALLY GENERATED FILE +# do not edit! +# +# + +MAKE = $make_command + +__E + +print MAKE "all:\t",join(' ',map {"build-".$_} @parcels),"\n\n"; + +my $runtest_script = $target_windows ? './infrastructure/mingw/runtest.sh' + : './runtest.pl'; + +print MAKE <<__END_OF_FRAGMENT; +test: debug/common/test release/common/test + +debug/common/test: + $runtest_script ALL debug + +release/common/test: + $runtest_script ALL release + +.PHONY: docs +docs: + cd docs; \$(MAKE) + +__END_OF_FRAGMENT + +my $release_flag = BoxPlatform::make_flag('RELEASE'); +my @clean_deps; + +for my $parcel (@parcels) +{ + my $version = BoxPlatform::parcel_root($parcel); + my $make_target = BoxPlatform::parcel_target($parcel); + my $dir = BoxPlatform::parcel_dir($parcel); + my @parcel_deps; + + # Need to use BSD install on Solaris + my $install_bin = $build_os eq 'SunOS' ? '/usr/ucb/install' : 'install'; + + unless ($target_windows) + { + open SCRIPT,">parcels/scripts/install-$parcel" or die + "Can't open installer script for $parcel for writing"; + print SCRIPT "#!/bin/sh\n\n"; + } + + for(@{$parcel_contents{$parcel}}) + { + my @args = split /\s+/; + + my ($type,$name,$dest) = @args; + my $optional = 0; + my $install = 1; + + if ($type eq 'optional') + { + $optional = 1; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'noinstall') + { + $install = 0; + shift @args; + ($type,$name,$dest) = @args; + } + + if($type eq 'bin') + { + my $exeext = $platform_exe_ext; + print MAKE <<EOF; +$dir/$name$exeext: release/bin/$name/$name$exeext + mkdir -p $dir + $copy_command release/bin/$name/$name$exeext $dir + +.PHONY: release/bin/$name/$name$exeext +release/bin/$name/$name$exeext: + (cd bin/$name; \$(MAKE) $release_flag) + +EOF + push @parcel_deps, "$dir/$name$exeext"; + } + elsif ($type eq 'script') + { + # Replace any variables ($ac_target etc.) with their + # values. + $name =~ s|(\$[^/ ]+)|$1|eeg; + my $fullpath = $name; + my $filename = $name; + # remove path from script name + $filename =~ s{.*/}{}; + + print MAKE <<EOF; +$dir/$filename: $fullpath + mkdir -p $dir +EOF + + if ($optional) + { + print MAKE "\ttest -r $fullpath " . + "&& $copy_command $fullpath $dir || true\n"; + } + else + { + print MAKE "\t$copy_command $fullpath $dir\n"; + } + + print MAKE "\n"; + + push @parcel_deps, "$dir/$filename"; + } + elsif($type eq 'man') + { + print MAKE <<EOF; +$dir/${name}.gz: docs/man/${name}.gz + mkdir -p $dir + $copy_command docs/man/${name}.gz $dir + +EOF + # Releases have the docs pre-made, but users + # may want to rebuild them for some reason. + 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 + +EOF + push @parcel_deps, "$dir/${name}.gz"; + } + elsif($type eq 'html') + { + print MAKE <<EOF; +$dir/docs/${name}.html: docs/htmlguide/man-html/${name}.html + mkdir -p $dir/docs + $copy_command docs/htmlguide/man-html/${name}.html $dir/docs + +EOF + # Releases have the docs pre-made, but users + # may want to rebuild them for some reason. + 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 + +EOF + push @parcel_deps, "$dir/docs/${name}.html"; + } + elsif ($type eq 'subdir') + { + shift @args; + my $subdir = shift @args; + print MAKE <<EOF; +.PHONY: $name-build $name-clean + +$name-build: + cd $subdir; \$(MAKE) @args + +$name-clean: + cd $name; \$(MAKE) clean +EOF + push @parcel_deps, "$name-build"; + push @clean_deps, "$name-clean"; + } + } + + print MAKE <<EOF; +build-$parcel: $make_target + +$make_target: @parcel_deps + test -d $dir || mkdir $dir +EOF + + for(@{$parcel_contents{$parcel}}) + { + my @args = split /\s+/; + + my ($type,$name,$dest) = @args; + + my $optional = 0; + my $install = 1; + + if ($type eq 'optional') + { + $optional = 1; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'noinstall') + { + $install = 0; + shift @args; + ($type,$name,$dest) = @args; + } + + if ($type eq 'script') + { + # remove path from script name + $name =~ s{.*/}{}; + } + + if ($type eq 'html') + { + $dest = "share/doc/@PACKAGE_TARNAME@"; + $name = "docs/$name.html"; + } + + if ($type eq 'man') + { + $name =~ /([0-9])$/; + $dest = "man/man$1"; + $name =~ s/$/\.gz/; + } + + if ($install and not $target_windows and not $type eq "subdir") + { + my $local_install_dir = $install_into_dir; + if (defined $dest) + { + if ($dest =~ m,^/,) + { + # Don't add $prefix if $dest is a literal path + $local_install_dir = $dest; + } + else + { + $local_install_dir = "@prefix@/$dest"; + } + } + print SCRIPT "mkdir -p " . + "\${DESTDIR}$local_install_dir/\n"; + print SCRIPT "$install_bin $name " . + "\${DESTDIR}$local_install_dir\n"; + } + } + + unless ($target_windows) + { + close SCRIPT; + chmod 0755,"parcels/scripts/install-$parcel"; + } + + my $root = BoxPlatform::parcel_root($parcel); + + unless ($target_windows) + { + print MAKE "\tcp parcels/scripts/install-$parcel $dir\n"; + } + + print MAKE "\t(cd parcels; tar cf - $root | gzip -9 - > $root.tgz )\n"; + + print MAKE "\n"; + + unless ($target_windows) + { + print MAKE "install-$parcel:\n"; + print MAKE "\t(cd $dir; ./install-$parcel)\n\n"; + } +} + +print MAKE <<EOF; +install: + cat local/install.msg + +clean: @clean_deps + cd docs; \$(MAKE) clean +EOF + +if ($build_os eq 'CYGWIN') +{ + 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"; +} + +for my $parcel (@parcels) +{ + # 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"; +} + +close MAKE; + +open INSTALLMSG,">local/install.msg" or die "Can't open install message file for writing"; +print INSTALLMSG <<__E; + +Parcels need to be installed separately, and as root. Type one of the following: + +__E + +for(@parcels) +{ + print INSTALLMSG " $make_command install-".$_."\n"; +} +print INSTALLMSG "\n"; + +close INSTALLMSG; + diff --git a/infrastructure/mingw/compile-boxbackup-cygwin.sh b/infrastructure/mingw/compile-boxbackup-cygwin.sh new file mode 100755 index 00000000..02b05852 --- /dev/null +++ b/infrastructure/mingw/compile-boxbackup-cygwin.sh @@ -0,0 +1,57 @@ +#!/bin/sh + +set -e +set -x + +basedir=`cd $(dirname $0)/../../.. && pwd` +cd $basedir + +wget -c https://cygwin.com/setup-x86_64.exe \ +|| powershell wget https://cygwin.com/setup-x86_64.exe -UseBasicParsing -outfile setup-x86_64.exe + +chmod a+x setup-x86_64.exe +./setup-x86_64.exe --quiet-mode --packages \ + "autoconf,automake,gdb,make,mingw64-x86_64-gcc,mingw64-x86_64-gcc-g++, + ,mingw64-x86_64-zlib,libxml2,libxslt,perl,subversion,unzip,vim,wget" + +install_prefix=/usr/x86_64-w64-mingw32 +compiler_prefix=x86_64-w64-mingw32 + +openssl_source=https://www.openssl.org/source/ +latest_openssl=`wget -O- -q $openssl_source \ +| grep '<td><a href="openssl-1.0.*.tar.gz">' \ +| sed -e 's/.tar.gz">.*//' -e 's/.*"//' | sort | tail -1` + +wget -c $openssl_source/$latest_openssl.tar.gz +tar xzf $latest_openssl.tar.gz --exclude $latest_openssl/Makefile +( + cd $latest_openssl + ./Configure --prefix=$install_prefix mingw64 \ + --cross-compile-prefix=$compiler_prefix- + + # Avoid recompilation by caching the previous Makefile for its timestamp, + # and reusing if it hasn't changed. + if diff --brief Makefile ../openssl.makefile.cache; then + cp -a ../openssl.makefile.cache Makefile + else + cp -a Makefile ../openssl.makefile.cache + fi + + make + make install_sw +) + +pcre_url=`wget -O- -q ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/ \ +| grep 'pcre-8.*.tar.bz2"' | sed -e 's/">pcre-.*//' | sed -e 's/.*<a href="//' \ +| tail -1` +pcre_version=`basename $pcre_url .tar.bz2` +wget -c $pcre_url +tar xjf `basename $pcre_url` +( + cd $pcre_version + ./configure --prefix=$install_prefix --disable-shared \ + --host=$compiler_prefix + make + make install +) + diff --git a/infrastructure/mingw/configure.sh b/infrastructure/mingw/configure.sh new file mode 100755 index 00000000..98c0cb49 --- /dev/null +++ b/infrastructure/mingw/configure.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +source `dirname $0`/environment.sh + +if [ ! -r "$DEP_PATH/lib/libssl.a" ]; then + echo "Error: install OpenSSL as instructed by" \ + "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 + exit 2 +fi + +if [ ! -r "$DEP_PATH/lib/libpcreposix.a" \ + -o ! -r "$DEP_PATH/lib/libpcre.a" \ + -o ! -r "$DEP_PATH/include/pcreposix.h" ]; then + echo "Error: install PCRE as instructed by" \ + "docs/backup/win32_build_on_cygwin_using_mingw.txt" >&2 + exit 2 +fi + +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 + +if [ ! -x "configure" ]; then + if ! ./bootstrap; then + echo "Error: bootstrap failed, aborting." >&2 + exit 1 + fi +fi + +if ! ./configure "$@" --host=$target \ + --with-ssl-headers="${DEP_PATH}/include" \ + --with-ssl-lib="${DEP_PATH}/lib" \ + CFLAGS="-mthreads" \ + CXXFLAGS="-mthreads" \ + LDFLAGS="-Wl,-Bstatic -mthreads -L${LIBZ_PATH}" \ + LIBS="-lws2_32 -lgdi32" +then + echo "Error: configure failed, aborting." >&2 + exit 1 +fi + +exit 0 diff --git a/infrastructure/mingw/environment.sh b/infrastructure/mingw/environment.sh new file mode 100644 index 00000000..1f2f14c2 --- /dev/null +++ b/infrastructure/mingw/environment.sh @@ -0,0 +1,11 @@ +case "`uname -m`" in +x86_64) + DEP_PATH=/usr/x86_64-w64-mingw32 + target=x86_64-w64-mingw32 ;; +i686) + DEP_PATH=/usr/i686-pc-mingw32 + target=i686-pc-mingw32 ;; +*) + echo "Error: unknown machine type `uname -m`" >&2; exit 1 ;; +esac + diff --git a/infrastructure/mingw/runtest.sh b/infrastructure/mingw/runtest.sh new file mode 100755 index 00000000..4deecb47 --- /dev/null +++ b/infrastructure/mingw/runtest.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +source `dirname $0`/environment.sh + +export PATH=$DEP_PATH/sys-root/mingw/bin:$PATH + +exec `dirname $0`/../../runtest.pl "$@" diff --git a/infrastructure/msvc/2003/bbackupctl.vcproj b/infrastructure/msvc/2003/bbackupctl.vcproj new file mode 100644 index 00000000..02f7482e --- /dev/null +++ b/infrastructure/msvc/2003/bbackupctl.vcproj @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="bbackupctl" + ProjectGUID="{9FD51412-E945-4457-A17A-CA3C505CF431}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/bbackupctl.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupctl" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupctl\bbackupctl.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/bbackupd.vcproj b/infrastructure/msvc/2003/bbackupd.vcproj new file mode 100644 index 00000000..f34db0cc --- /dev/null +++ b/infrastructure/msvc/2003/bbackupd.vcproj @@ -0,0 +1,219 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="bbackupd" + ProjectGUID="{22D325FB-9131-4BD6-B390-968F0491D687}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(SolutionDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(SolutionDir)..\..\..\..\boost_1_31_0";"$(SolutionDir)..\..\..\..\openssl\include";"$(SolutionDir)..\..\..\..\zlib\include";"$(SolutionDir)..\..\..\lib\backupclient";"$(SolutionDir)..\..\..\lib\server";"$(SolutionDir)..\..\..\lib\crypto";"$(SolutionDir)..\..\..\lib\compress";"$(SolutionDir)..\..\..\lib\win32";"$(SolutionDir)..\..\..\lib\common\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="2" + IgnoreAllDefaultLibraries="FALSE" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/bbackupd.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOOST_REGEX_NO_LIB" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupd" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\bbackupd.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="bbackupd" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.h"> + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/boxbackup.sln b/infrastructure/msvc/2003/boxbackup.sln new file mode 100644 index 00000000..d9a28041 --- /dev/null +++ b/infrastructure/msvc/2003/boxbackup.sln @@ -0,0 +1,57 @@ +Microsoft Visual Studio Solution File, Format Version 8.00 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfiguration) = preSolution + Debug = Debug + Release = Release + EndGlobalSection + GlobalSection(ProjectConfiguration) = postSolution + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.ActiveCfg = Debug|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Debug.Build.0 = Debug|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.ActiveCfg = Release|Win32 + {FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}.Release.Build.0 = Release|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.ActiveCfg = Debug|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Debug.Build.0 = Debug|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.ActiveCfg = Release|Win32 + {A089CEE6-EBF0-4232-A0C0-74850A8127A6}.Release.Build.0 = Release|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.ActiveCfg = Debug|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Debug.Build.0 = Debug|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Release.ActiveCfg = Release|Win32 + {22D325FB-9131-4BD6-B390-968F0491D687}.Release.Build.0 = Release|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.ActiveCfg = Debug|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Debug.Build.0 = Debug|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.ActiveCfg = Release|Win32 + {28C29E72-76A2-4D0C-B35B-12D446733D2E}.Release.Build.0 = Release|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.ActiveCfg = Debug|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Debug.Build.0 = Debug|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.ActiveCfg = Release|Win32 + {9FD51412-E945-4457-A17A-CA3C505CF431}.Release.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + EndGlobalSection + GlobalSection(ExtensibilityAddIns) = postSolution + EndGlobalSection +EndGlobal diff --git a/infrastructure/msvc/2003/boxquery.vcproj b/infrastructure/msvc/2003/boxquery.vcproj new file mode 100644 index 00000000..6ac09024 --- /dev/null +++ b/infrastructure/msvc/2003/boxquery.vcproj @@ -0,0 +1,174 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="boxquery" + ProjectGUID="{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + RootNamespace="boxquery" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/boxquery.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOOST_REGEX_NO_LIB" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\Release\common.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="FALSE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="backupquery" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupquery\autogen_Documentation.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.cpp"> + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\bbackupquery.cpp"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="bin" + Filter=""> + <Filter + Name="backupquery" + Filter=""> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + <File + RelativePath="..\..\..\lib\win32\messages.rc"> + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/common.vcproj b/infrastructure/msvc/2003/common.vcproj new file mode 100644 index 00000000..fb18b76a --- /dev/null +++ b/infrastructure/msvc/2003/common.vcproj @@ -0,0 +1,672 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="common" + ProjectGUID="{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="4" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;BOX_RELEASE_BUILD " + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="4" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + 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\";"$(SolutionDir)..\..\..\..\boost_1_31_0\"" + PreprocessorDefinitions="BOOST_REGEX_NO_LIB;WIN32;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <Filter + Name="lib" + Filter=""> + <Filter + Name="compress" + Filter=""> + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.cpp"> + </File> + </Filter> + <Filter + Name="common" + Filter=""> + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ConversionString.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugAssertFailed.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugMemLeakFinder.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\DebugPrintf.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Logging.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\Utils.cpp"> + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.cpp"> + </File> + </Filter> + <Filter + Name="backupclient" + Filter=""> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbIdx.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCombine.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileRevDiff.cpp"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectDump.cpp"> + </File> + </Filter> + <Filter + Name="crypto" + Filter=""> + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.cpp"> + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.cpp"> + </File> + </Filter> + <Filter + Name="win32" + Filter=""> + <File + RelativePath="..\..\..\lib\win32\emu.cpp"> + </File> + <File + RelativePath="..\..\..\lib\win32\getopt_long.cxx"> + </File> + </Filter> + <Filter + Name="server" + Filter=""> + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\Socket.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.cpp"> + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.cpp"> + </File> + + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + <Filter + Name="lib" + Filter=""> + <Filter + Name="compress" + Filter=""> + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\Compress.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressException.h"> + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.h"> + </File> + </Filter> + <Filter + Name="common" + Filter=""> + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BannerText.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BeginStructPackForWire.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Box.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxPlatform.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxPortsAndFiles.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToUnix.h"> + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\CommonException.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Conversion.h"> + </File> + <File + RelativePath="..\..\..\lib\common\EndStructPackForWire.h"> + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.h"> + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FileModificationTime.h"> + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Guards.h"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.h"> + </File> + <File + RelativePath="..\..\..\lib\common\LinuxWorkaround.h"> + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Logging.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MainHelper.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFinder.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOff.h"> + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOn.h"> + </File> + <File + RelativePath="..\..\..\lib\common\NamedLock.h"> + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.h"> + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.h"> + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.h"> + </File> + <File + RelativePath="..\..\..\lib\common\TemporaryDirectory.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Test.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Timer.h"> + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.h"> + </File> + <File + RelativePath="..\..\..\lib\common\Utils.h"> + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.h"> + </File> + <File + RelativePath="..\..\..\lib\common\BoxVersion.h"> + </File> + </Filter> + <Filter + Name="backupclient" + Filter=""> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreConstants.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreException.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileWire.h"> + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectMagic.h"> + </File> + </Filter> + <Filter + Name="crypto" + Filter=""> + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherException.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.h"> + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.h"> + </File> + </Filter> + <Filter + Name="win32" + Filter=""> + <File + RelativePath="..\..\..\lib\win32\emu.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\getopt.h"> + </File> + <File + RelativePath="..\..\..\lib\win32\WinNamedPipeStream.h"> + </File> + </Filter> + <Filter + Name="server" + Filter=""> + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolWire.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerException.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\ServerTLS.h"> + </File> + <File + RelativePath="..\..\..\lib\server\Socket.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketListen.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.h"> + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.h"> + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.h"> + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.h"> + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2003/win32test.vcproj b/infrastructure/msvc/2003/win32test.vcproj new file mode 100644 index 00000000..2ef7164e --- /dev/null +++ b/infrastructure/msvc/2003/win32test.vcproj @@ -0,0 +1,148 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="7.10" + Name="win32test" + ProjectGUID="{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + Keyword="Win32Proj"> + <Platforms> + <Platform + Name="Win32"/> + </Platforms> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + CharacterSet="2"> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\bin\bbackupd";"$(ProjectDir)..\..\..\..\db-4.2.52.NC\build_win32";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + MinimalRebuild="TRUE" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="4"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="2" + GenerateDebugInformation="TRUE" + ProgramDatabaseFile="$(OutDir)/win32test.pdb" + SubSystem="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + CharacterSet="2" + WholeProgramOptimization="TRUE"> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="TRUE" + OptimizeForProcessor="1" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\bin\bbackupd";"$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\..\openssl\include";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\lib\common\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING" + RuntimeLibrary="0" + BufferSecurityCheck="FALSE" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="TRUE" + DebugInformationFormat="3"/> + <Tool + Name="VCCustomBuildTool"/> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib $(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib $(ProjectDir)..\..\..\Release\common.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="TRUE" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1"/> + <Tool + Name="VCMIDLTool"/> + <Tool + Name="VCPostBuildEventTool"/> + <Tool + Name="VCPreBuildEventTool"/> + <Tool + Name="VCPreLinkEventTool"/> + <Tool + Name="VCResourceCompilerTool"/> + <Tool + Name="VCWebServiceProxyGeneratorTool"/> + <Tool + Name="VCXMLDataGeneratorTool"/> + <Tool + Name="VCWebDeploymentTool"/> + <Tool + Name="VCManagedWrapperGeneratorTool"/> + <Tool + Name="VCAuxiliaryManagedWrapperGeneratorTool"/> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}"> + <File + RelativePath="..\..\..\lib\win32\emu.cpp"> + </File> + <File + RelativePath="..\..\..\test\win32\testlibwin32.cpp"> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}"> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}"> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt"> + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/bbackupctl.vcproj b/infrastructure/msvc/2005/bbackupctl.vcproj new file mode 100644 index 00000000..216a284b --- /dev/null +++ b/infrastructure/msvc/2005/bbackupctl.vcproj @@ -0,0 +1,222 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="bbackupctl" + ProjectGUID="{9FD51412-E945-4457-A17A-CA3C505CF431}" + RootNamespace="bbackupctl" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/bbackupctl.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupctl.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupctl" + > + <File + RelativePath="..\..\..\bin\bbackupctl\bbackupctl.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/bbackupd.vcproj b/infrastructure/msvc/2005/bbackupd.vcproj new file mode 100644 index 00000000..ac8eb86a --- /dev/null +++ b/infrastructure/msvc/2005/bbackupd.vcproj @@ -0,0 +1,299 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="bbackupd" + ProjectGUID="{22D325FB-9131-4BD6-B390-968F0491D687}" + RootNamespace="bbackupd" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(SolutionDir)..\..\..\lib\backupclient";"$(SolutionDir)..\..\..\lib\common";"$(SolutionDir)..\..\..\lib\compress";"$(SolutionDir)..\..\..\lib\crypto";"$(SolutionDir)..\..\..\lib\server";"$(SolutionDir)..\..\..\lib\win32";"$(SolutionDir)..\..\..\..\openssl\inc32";"$(SolutionDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib User32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="2" + IgnoreAllDefaultLibraries="false" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/bbackupd.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupd.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupd" + > + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\bbackupd.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="bin" + > + <Filter + Name="bbackupd" + > + <File + RelativePath="..\..\..\bin\bbackupd\autogen_ClientException.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientContext.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDeleteList.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientDirectoryRecord.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupClientInodeToIDMap.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\BackupDaemon.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32BackupService.h" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupd\Win32ServiceFunctions.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/boxbackup.sln b/infrastructure/msvc/2005/boxbackup.sln new file mode 100644 index 00000000..833066e9 --- /dev/null +++ b/infrastructure/msvc/2005/boxbackup.sln @@ -0,0 +1,55 @@ +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual C++ Express 2005 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "boxquery", "boxquery.vcproj", "{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "common", "common.vcproj", "{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupd", "bbackupd.vcproj", "{22D325FB-9131-4BD6-B390-968F0491D687}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "win32test", "win32test.vcproj", "{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bbackupctl", "bbackupctl.vcproj", "{9FD51412-E945-4457-A17A-CA3C505CF431}" + ProjectSection(ProjectDependencies) = postProject + {A089CEE6-EBF0-4232-A0C0-74850A8127A6} = {A089CEE6-EBF0-4232-A0C0-74850A8127A6} + EndProjectSection +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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/infrastructure/msvc/2005/boxbackup.suo b/infrastructure/msvc/2005/boxbackup.suo new file mode 100644 index 00000000..534f337c Binary files /dev/null and b/infrastructure/msvc/2005/boxbackup.suo differ diff --git a/infrastructure/msvc/2005/boxquery.vcproj b/infrastructure/msvc/2005/boxquery.vcproj new file mode 100644 index 00000000..776c0ac9 --- /dev/null +++ b/infrastructure/msvc/2005/boxquery.vcproj @@ -0,0 +1,246 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="boxquery" + ProjectGUID="{FE9EC666-4B3A-4370-B3D4-DEBD4A21F36E}" + RootNamespace="boxquery" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\pcre";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/boxquery.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/bbackupquery.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="false" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="bin" + > + <Filter + Name="backupquery" + > + <File + RelativePath="..\..\..\bin\bbackupquery\autogen_Documentation.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.cpp" + > + </File> + <File + RelativePath="..\..\..\bin\bbackupquery\bbackupquery.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="bin" + > + <Filter + Name="backupquery" + > + <File + RelativePath="..\..\..\bin\bbackupquery\BackupQueries.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + <File + RelativePath="..\..\..\lib\win32\messages.rc" + > + </File> + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/common.vcproj b/infrastructure/msvc/2005/common.vcproj new file mode 100644 index 00000000..256bce06 --- /dev/null +++ b/infrastructure/msvc/2005/common.vcproj @@ -0,0 +1,896 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="common" + ProjectGUID="{A089CEE6-EBF0-4232-A0C0-74850A8127A6}" + RootNamespace="common" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="4" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + Description="Determining Version Number" + CommandLine="perl $(InputDir)..\getversion.pl" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(ProjectDir)..\..\..\lib\common";"$(ProjectDir)..\..\..\lib\compress";"$(ProjectDir)..\..\..\lib\crypto";"$(ProjectDir)..\..\..\lib\server";"$(ProjectDir)..\..\..\lib\win32";"$(ProjectDir)..\..\..\..\openssl\inc32";"$(ProjectDir)..\..\..\..\zlib\include";"$(ProjectDir)..\..\..\..\pcre";$(NOINHERIT)" + PreprocessorDefinitions="WIN32;_DEBUG;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + WarnAsError="false" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="4" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + AdditionalIncludeDirectories=""$(ProjectDir)..\..\..\lib\backupclient";"$(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\"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLibrarianTool" + OutputFile="$(OutDir)/common.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <Filter + Name="lib" + > + <Filter + Name="compress" + > + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.cpp" + > + </File> + </Filter> + <Filter + Name="common" + > + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ConversionString.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugAssertFailed.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugMemLeakFinder.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\DebugPrintf.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Logging.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Timer.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\Utils.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.cpp" + > + </File> + </Filter> + <Filter + Name="backupclient" + > + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCmbIdx.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCombine.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileRevDiff.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectDump.cpp" + > + </File> + </Filter> + <Filter + Name="crypto" + > + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.cpp" + > + </File> + </Filter> + <Filter + Name="win32" + > + <File + RelativePath="..\..\..\lib\win32\emu.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\win32\getopt_long.cxx" + > + </File> + </Filter> + <Filter + Name="server" + > + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\Socket.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.cpp" + > + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.cpp" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + <Filter + Name="lib" + > + <Filter + Name="compress" + > + <File + RelativePath="..\..\..\lib\compress\autogen_CompressException.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\Compress.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressException.h" + > + </File> + <File + RelativePath="..\..\..\lib\compress\CompressStream.h" + > + </File> + </Filter> + <Filter + Name="common" + > + <File + RelativePath="..\..\..\lib\common\autogen_CommonException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\autogen_ConversionException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BannerText.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BeginStructPackForWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Box.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxConfig-MSVC.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxPlatform.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxPortsAndFiles.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTime.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToText.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxTimeToUnix.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\BoxVersion.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\CollectInBufferStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\CommonException.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Configuration.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Conversion.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\EndStructPackForWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\EventWatchFilesystemObject.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ExcludeList.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FdGetLine.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileModificationTime.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\FileStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Guards.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\IOStreamGetLine.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\LocalProcessStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Logging.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MainHelper.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemBlockStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFinder.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOff.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\MemLeakFindOn.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\NamedLock.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\PartialReadStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\PathUtils.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadGatherStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\ReadLoggingStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\StreamableMemBlock.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\TemporaryDirectory.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Test.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Timer.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\UnixUser.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\Utils.h" + > + </File> + <File + RelativePath="..\..\..\lib\common\WaitForEvent.h" + > + </File> + </Filter> + <Filter + Name="backupclient" + > + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupProtocolClient.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\autogen_BackupStoreException.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientCryptoKeys.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientFileAttributes.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupClientRestore.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreConstants.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreDirectory.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreException.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFile.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileCryptVar.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileEncodeStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilename.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFilenameClear.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreFileWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\backupclient\BackupStoreObjectMagic.h" + > + </File> + </Filter> + <Filter + Name="crypto" + > + <File + RelativePath="..\..\..\lib\crypto\autogen_CipherException.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherAES.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherBlowfish.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherContext.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherDescription.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\CipherException.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\MD5Digest.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\Random.h" + > + </File> + <File + RelativePath="..\..\..\lib\crypto\RollingChecksum.h" + > + </File> + </Filter> + <Filter + Name="win32" + > + <File + RelativePath="..\..\..\lib\win32\emu.h" + > + </File> + <File + RelativePath="..\..\..\lib\win32\getopt.h" + > + </File> + </Filter> + <Filter + Name="server" + > + <File + RelativePath="..\..\..\lib\server\autogen_ConnectionException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\autogen_ServerException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Daemon.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Protocol.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolObject.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolUncertainStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ProtocolWire.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerException.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\ServerTLS.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\Socket.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketListen.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStream.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SocketStreamTLS.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\SSLLib.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\TLSContext.h" + > + </File> + <File + RelativePath="..\..\..\lib\server\WinNamedPipeStream.h" + > + </File> + </Filter> + </Filter> + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2005/win32test.vcproj b/infrastructure/msvc/2005/win32test.vcproj new file mode 100644 index 00000000..0f97c302 --- /dev/null +++ b/infrastructure/msvc/2005/win32test.vcproj @@ -0,0 +1,218 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="win32test" + ProjectGUID="{28C29E72-76A2-4D0C-B35B-12D446733D2E}" + RootNamespace="win32test" + Keyword="Win32Proj" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Debug|Win32" + OutputDirectory="..\..\..\Debug" + IntermediateDirectory="..\..\..\Debug" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + Optimization="0" + 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"" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + MinimalRebuild="true" + BasicRuntimeChecks="3" + RuntimeLibrary="1" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="4" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalDependencies="Ws2_32.lib Advapi32.lib $(ProjectDir)..\..\..\..\zlib\lib\zdll.lib $(ProjectDir)..\..\..\..\openssl\out32dll\libeay32.lib $(ProjectDir)..\..\..\..\openssl\out32dll\ssleay32.lib $(ProjectDir)..\..\..\Debug\common.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcreposix.lib $(ProjectDir)..\..\..\..\pcre\bin\debug\pcre.lib" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="2" + GenerateDebugInformation="true" + ProgramDatabaseFile="$(OutDir)/win32test.pdb" + SubSystem="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + <Configuration + Name="Release|Win32" + OutputDirectory="..\..\..\Release" + IntermediateDirectory="..\..\..\Release" + ConfigurationType="1" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + CharacterSet="2" + WholeProgramOptimization="1" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + /> + <Tool + Name="VCCLCompilerTool" + EnableFiberSafeOptimizations="true" + 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"" + PreprocessorDefinitions="WIN32;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC" + RuntimeLibrary="0" + BufferSecurityCheck="false" + UsePrecompiledHeader="0" + WarningLevel="3" + Detect64BitPortabilityProblems="true" + DebugInformationFormat="3" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + 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" + OutputFile="$(OutDir)/win32test.exe" + LinkIncremental="1" + IgnoreDefaultLibraryNames="" + GenerateDebugInformation="true" + SubSystem="1" + OptimizeReferences="2" + EnableCOMDATFolding="2" + OptimizeForWindows98="1" + TargetMachine="1" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;def;odl;idl;hpj;bat;asm;asmx" + UniqueIdentifier="{4FC737F1-C7A5-4376-A066-2A32D752A2FF}" + > + <File + RelativePath="..\..\..\lib\win32\emu.cpp" + > + </File> + <File + RelativePath="..\..\..\test\win32\testlibwin32.cpp" + > + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl;inc;xsd" + UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" + > + </Filter> + <Filter + Name="Resource Files" + Filter="rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx" + UniqueIdentifier="{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}" + > + </Filter> + <File + RelativePath="..\..\..\ReadMe.txt" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/infrastructure/msvc/2010/bbackupctl.vcxproj b/infrastructure/msvc/2010/bbackupctl.vcxproj new file mode 100644 index 00000000..eb40d161 --- /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-$(Platform)\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-$(Platform)\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-$(Platform)\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcreposixd.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcred.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..47906a26 --- /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-$(Platform)\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-$(Platform)\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-$(Platform)\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcreposixd.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcred.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..f9f9d3e8 --- /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-$(Platform)\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..2d48f9a8 --- /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-$(Platform)\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..4893f98c --- /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-$(Platform)\include;$(ProjectDir)..\..\..\..\openssl-$(Platform)\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-$(Platform)\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-$(Platform)\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcreposixd.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcred.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..ceefd548 --- /dev/null +++ b/infrastructure/msvc/2010/common.vcxproj @@ -0,0 +1,249 @@ +<?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-$(Platform)\include;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre-$(Platform)\include</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-$(Platform)\lib\pcred.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcreposixd.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\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-$(Platform)\inc32;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre\pcre-$(Platform)\include-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\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\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..b546f8d4 --- /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-$(Platform)\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-$(Platform)\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\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..6fe04cd1 --- /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-$(Platform)\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-$(Platform)\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\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..ed8b0597 --- /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-$(Platform)\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-$(Platform)\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-$(Platform)\out32dll\libeay32.lib;$(ProjectDir)..\..\..\..\openssl-$(Platform)\out32dll\ssleay32.lib;$(ProjectDir)..\..\..\Release\common.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcreposixd.lib;$(ProjectDir)..\..\..\..\pcre-$(Platform)\lib\pcred.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/2013/bbackupctl.vcxproj b/infrastructure/msvc/2013/bbackupctl.vcxproj new file mode 100644 index 00000000..5f021c26 --- /dev/null +++ b/infrastructure/msvc/2013/bbackupctl.vcxproj @@ -0,0 +1,117 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</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\backupstore;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\bin\bbackupd;$(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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>$(OutDir)\libbackupclient.lib;$(OutDir)\libbackupstore.lib;VssApi.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations> + <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\backupclient;$(ProjectDir)..\..\..\lib\backupstore;$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\bin\bbackupd;$(ProjectDir)..\..\..\..\openssl\include;$(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>true</BufferSecurityCheck> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>$(OutDir)\libbackupclient.lib;$(OutDir)\libbackupstore.lib;VssApi.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)bbackupctl.exe</OutputFile> + <IgnoreSpecificDefaultLibraries> + </IgnoreSpecificDefaultLibraries> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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/2013/bbackupd.vcxproj b/infrastructure/msvc/2013/bbackupd.vcxproj new file mode 100644 index 00000000..791e4813 --- /dev/null +++ b/infrastructure/msvc/2013/bbackupd.vcxproj @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>MultiByte</CharacterSet> + <UseOfMfc>false</UseOfMfc> + <PlatformToolset>v120</PlatformToolset> + </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'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</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)..\..\..\lib\httpserver;$(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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>$(OutDir)\libbackupclient.lib;$(OutDir)\libbackupstore.lib;VssApi.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations> + <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\lib\httpserver;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\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>true</BufferSecurityCheck> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>$(OutDir)\libbackupclient.lib;$(OutDir)\libbackupstore.lib;VssApi.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)bbackupd.exe</OutputFile> + <IgnoreSpecificDefaultLibraries> + </IgnoreSpecificDefaultLibraries> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences> + </OptimizeReferences> + <EnableCOMDATFolding> + </EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <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> + <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> + <ItemGroup> + <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\BackupDaemonInterface.h" /> + <ClInclude Include="..\..\..\bin\bbackupd\Win32BackupService.h" /> + <ClInclude Include="..\..\..\bin\bbackupd\Win32ServiceFunctions.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/msvc/2013/bbstoreaccounts.vcxproj b/infrastructure/msvc/2013/bbstoreaccounts.vcxproj new file mode 100644 index 00000000..d5a37d63 --- /dev/null +++ b/infrastructure/msvc/2013/bbstoreaccounts.vcxproj @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <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)..\..\..\lib\httpserver;$(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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>$(OutDir)\libbackupstore.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <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)..\..\..\lib\httpserver;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <AdditionalDependencies>$(OutDir)\libbackupstore.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib</AdditionalDependencies> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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/2013/bbstored.vcxproj b/infrastructure/msvc/2013/bbstored.vcxproj new file mode 100644 index 00000000..f703ab94 --- /dev/null +++ b/infrastructure/msvc/2013/bbstored.vcxproj @@ -0,0 +1,109 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <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\httpserver;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <DisableSpecificWarnings>4521;4996</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <AdditionalDependencies>$(OutDir)\libbackupstore.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <PreprocessorDefinitions>WIN32;NDEBUG;_LIB;BOX_RELEASE_BUILD;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <AdditionalIncludeDirectories>$(SolutionDir)..\..\..\lib\backupclient;$(SolutionDir)..\..\..\lib\backupstore;$(SolutionDir)..\..\..\lib\raidfile;$(SolutionDir)..\..\..\lib\common;$(SolutionDir)..\..\..\lib\compress;$(SolutionDir)..\..\..\lib\crypto;$(SolutionDir)..\..\..\lib\server;$(SolutionDir)..\..\..\lib\httpserver;$(SolutionDir)..\..\..\lib\win32;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <DisableSpecificWarnings>4521;4996</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <AdditionalDependencies>$(OutDir)\libbackupstore.lib;$(OutDir)\qdbm.lib;Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib</AdditionalDependencies> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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" /> + <ClCompile Include="..\..\..\qdbm\depot.c" /> + </ItemGroup> + <ItemGroup> + <None Include="..\..\..\bin\bbstored\bbstored-certs.in" /> + <None Include="..\..\..\bin\bbstored\bbstored-config.in" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\..\bin\bbstored\BackupStoreDaemon.h" /> + <ClInclude Include="..\..\..\qdbm\depot.h" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project> \ No newline at end of file diff --git a/infrastructure/msvc/2013/boxbackup.sln b/infrastructure/msvc/2013/boxbackup.sln new file mode 100644 index 00000000..dbd14aae --- /dev/null +++ b/infrastructure/msvc/2013/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/2013/boxquery.vcxproj b/infrastructure/msvc/2013/boxquery.vcxproj new file mode 100644 index 00000000..fd871d3b --- /dev/null +++ b/infrastructure/msvc/2013/boxquery.vcxproj @@ -0,0 +1,130 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</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)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations> + <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;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <AdditionalDependencies>Ws2_32.lib;Advapi32.lib;User32.lib;Gdi32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)bbackupquery.exe</OutputFile> + <IgnoreSpecificDefaultLibraries> + </IgnoreSpecificDefaultLibraries> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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/2013/common.vcxproj b/infrastructure/msvc/2013/common.vcxproj new file mode 100644 index 00000000..50c93384 --- /dev/null +++ b/infrastructure/msvc/2013/common.vcxproj @@ -0,0 +1,258 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</IntDir> + <IncludePath Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">$(IncludePath)</IncludePath> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <PreBuildEvent /> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>$(ProjectDir)..\..\..\lib\common;$(ProjectDir)..\..\..\lib\compress;$(ProjectDir)..\..\..\lib\crypto;$(ProjectDir)..\..\..\lib\server;$(ProjectDir)..\..\..\lib\win32;$(ProjectDir)..\..\..\..\openssl\include;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre;$(ProjectDir)..\..\..\qdbm</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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Lib /> + <CustomBuildStep /> + <CustomBuildStep /> + <CustomBuildStep /> + <Lib> + <AdditionalDependencies>$(ProjectDir)..\..\..\..\pcre\build\vc2013\Debug\pcre.lib;$(ProjectDir)..\..\..\..\pcre\build\vc2013\Debug\pcreposix.lib;$(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib;$(ProjectDir)..\..\..\..\zlib\projects\visualc10\LIB Debug\zlibd.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\include;$(ProjectDir)..\..\..\..\zlib\include;$(ProjectDir)..\..\..\..\pcre;$(ProjectDir)..\..\..\qdbm;%(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>true</BufferSecurityCheck> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Lib> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <AdditionalDependencies>$(ProjectDir)..\..\..\..\pcre\build\vc2013\Release\pcre.lib;$(ProjectDir)..\..\..\..\pcre\build\vc2013\Release\pcreposix.lib;$(ProjectDir)..\..\..\..\openssl\lib\libeay32.lib;$(ProjectDir)..\..\..\..\openssl\lib\ssleay32.lib;$(ProjectDir)..\..\..\..\zlib\projects\visualc10\\LIB Release\zlib.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Lib> + <PreBuildEvent> + <Command>perl $(ProjectDir)..\getversion.pl</Command> + <Message>Determining Version Number</Message> + </PreBuildEvent> + </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\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\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/2013/libbackupclient.vcxproj b/infrastructure/msvc/2013/libbackupclient.vcxproj new file mode 100644 index 00000000..e52e3dd5 --- /dev/null +++ b/infrastructure/msvc/2013/libbackupclient.vcxproj @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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\autogen_ClientException.cpp" /> + <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\autogen_ClientException.h" /> + <ClInclude Include="..\..\..\lib\backupclient\BackupClientCryptoKeys.h" /> + <ClInclude Include="..\..\..\lib\backupclient\BackupClientMakeExcludeList.h" /> + <ClInclude Include="..\..\..\lib\backupclient\BackupClientRestore.h" /> + <ClInclude Include="..\..\..\lib\backupclient\BackupDaemonConfigVerify.h" /> + <ClInclude Include="..\..\..\lib\backupclient\BackupDaemonInterface.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </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> + <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;NDEBUG;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </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/2013/libbackupstore.vcxproj b/infrastructure/msvc/2013/libbackupstore.vcxproj new file mode 100644 index 00000000..6bf54572 --- /dev/null +++ b/infrastructure/msvc/2013/libbackupstore.vcxproj @@ -0,0 +1,184 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <OutDir>$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <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)..\..\..\lib\httpserver;$(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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </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> + <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)..\..\..\lib\httpserver;$(SolutionDir)..\..\..\qdbm;$(SolutionDir)..\..\..\..\openssl\include;$(SolutionDir)..\..\..\..\zlib\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>WIN32;NDEBUG;BOX_RELEASE_BUILD;_LIB;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;QDBM_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + </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\BackupAccountControl.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\httpserver\autogen_HTTPException.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\cdecode.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\cencode.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\HTTPQueryDecoder.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\HTTPRequest.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\HTTPResponse.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\HTTPServer.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\S3Client.cpp" /> + <ClCompile Include="..\..\..\lib\httpserver\S3Simulator.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\BackupAccountControl.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\httpserver\autogen_HTTPException.h" /> + <ClInclude Include="..\..\..\lib\httpserver\cdecode.h" /> + <ClInclude Include="..\..\..\lib\httpserver\cencode.h" /> + <ClInclude Include="..\..\..\lib\httpserver\decode.h" /> + <ClInclude Include="..\..\..\lib\httpserver\encode.h" /> + <ClInclude Include="..\..\..\lib\httpserver\HTTPQueryDecoder.h" /> + <ClInclude Include="..\..\..\lib\httpserver\HTTPRequest.h" /> + <ClInclude Include="..\..\..\lib\httpserver\HTTPResponse.h" /> + <ClInclude Include="..\..\..\lib\httpserver\HTTPServer.h" /> + <ClInclude Include="..\..\..\lib\httpserver\S3Client.h" /> + <ClInclude Include="..\..\..\lib\httpserver\S3Simulator.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/2013/qdbm.vcxproj b/infrastructure/msvc/2013/qdbm.vcxproj new file mode 100644 index 00000000..296de088 --- /dev/null +++ b/infrastructure/msvc/2013/qdbm.vcxproj @@ -0,0 +1,90 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>StaticLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <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> + <DisableSpecificWarnings>4996</DisableSpecificWarnings> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <CompileAsManaged>false</CompileAsManaged> + </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/2013/win32test.vcxproj b/infrastructure/msvc/2013/win32test.vcxproj new file mode 100644 index 00000000..8369aabc --- /dev/null +++ b/infrastructure/msvc/2013/win32test.vcxproj @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="12.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> + <PlatformToolset>v120</PlatformToolset> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>Application</ConfigurationType> + <CharacterSet>MultiByte</CharacterSet> + <PlatformToolset>v120</PlatformToolset> + </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'">$(ProjectDir)..\..\..\$(Configuration)\</OutDir> + <IntDir Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">$(ProjectDir)..\..\..\$(Configuration)\$(ProjectName)\</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> + <DisableSpecificWarnings>4521</DisableSpecificWarnings> + </ClCompile> + <Link> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(OutDir)$(TargetName).pdb</ProgramDatabaseFile> + <SubSystem>Console</SubSystem> + <TargetMachine>MachineX86</TargetMachine> + <ShowProgress>NotSet</ShowProgress> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <EnableFiberSafeOptimizations>true</EnableFiberSafeOptimizations> + <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;BOX_RELEASE_BUILD;_CONSOLE;PLATFORM_DISABLE_MEM_LEAK_TESTING;_CRT_SECURE_NO_DEPRECATE;PCRE_STATIC;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <PrecompiledHeader> + </PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + </ClCompile> + <Link> + <AdditionalDependencies>Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <IgnoreSpecificDefaultLibraries> + </IgnoreSpecificDefaultLibraries> + <GenerateDebugInformation>true</GenerateDebugInformation> + <SubSystem>Console</SubSystem> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </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 new file mode 100755 index 00000000..fe958831 --- /dev/null +++ b/infrastructure/msvc/getversion.pl @@ -0,0 +1,33 @@ +#!perl + +$basedir = $0; +$basedir =~ s|/|\\|g; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +$basedir =~ s/\\[^\\]*$//; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; + +require "$basedir\\infrastructure\\BoxPlatform.pm.in"; + +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; +exit 0; diff --git a/infrastructure/msvc/win32.bat b/infrastructure/msvc/win32.bat new file mode 100644 index 00000000..7c25723a --- /dev/null +++ b/infrastructure/msvc/win32.bat @@ -0,0 +1,44 @@ +@echo off + +echo quick and dirty to get up and running by generating the required files +echo using Cygwin and Perl + +cd ..\.. + +copy .\infrastructure\BoxPlatform.pm.in .\infrastructure\BoxPlatform.pm +copy .\lib\common\BoxPortsAndFiles.h.in .\lib\common\BoxPortsAndFiles.h +copy .\lib\common\BoxConfig-MSVC.h .\lib\common\BoxConfig.h + +cd .\bin\bbackupquery\ & perl ./../../bin/bbackupquery/makedocumentation.pl.in +cd ..\..\ + +cd .\lib\backupstore & perl ./../../lib/common/makeexception.pl.in BackupStoreException.txt & perl ./../../lib/server/makeprotocol.pl.in backupprotocol.txt +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 +cd ..\..\ + +cd .\lib\raidfile & perl ./../../lib/common/makeexception.pl.in RaidFileException.txt +cd ..\..\ + +cd .\lib\backupclient & perl ./../../lib/common/makeexception.pl.in ClientException.txt +cd ..\..\ + +cd .\lib\crypto & perl ./../../lib/common/makeexception.pl.in CipherException.txt +cd ..\..\ + +cd .\lib\httpserver & perl ./../../lib/common/makeexception.pl.in HTTPException.txt +cd ..\..\ + +echo server parts - which appears as though some of the clients rely on + +cd .\lib\server & perl ./../../lib/common/makeexception.pl.in ServerException.txt & perl ./../../lib/common/makeexception.pl.in ConnectionException.txt +cd ..\..\ + +perl -pe 's/@PERL@/perl/' ./test/bbackupd/testfiles/bbackupd.conf.in > .\test\bbackupd\testfiles\bbackupd.conf + +echo Generating InstallJammer configuration file +perl infrastructure/msvc/fake-config.sub.pl ./contrib/windows/installer/boxbackup.mpi.in > ./contrib/windows/installer/boxbackup.mpi diff --git a/infrastructure/parcelpath.pl b/infrastructure/parcelpath.pl new file mode 100644 index 00000000..24f951a2 --- /dev/null +++ b/infrastructure/parcelpath.pl @@ -0,0 +1,17 @@ +#!perl + +unless (@ARGV == 2) +{ + die "Usage: $0 <parcel-name> <target-os>\n"; +} + +$basedir = $0; +$basedir =~ s|/.*||; +$basedir .= "/.."; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; +require "infrastructure/BoxPlatform.pm.in"; + +print BoxPlatform::parcel_dir(@ARGV) . "\n"; + +exit 0; diff --git a/infrastructure/printversion.pl b/infrastructure/printversion.pl new file mode 100644 index 00000000..13815284 --- /dev/null +++ b/infrastructure/printversion.pl @@ -0,0 +1,12 @@ +#!perl + +$basedir = $0; +$basedir =~ s|/.*||; +$basedir .= "/.."; +-d $basedir or die "$basedir: $!"; +chdir $basedir or die "$basedir: $!"; +require "infrastructure/BoxPlatform.pm.in"; + +print "$BoxPlatform::product_version\n"; + +exit 0; diff --git a/infrastructure/setupexternal.pl b/infrastructure/setupexternal.pl new file mode 100755 index 00000000..87ec5560 --- /dev/null +++ b/infrastructure/setupexternal.pl @@ -0,0 +1,55 @@ +#!@PERL@ +use strict; + +# This script links in the essential directories and processes various +# files to allow the Box libraries to be used in projects outside the main +# box library tree. + +# directories to link through +my @linkdirs = qw/lib infrastructure/; + +# ---------------------------------------------------- + +my $libdir = $ARGV[0]; +die "Provided library dir $libdir does not exist" unless -d $libdir; + +# Check and remove links from the directory, then add new symlinks +for my $d (@linkdirs) +{ + if(-e $d) + { + die "In project, $d is not a symbolic link" + unless -l $d; + print "Removing existing symlink $d\n"; + unlink $d; + } + my $link_target = "$libdir/$d"; + print "Add symlink $d -> $link_target\n"; + die "Can't create symlink $d" unless + symlink $link_target, $d; +} + +# Copy and create a base modules file which includes all the libraries +print "Create new modules_base.txt file\n"; +open OUT,">modules_base.txt" or die "Can't open modules_base.txt file for writing"; +print OUT <<__E; +# +# Automatically generated file, do not edit +# +# Source: $libdir/modules.txt +# + +__E + +open IN,"$libdir/modules.txt" or die "Can't open $libdir/modules.txt for reading"; + +while(<IN>) +{ + if(m/\A(lib\/.+?)\s/) + { + print OUT + } +} + +close IN; +close OUT; diff --git a/lib/backupclient/BackupClientCryptoKeys.cpp b/lib/backupclient/BackupClientCryptoKeys.cpp new file mode 100644 index 00000000..7a8da7ba --- /dev/null +++ b/lib/backupclient/BackupClientCryptoKeys.cpp @@ -0,0 +1,85 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientCryptoKeys.cpp +// Purpose: function for setting up all the backup client keys +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "BackupClientCryptoKeys.h" +#include "FileStream.h" +#include "BackupStoreFilenameClear.h" +#include "BackupStoreException.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreFile.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientCryptoKeys_Setup(const char *) +// Purpose: Read in the key material file, and set keys to all the backup elements required. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename) +{ + // Read in the key material + unsigned char KeyMaterial[BACKUPCRYPTOKEYS_FILE_SIZE]; + + // Open the file + FileStream file(rKeyMaterialFilename); + + // Read in data + if(!file.ReadFullBuffer(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE, 0)) + { + THROW_EXCEPTION(BackupStoreException, CouldntLoadClientKeyMaterial) + } + + // Setup keys and encoding method for filename encryption + BackupStoreFilenameClear::SetBlowfishKey( + KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_KEY_START, + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH, + KeyMaterial + BACKUPCRYPTOKEYS_FILENAME_IV_START, + BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH); + BackupStoreFilenameClear::SetEncodingMethod( + BackupStoreFilename::Encoding_Blowfish); + + // Setup key for attributes encryption + BackupClientFileAttributes::SetBlowfishKey( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH); + + // Setup secret for attribute hashing + BackupClientFileAttributes::SetAttributeHashSecret( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START, + BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH); + + // Setup keys for file data encryption + BackupStoreFile::SetBlowfishKeys( + KeyMaterial + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START, + BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH, + KeyMaterial + BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START, + BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH); + +#ifndef HAVE_OLD_SSL + // Use AES where available + BackupStoreFile::SetAESKey( + KeyMaterial + BACKUPCRYPTOKEYS_FILE_AES_KEY_START, + BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH); +#endif + + // Wipe the key material from memory + #ifdef _MSC_VER // not defined on MinGW + SecureZeroMemory(KeyMaterial, BACKUPCRYPTOKEYS_FILE_SIZE); + #else + ::memset(KeyMaterial, 0, BACKUPCRYPTOKEYS_FILE_SIZE); + #endif +} + diff --git a/lib/backupclient/BackupClientCryptoKeys.h b/lib/backupclient/BackupClientCryptoKeys.h new file mode 100644 index 00000000..f40e2e03 --- /dev/null +++ b/lib/backupclient/BackupClientCryptoKeys.h @@ -0,0 +1,55 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientCryptoKeys.h +// Purpose: Format of crypto keys file, and function for setting everything up +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTCRYTOKEYS__H +#define BACKUPCLIENTCRYTOKEYS__H + + +// All keys are the maximum size that Blowfish supports. Since only the +// setup time is affected by key length (encryption same speed whatever) +// there is no disadvantage to using long keys as they are never +// transmitted and are static over long periods of time. + + +// All sizes in bytes. Some gaps deliberately left in the used material. + +// How long the key material file is expected to be +#define BACKUPCRYPTOKEYS_FILE_SIZE 1024 + +// key for encrypting filenames (448 bits) +#define BACKUPCRYPTOKEYS_FILENAME_KEY_START 0 +#define BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH 56 +#define BACKUPCRYPTOKEYS_FILENAME_IV_START (0 + BACKUPCRYPTOKEYS_FILENAME_KEY_LENGTH) +#define BACKUPCRYPTOKEYS_FILENAME_IV_LENGTH 8 + +// key for encrypting attributes (448 bits) +#define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START (BACKUPCRYPTOKEYS_FILENAME_KEY_START+64) +#define BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_LENGTH 56 + +// Blowfish key for encrypting file data (448 bits (max blowfish key length)) +#define BACKUPCRYPTOKEYS_FILE_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTES_KEY_START+64) +#define BACKUPCRYPTOKEYS_FILE_KEY_LENGTH 56 + +// key for encrypting file block index entries +#define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START (BACKUPCRYPTOKEYS_FILE_KEY_START+64) +#define BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_LENGTH 56 + +// Secret for hashing attributes +#define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START (BACKUPCRYPTOKEYS_FILE_BLOCK_ENTRY_KEY_START+64) +#define BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_LENGTH 128 + +// AES key for encrypting file data (256 bits (max AES key length)) +#define BACKUPCRYPTOKEYS_FILE_AES_KEY_START (BACKUPCRYPTOKEYS_ATTRIBUTE_HASH_SECRET_START+128) +#define BACKUPCRYPTOKEYS_FILE_AES_KEY_LENGTH 32 + + +void BackupClientCryptoKeys_Setup(const std::string& rKeyMaterialFilename); + +#endif // BACKUPCLIENTCRYTOKEYS__H + diff --git a/lib/backupclient/BackupClientMakeExcludeList.cpp b/lib/backupclient/BackupClientMakeExcludeList.cpp new file mode 100644 index 00000000..0615faaa --- /dev/null +++ b/lib/backupclient/BackupClientMakeExcludeList.cpp @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientMakeExcludeList.cpp +// Purpose: Makes exclude lists from bbbackupd config location entries +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "BackupClientMakeExcludeList.h" +#include "Configuration.h" +#include "ExcludeList.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList(const Configuration &, const char *, const char *) +// Purpose: Given a Configuration object corresponding to a bbackupd Location, and the +// two names of the keys for definite and regex entries, return a ExcludeList. +// Or 0 if it isn't required. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, + const char *AlwaysIncludeDefiniteName, const char *AlwaysIncludeRegexName) +{ + // Check that at least one of the entries exists + if(!rConfig.KeyExists(DefiniteName) && !rConfig.KeyExists(RegexName)) + { + // Neither exists -- return 0 as an Exclude list isn't required. + return 0; + } + + // Create the exclude list + ExcludeList *pexclude = new ExcludeList; + + try + { + // Definite names to add? + if(rConfig.KeyExists(DefiniteName)) + { + pexclude->AddDefiniteEntries(rConfig.GetKeyValue(DefiniteName)); + } + // Regular expressions to add? + if(rConfig.KeyExists(RegexName)) + { + pexclude->AddRegexEntries(rConfig.GetKeyValue(RegexName)); + } + + // Add a "always include" list? + if(AlwaysIncludeDefiniteName != 0 && AlwaysIncludeRegexName != 0) + { + // This will accept NULL as a valid argument, so safe to do this. + pexclude->SetAlwaysIncludeList( + BackupClientMakeExcludeList(rConfig, AlwaysIncludeDefiniteName, AlwaysIncludeRegexName) + ); + } + } + catch(...) + { + // Clean up + delete pexclude; + throw; + } + + return pexclude; +} + + + diff --git a/lib/backupclient/BackupClientMakeExcludeList.h b/lib/backupclient/BackupClientMakeExcludeList.h new file mode 100644 index 00000000..d5dd8af4 --- /dev/null +++ b/lib/backupclient/BackupClientMakeExcludeList.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientMakeExcludeList.h +// Purpose: Makes exclude lists from bbbackupd config location entries +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTMAKEEXCLUDELIST__H +#define BACKUPCLIENTMAKEEXCLUDELIST__H + +class ExcludeList; +class Configuration; + +ExcludeList *BackupClientMakeExcludeList(const Configuration &rConfig, const char *DefiniteName, const char *RegexName, + const char *AlwaysIncludeDefiniteName = 0, const char *AlwaysIncludeRegexName = 0); + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList_Files(const Configuration &) +// Purpose: Create a exclude list from config file entries for files. May return 0. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +inline ExcludeList *BackupClientMakeExcludeList_Files(const Configuration &rConfig) +{ + return BackupClientMakeExcludeList(rConfig, "ExcludeFile", "ExcludeFilesRegex", "AlwaysIncludeFile", "AlwaysIncludeFilesRegex"); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientMakeExcludeList_Dirs(const Configuration &) +// Purpose: Create a exclude list from config file entries for directories. May return 0. +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +inline ExcludeList *BackupClientMakeExcludeList_Dirs(const Configuration &rConfig) +{ + return BackupClientMakeExcludeList(rConfig, "ExcludeDir", "ExcludeDirsRegex", "AlwaysIncludeDir", "AlwaysIncludeDirsRegex"); +} + + +#endif // BACKUPCLIENTMAKEEXCLUDELIST__H + diff --git a/lib/backupclient/BackupClientRestore.cpp b/lib/backupclient/BackupClientRestore.cpp new file mode 100644 index 00000000..d3300604 --- /dev/null +++ b/lib/backupclient/BackupClientRestore.cpp @@ -0,0 +1,920 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientRestore.cpp +// Purpose: +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> +#include <string> +#include <set> +#include <limits.h> +#include <stdio.h> +#include <errno.h> + +#include "BackupClientRestore.h" +#include "autogen_BackupProtocol.h" +#include "CommonException.h" +#include "BackupClientFileAttributes.h" +#include "IOStream.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "CollectInBufferStream.h" +#include "FileStream.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES (128*1024) + +class RestoreResumeInfo +{ +public: + // constructor + RestoreResumeInfo() + : mNextLevelID(0), + mpNextLevel(0) + { + } + + // destructor + ~RestoreResumeInfo() + { + delete mpNextLevel; + mpNextLevel = 0; + } + + // Get a next level object + RestoreResumeInfo &AddLevel(int64_t ID, const std::string &rLocalName) + { + ASSERT(mpNextLevel == 0 && mNextLevelID == 0); + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = ID; + mNextLevelLocalName = rLocalName; + return *mpNextLevel; + } + + // Remove the next level info + void RemoveLevel() + { + ASSERT(mpNextLevel != 0 && mNextLevelID != 0); + delete mpNextLevel; + mpNextLevel = 0; + mNextLevelID = 0; + mNextLevelLocalName.erase(); + } + + void Save(const std::string &rFilename) const + { + // TODO: use proper buffered streams when they're done + // Build info in memory buffer + CollectInBufferStream write; + + // Save this level + SaveLevel(write); + + // Store in file + write.SetForReading(); + FileStream file(rFilename.c_str(), O_WRONLY | O_CREAT); + write.CopyStreamTo(file, IOStream::TimeOutInfinite, 8*1024 /* large buffer */); + } + + void SaveLevel(IOStream &rWrite) const + { + // Write the restored objects + int64_t numObjects = mRestoredObjects.size(); + rWrite.Write(&numObjects, sizeof(numObjects)); + for(std::set<int64_t>::const_iterator i(mRestoredObjects.begin()); i != mRestoredObjects.end(); ++i) + { + int64_t id = *i; + rWrite.Write(&id, sizeof(id)); + } + + // Next level? + if(mpNextLevel != 0) + { + // ID + rWrite.Write(&mNextLevelID, sizeof(mNextLevelID)); + // Name string + int32_t nsize = mNextLevelLocalName.size(); + rWrite.Write(&nsize, sizeof(nsize)); + rWrite.Write(mNextLevelLocalName.c_str(), nsize); + // And then the level itself + mpNextLevel->SaveLevel(rWrite); + } + else + { + // Just write a zero + int64_t zero = 0; + rWrite.Write(&zero, sizeof(zero)); + } + } + + // Not written to be efficient -- shouldn't be called very often. + bool Load(const std::string &rFilename) + { + // Delete and reset if necessary + if(mpNextLevel != 0) + { + RemoveLevel(); + } + + // Open file + FileStream file(rFilename.c_str()); + + // Load this level + return LoadLevel(file); + } + + #define CHECKED_READ(x, s) if(!rRead.ReadFullBuffer(x, s, 0)) {return false;} + bool LoadLevel(IOStream &rRead) + { + // Load the restored objects list + mRestoredObjects.clear(); + int64_t numObjects = 0; + CHECKED_READ(&numObjects, sizeof(numObjects)); + for(int64_t o = 0; o < numObjects; ++o) + { + int64_t id; + CHECKED_READ(&id, sizeof(id)); + mRestoredObjects.insert(id); + } + + // ID of next level? + int64_t nextID = 0; + CHECKED_READ(&nextID, sizeof(nextID)); + if(nextID != 0) + { + // Load the next level! + std::string name; + int32_t nsize = 0; + CHECKED_READ(&nsize, sizeof(nsize)); + char n[PATH_MAX]; + if(nsize > PATH_MAX) return false; + CHECKED_READ(n, nsize); + name.assign(n, nsize); + + // Create a new level + mpNextLevel = new RestoreResumeInfo; + mNextLevelID = nextID; + mNextLevelLocalName = name; + + // And ask it to read itself in + if(!mpNextLevel->LoadLevel(rRead)) + { + return false; + } + } + + return true; + } + + // List of objects at this level which have been done already + std::set<int64_t> mRestoredObjects; + // Next level ID + int64_t mNextLevelID; + // Pointer to next level + RestoreResumeInfo *mpNextLevel; + // Local filename of next level + std::string mNextLevelLocalName; +}; + +// parameters structure +typedef struct +{ + bool PrintDots; + bool RestoreDeleted; + bool ContinueAfterErrors; + bool ContinuedAfterError; + std::string mRestoreResumeInfoFilename; + RestoreResumeInfo mResumeInfo; +} RestoreParams; + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestoreDir(BackupProtocolCallable &, +// int64_t, const char *, bool) +// Purpose: Restore a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +static int BackupClientRestoreDir(BackupProtocolCallable &rConnection, + int64_t DirectoryID, const std::string &rRemoteDirectoryName, + const std::string &rLocalDirectoryName, + RestoreParams &Params, RestoreResumeInfo &rLevel) +{ + // If we're resuming... check that we haven't got a next level to + // look at + if(rLevel.mpNextLevel != 0) + { + // Recurse immediately + std::string localDirname(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + rLevel.mNextLevelLocalName); + BackupClientRestoreDir(rConnection, rLevel.mNextLevelID, + rRemoteDirectoryName + '/' + + rLevel.mNextLevelLocalName, localDirname, + Params, *rLevel.mpNextLevel); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(rLevel.mNextLevelID); + + // Remove the level for the recursed directory + rLevel.RemoveLevel(); + } + + // Create the local directory, if not already done. + // Path and owner set later, just use restrictive owner mode. + + int exists; + + try + { + exists = ObjectExists(rLocalDirectoryName.c_str()); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(std::exception &e) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to check existence for " << + rLocalDirectoryName << ": unknown error"); + return Restore_UnknownError; + } + + switch(exists) + { + case ObjectExists_Dir: + // Do nothing + break; + case ObjectExists_File: + { + // File exists with this name, which is fun. + // Get rid of it. + BOX_WARNING("File present with name '" << + rLocalDirectoryName << "', removing " + "out of the way of restored directory. " + "Use specific restore with ID to " + "restore this object."); + if(::unlink(rLocalDirectoryName.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete " + "file '" << + rLocalDirectoryName << "'"); + return Restore_UnknownError; + } + BOX_TRACE("In restore, directory name " + "collision with file '" << + rLocalDirectoryName << "'"); + } + break; + case ObjectExists_NoObject: + // we'll create it in a second, after checking + // whether the parent directory exists + break; + default: + ASSERT(false); + break; + } + + std::string parentDirectoryName(rLocalDirectoryName); + if(parentDirectoryName[parentDirectoryName.size() - 1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + parentDirectoryName.resize(parentDirectoryName.size() - 1); + } + + size_t lastSlash = parentDirectoryName.rfind(DIRECTORY_SEPARATOR_ASCHAR); + + if(lastSlash == std::string::npos) + { + // might be a forward slash separator, + // especially in the unit tests! + lastSlash = parentDirectoryName.rfind('/'); + } + + if(lastSlash != std::string::npos) + { + // the target directory is a deep path, remove the last + // directory name and check that the resulting parent + // exists, otherwise the restore should fail. + parentDirectoryName.resize(lastSlash); + + #ifdef WIN32 + // if the path is a drive letter, then we need to + // add a a backslash to query the root directory. + if (lastSlash == 2 && parentDirectoryName[1] == ':') + { + parentDirectoryName += '\\'; + } + else if (lastSlash == 0) + { + parentDirectoryName += '\\'; + } + #endif + + int parentExists; + + try + { + parentExists = ObjectExists(parentDirectoryName.c_str()); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(std::exception &e) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to check existence for " << + parentDirectoryName << ": unknown error"); + return Restore_UnknownError; + } + + switch(parentExists) + { + case ObjectExists_Dir: + // this is fine, do nothing + break; + + case ObjectExists_File: + BOX_ERROR("Failed to restore: '" << + parentDirectoryName << "' " + "is a file, but should be a " + "directory."); + return Restore_TargetPathNotFound; + + case ObjectExists_NoObject: + BOX_ERROR("Failed to restore: parent '" << + parentDirectoryName << "' of target " + "directory does not exist."); + return Restore_TargetPathNotFound; + + default: + BOX_ERROR("Failed to restore: unknown " + "result from ObjectExists('" << + parentDirectoryName << "')"); + return Restore_UnknownError; + } + } + + if((exists == ObjectExists_NoObject || + exists == ObjectExists_File) && + ::mkdir(rLocalDirectoryName.c_str(), S_IRWXU) != 0) + { + BOX_LOG_SYS_ERROR("Failed to create directory '" << + rLocalDirectoryName << "'"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Save the restore info, in case it's needed later + try + { + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Fetch the directory listing from the server -- getting a + // list of files which is appropriate to the restore type + rConnection.QueryListDirectory( + DirectoryID, + Params.RestoreDeleted?(BackupProtocolListDirectory::Flags_Deleted):(BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING), + BackupProtocolListDirectory::Flags_OldVersion | (Params.RestoreDeleted?(0):(BackupProtocolListDirectory::Flags_Deleted)), + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(rConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, rConnection.GetTimeout()); + + // Apply attributes to the directory + const StreamableMemBlock &dirAttrBlock(dir.GetAttributes()); + BackupClientFileAttributes dirAttr(dirAttrBlock); + + try + { + dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), true); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': " << e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + int64_t bytesWrittenSinceLastRestoreInfoSave = 0; + + // Process files + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_File)) + != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) + == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localFilename(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + nm.GetClearFilename()); + + // Unlink anything which already exists: + // For resuming restores, we can't overwrite + // files already there. + if(ObjectExists(localFilename) + != ObjectExists_NoObject && + ::unlink(localFilename.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete " + "file '" << localFilename << + "'"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + BOX_TRACE("Restoring file: " << + rRemoteDirectoryName + '/' + + nm.GetClearFilename() << " (" << + en->GetSizeInBlocks() << " blocks)"); + + // Request it from the store + rConnection.QueryGetFile(DirectoryID, + en->GetObjectID()); + + // Stream containing encoded file + std::auto_ptr<IOStream> objectStream( + rConnection.ReceiveStream()); + + // Decode the file -- need to do different + // things depending on whether the directory + // entry has additional attributes + try + { + if(en->HasAttributes()) + { + // Use these attributes + const StreamableMemBlock &storeAttr(en->GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout(), &attr); + } + else + { + // Use attributes stored in file + BackupStoreFile::DecodeFile(*objectStream, localFilename.c_str(), rConnection.GetTimeout()); + } + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore file '" << + localFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + // ensure that protocol remains usable + objectStream->Flush(); + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore file '" << + localFilename << + "': unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + // Progress display? + if(Params.PrintDots) + { + printf("."); + fflush(stdout); + } + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + + // Save restore info? + int64_t fileSize; + bool exists = false; + + try + { + exists = FileExists( + localFilename.c_str(), + &fileSize, + true /* treat links as not + existing */); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to determine " + "whether file exists: '" << + localFilename << "': " << + e.what()); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to determine " + "whether file exists: '" << + localFilename << "': " + "unknown error"); + + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + if(exists) + { + // File exists... + bytesWrittenSinceLastRestoreInfoSave + += fileSize; + + if(bytesWrittenSinceLastRestoreInfoSave > MAX_BYTES_WRITTEN_BETWEEN_RESTORE_INFO_SAVES) + { + // Save the restore info, in + // case it's needed later + try + { + Params.mResumeInfo.Save(Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': " << e.what()); + return Restore_UnknownError; + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + return Restore_UnknownError; + } + + bytesWrittenSinceLastRestoreInfoSave = 0; + } + } + } + } + } + + // Make sure the restore info has been saved + if(bytesWrittenSinceLastRestoreInfoSave != 0) + { + // Save the restore info, in case it's needed later + try + { + Params.mResumeInfo.Save( + Params.mRestoreResumeInfoFilename); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << "': " << + e.what()); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to save resume info file '" << + Params.mRestoreResumeInfoFilename << + "': unknown error"); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + bytesWrittenSinceLastRestoreInfoSave = 0; + } + + + // Recurse to directories + { + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next(BackupStoreDirectory::Entry::Flags_Dir)) + != 0) + { + // Check ID hasn't already been done + if(rLevel.mRestoredObjects.find(en->GetObjectID()) + == rLevel.mRestoredObjects.end()) + { + // Local name + BackupStoreFilenameClear nm(en->GetName()); + std::string localDirname(rLocalDirectoryName + + DIRECTORY_SEPARATOR_ASCHAR + + nm.GetClearFilename()); + + // Add the level for the next entry + RestoreResumeInfo &rnextLevel( + rLevel.AddLevel(en->GetObjectID(), + nm.GetClearFilename())); + + // Recurse + + BOX_TRACE("Entering directory: " << + rRemoteDirectoryName + '/' + + nm.GetClearFilename()); + + int result = BackupClientRestoreDir( + rConnection, en->GetObjectID(), + rRemoteDirectoryName + '/' + + nm.GetClearFilename(), localDirname, + Params, rnextLevel); + + if (result != Restore_Complete) + { + return result; + } + + // Remove the level for the above call + rLevel.RemoveLevel(); + + // Add it to the list of done itmes + rLevel.mRestoredObjects.insert(en->GetObjectID()); + } + } + } + + // now remove the user writable flag, if we added it earlier + try + { + dirAttr.WriteAttributes(rLocalDirectoryName.c_str(), false); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': " << e.what()); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + catch(...) + { + BOX_ERROR("Failed to restore attributes for '" << + rLocalDirectoryName << "': unknown error"); + if (Params.ContinueAfterErrors) + { + Params.ContinuedAfterError = true; + } + else + { + return Restore_UnknownError; + } + } + + return Restore_Complete; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientRestore(BackupProtocolCallable &, int64_t, +// const char *, bool, bool, bool, bool, bool) +// Purpose: Restore a directory on the server to a local +// directory on the disc. The local directory must not +// already exist. +// +// If a restore is aborted for any reason, then it may +// be resumed if Resume == true. If Resume == false +// and resumption is possible, then +// Restore_ResumePossible is returned. +// +// Set RestoreDeleted to restore a deleted directory. +// This may not give the directory structure when it +// was deleted, because files may have been deleted +// within it before it was deleted. +// +// Returns Restore_TargetExists if the target +// directory exists, but there is no restore possible. +// (Won't attempt to overwrite things.) +// +// Returns Restore_Complete on success. (Exceptions +// on error, unless ContinueAfterError is true and +// the error is recoverable, in which case it returns +// Restore_CompleteWithErrors) +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +int BackupClientRestore(BackupProtocolCallable &rConnection, + int64_t DirectoryID, const std::string& RemoteDirectoryName, + const std::string& LocalDirectoryName, bool PrintDots, bool RestoreDeleted, + bool UndeleteAfterRestoreDeleted, bool Resume, + bool ContinueAfterErrors) +{ + // Parameter block + RestoreParams params; + params.PrintDots = PrintDots; + params.RestoreDeleted = RestoreDeleted; + params.ContinueAfterErrors = ContinueAfterErrors; + params.ContinuedAfterError = false; + params.mRestoreResumeInfoFilename = LocalDirectoryName; + params.mRestoreResumeInfoFilename += ".boxbackupresume"; + + // Target exists? + int targetExistance = ObjectExists(LocalDirectoryName); + + // Does any resumption information exist? + bool doingResume = false; + if(FileExists(params.mRestoreResumeInfoFilename.c_str()) && + targetExistance == ObjectExists_Dir) + { + if(!Resume) + { + // Caller didn't specify that resume should be done, + // so refuse to do it but say why. + return Restore_ResumePossible; + } + + // Attempt to load the resume info file + if(!params.mResumeInfo.Load(params.mRestoreResumeInfoFilename)) + { + // failed -- bad file, so things have gone a bit wrong + return Restore_TargetExists; + } + + // Flag as doing resume so next check isn't actually performed + doingResume = true; + } + + // Does the directory already exist? + if(targetExistance != ObjectExists_NoObject && !doingResume) + { + // Don't do anything in this case! + return Restore_TargetExists; + } + + // Restore the directory + int result = BackupClientRestoreDir(rConnection, DirectoryID, + RemoteDirectoryName, LocalDirectoryName, params, + params.mResumeInfo); + if (result != Restore_Complete) + { + return result; + } + + // Undelete the directory on the server? + if(RestoreDeleted && UndeleteAfterRestoreDeleted) + { + // Send the command + rConnection.QueryUndeleteDirectory(DirectoryID); + } + + // Finish progress display? + if(PrintDots) + { + printf("\n"); + fflush(stdout); + } + + // Delete the resume information file + ::unlink(params.mRestoreResumeInfoFilename.c_str()); + + return params.ContinuedAfterError ? Restore_CompleteWithErrors + : Restore_Complete; +} + diff --git a/lib/backupclient/BackupClientRestore.h b/lib/backupclient/BackupClientRestore.h new file mode 100644 index 00000000..cdbedea7 --- /dev/null +++ b/lib/backupclient/BackupClientRestore.h @@ -0,0 +1,36 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientRestore.h +// Purpose: Functions to restore files from a backup store +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTRESTORE_H +#define BACKUPCLIENTRESTORE_H + +class BackupProtocolCallable; + +enum +{ + Restore_Complete = 0, + Restore_ResumePossible, + Restore_TargetExists, + Restore_TargetPathNotFound, + Restore_UnknownError, + Restore_CompleteWithErrors, +}; + +int BackupClientRestore(BackupProtocolCallable &rConnection, + int64_t DirectoryID, + const std::string& RemoteDirectoryName, + const std::string& LocalDirectoryName, + bool PrintDots, + bool RestoreDeleted, + bool UndeleteAfterRestoreDeleted, + bool Resume, + bool ContinueAfterErrors); + +#endif // BACKUPCLIENTRESTORE_H + diff --git a/lib/backupclient/BackupDaemonConfigVerify.cpp b/lib/backupclient/BackupDaemonConfigVerify.cpp new file mode 100644 index 00000000..865ee413 --- /dev/null +++ b/lib/backupclient/BackupDaemonConfigVerify.cpp @@ -0,0 +1,161 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonConfigVerify.cpp +// Purpose: Configuration file definition for bbackupd +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupDaemonConfigVerify.h" +#include "Daemon.h" +#include "BoxPortsAndFiles.h" +#include "BackupConstants.h" + +#include "MemLeakFindOn.h" + + +static const ConfigurationVerifyKey backuplocationkeys[] = +{ + ConfigurationVerifyKey("ExcludeFile", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeFilesRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeDir", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("ExcludeDirsRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeFile", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeFilesRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeDir", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("AlwaysIncludeDirsRegex", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("Path", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +static const ConfigurationVerify backuplocations[] = +{ + { + "*", + 0, + backuplocationkeys, + ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyserverkeys[] = +{ + DAEMON_VERIFY_SERVER_KEYS +}; + +static const ConfigurationVerifyKey verifys3keys[] = +{ + // These values are only required for Amazon S3-compatible stores + ConfigurationVerifyKey("HostName", ConfigTest_Exists), + ConfigurationVerifyKey("Port", ConfigTest_Exists | ConfigTest_IsInt, 80), + ConfigurationVerifyKey("BasePath", ConfigTest_Exists), + ConfigurationVerifyKey("AccessKey", ConfigTest_Exists), + ConfigurationVerifyKey("SecretKey", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +static const ConfigurationVerify verifyserver[] = +{ + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists, + 0 + }, + { + "S3Store", + 0, + verifys3keys, + 0, + 0 + }, + { + "BackupLocations", + backuplocations, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyrootkeys[] = +{ + ConfigurationVerifyKey("UpdateStoreInterval", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("BackupErrorDelay", + ConfigTest_IsInt, BACKUP_ERROR_RETRY_SECONDS), + ConfigurationVerifyKey("MinimumFileAge", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("MaxUploadWait", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("MaxFileTimeInFuture", ConfigTest_IsInt, 172800), + // file is uploaded if the file is this much in the future + // (2 days default) + ConfigurationVerifyKey("AutomaticBackup", ConfigTest_IsBool, true), + + ConfigurationVerifyKey("SyncAllowScript", 0), + // script that returns "now" if backup is allowed now, or a number + // of seconds to wait before trying again if not + + ConfigurationVerifyKey("MaximumDiffingTime", ConfigTest_IsInt), + ConfigurationVerifyKey("DeleteRedundantLocationsAfter", + ConfigTest_IsInt, 172800), + + ConfigurationVerifyKey("FileTrackingSizeThreshold", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("DiffingUploadSizeThreshold", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), + // extended log to syslog + ConfigurationVerifyKey("ExtendedLogFile", 0), + // extended log to a file + ConfigurationVerifyKey("LogAllFileAccess", ConfigTest_IsBool, false), + // enable logging reasons why each file is backed up or not + ConfigurationVerifyKey("LogFile", 0), + // enable logging to a file + ConfigurationVerifyKey("LogFileLevel", 0), + // set the level of verbosity of file logging + ConfigurationVerifyKey("LogFileOverwrite", ConfigTest_IsBool, false), + // overwrite the log file on each backup + ConfigurationVerifyKey("CommandSocket", 0), + // not compulsory to have this + ConfigurationVerifyKey("KeepAliveTime", ConfigTest_IsInt), + ConfigurationVerifyKey("StoreObjectInfoFile", 0), + // optional + + ConfigurationVerifyKey("NotifyScript", 0), + // optional script to run when backup needs attention, eg store full + + ConfigurationVerifyKey("NotifyAlways", ConfigTest_IsBool, false), + // option to disable the suppression of duplicate notifications + + 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 + + ConfigurationVerifyKey("KeysFile", ConfigTest_Exists), + ConfigurationVerifyKey("DataDirectory", ConfigTest_Exists), + + // These values are only required for bbstored stores: + ConfigurationVerifyKey("StoreHostname", 0), + ConfigurationVerifyKey("StorePort", ConfigTest_IsInt, + BOX_PORT_BBSTORED), + ConfigurationVerifyKey("AccountNumber", + ConfigTest_IsUint32), + ConfigurationVerifyKey("CertificateFile", 0), + ConfigurationVerifyKey("PrivateKeyFile", 0), + ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_LastEntry), +}; + +const ConfigurationVerify BackupDaemonConfigVerify = +{ + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; diff --git a/lib/backupclient/BackupDaemonConfigVerify.h b/lib/backupclient/BackupDaemonConfigVerify.h new file mode 100644 index 00000000..fefa703c --- /dev/null +++ b/lib/backupclient/BackupDaemonConfigVerify.h @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonConfigVerify.h +// Purpose: Configuration file definition for bbackupd +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMONCONFIGVERIFY__H +#define BACKUPDAEMONCONFIGVERIFY__H + +#include "Configuration.h" + +extern const ConfigurationVerify BackupDaemonConfigVerify; + +#endif // BACKUPDAEMONCONFIGVERIFY__H + diff --git a/lib/backupclient/BackupStoreObjectDump.cpp b/lib/backupclient/BackupStoreObjectDump.cpp new file mode 100644 index 00000000..654317c1 --- /dev/null +++ b/lib/backupclient/BackupStoreObjectDump.cpp @@ -0,0 +1,227 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreObjectDump.cpp +// Purpose: Implementations of dumping objects to stdout/TRACE +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <stdarg.h> +#include <map> + +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileWire.h" +#include "autogen_BackupStoreException.h" +#include "BackupStoreFilename.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreObjectMagic.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void OutputLine(FILE *, bool, const char *, ...) +// Purpose: Output a line for the object dumping, to file and/or trace... +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +static void OutputLine(FILE *file, bool ToTrace, const char *format, ...) +{ + char text[512]; + int r = 0; + va_list ap; + va_start(ap, format); + r = vsnprintf(text, sizeof(text), format, ap); + va_end(ap); + + if(file != 0) + { + ::fprintf(file, "%s", text); + } + if(ToTrace) + { + BOX_TRACE(text); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) +// Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) +// Dump the contents to a file, or trace. +// Created: 3/5/04 +// +// -------------------------------------------------------------------------- +void BackupStoreDirectory::Dump(void *clibFileHandle, bool ToTrace) +{ + FILE *file = (FILE*)clibFileHandle; + + OutputLine(file, ToTrace, "Directory object.\nObject ID: %llx\nContainer ID: %llx\nNumber entries: %d\n"\ + "Attributes mod time: %llx\nAttributes size: %d\n", mObjectID, mContainerID, mEntries.size(), + mAttributesModTime, mAttributes.GetSize()); + + // So repeated filenames can be illustrated, even though they can't be decoded + std::map<std::string, int> nameNum; + int nameNumI = 0; + + // Dump items + OutputLine(file, ToTrace, "Items:\nID Size AttrHash AtSz NSz NIdx Flags\n"); + for(std::vector<Entry*>::const_iterator i(mEntries.begin()); i != mEntries.end(); ++i) + { + // Choose file name index number for this file + std::map<std::string, int>::iterator nn(nameNum.find((*i)->GetName().GetEncodedFilename())); + int ni = nameNumI; + if(nn != nameNum.end()) + { + ni = nn->second; + } + else + { + nameNum[(*i)->GetName().GetEncodedFilename()] = nameNumI; + ++nameNumI; + } + + // Do dependencies + char depends[128]; + depends[0] = '\0'; + int depends_l = 0; + if((*i)->GetDependsNewer() != 0) + { +#ifdef _MSC_VER + depends_l += ::sprintf(depends + depends_l, " depNew(%I64x)", (*i)->GetDependsNewer()); +#else + depends_l += ::sprintf(depends + depends_l, " depNew(%llx)", (long long)((*i)->GetDependsNewer())); +#endif + } + if((*i)->GetDependsOlder() != 0) + { +#ifdef _MSC_VER + depends_l += ::sprintf(depends + depends_l, " depOld(%I64x)", (*i)->GetDependsOlder()); +#else + depends_l += ::sprintf(depends + depends_l, " depOld(%llx)", (long long)((*i)->GetDependsOlder())); +#endif + } + + // Output item + int16_t f = (*i)->GetFlags(); +#ifdef WIN32 + OutputLine(file, ToTrace, + "%06I64x %4I64d %016I64x %4d %3d %4d%s%s%s%s%s%s\n", +#else + OutputLine(file, ToTrace, + "%06llx %4lld %016llx %4d %3d %4d%s%s%s%s%s%s\n", +#endif + (*i)->GetObjectID(), + (*i)->GetSizeInBlocks(), + (*i)->GetAttributesHash(), + (*i)->GetAttributes().GetSize(), + (*i)->GetName().GetEncodedFilename().size(), + ni, + ((f & BackupStoreDirectory::Entry::Flags_File)?" file":""), + ((f & BackupStoreDirectory::Entry::Flags_Dir)?" dir":""), + ((f & BackupStoreDirectory::Entry::Flags_Deleted)?" del":""), + ((f & BackupStoreDirectory::Entry::Flags_OldVersion)?" old":""), + ((f & BackupStoreDirectory::Entry::Flags_RemoveASAP)?" removeASAP":""), + depends); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreFile::DumpFile(void *, bool, IOStream &) +// Purpose: (first arg is FILE *, but avoid including stdio.h everywhere) +// Dump the contents to a file, or trace. +// Created: 4/5/04 +// +// -------------------------------------------------------------------------- +void BackupStoreFile::DumpFile(void *clibFileHandle, bool ToTrace, IOStream &rFile) +{ + FILE *file = (FILE*)clibFileHandle; + + // Read header + file_StreamFormat hdr; + if(!rFile.ReadFullBuffer(&hdr, sizeof(hdr), + 0 /* not interested in bytes read if this fails */, IOStream::TimeOutInfinite)) + { + // Couldn't read header + THROW_EXCEPTION(BackupStoreException, WhenDecodingExpectedToReadButCouldnt) + } + + // Check and output header info + if(hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V1) + && hdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_MAGIC_VALUE_V0)) + { + OutputLine(file, ToTrace, "File header doesn't have the correct magic, aborting dump\n"); + return; + } + + OutputLine(file, ToTrace, "File object.\nContainer ID: %llx\nModification time: %llx\n"\ + "Max block clear size: %d\nOptions: %08x\nNum blocks: %d\n", box_ntoh64(hdr.mContainerID), + box_ntoh64(hdr.mModificationTime), ntohl(hdr.mMaxBlockClearSize), ntohl(hdr.mOptions), + box_ntoh64(hdr.mNumBlocks)); + + // Read the next two objects + BackupStoreFilename fn; + fn.ReadFromStream(rFile, IOStream::TimeOutInfinite); + OutputLine(file, ToTrace, "Filename size: %d\n", + fn.GetEncodedFilename().size()); + + BackupClientFileAttributes attr; + attr.ReadFromStream(rFile, IOStream::TimeOutInfinite); + OutputLine(file, ToTrace, "Attributes size: %d\n", attr.GetSize()); + + // Dump the blocks + rFile.Seek(0, IOStream::SeekType_Absolute); + BackupStoreFile::MoveStreamPositionToBlockIndex(rFile); + + // Read in header + file_BlockIndexHeader bhdr; + rFile.ReadFullBuffer(&bhdr, sizeof(bhdr), 0); + if(bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) + && bhdr.mMagicValue != (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0)) + { + OutputLine(file, ToTrace, "WARNING: Block header doesn't have the correct magic\n"); + } + // number of blocks + int64_t nblocks = box_ntoh64(bhdr.mNumBlocks); + OutputLine(file, ToTrace, "Other file ID (for block refs): %llx\nNum blocks (in blk hdr): %lld\n", + box_ntoh64(bhdr.mOtherFileID), nblocks); + + // Dump info about each block + OutputLine(file, ToTrace, "======== ===== ==========\n Index Where EncSz/Idx\n"); + int64_t nnew = 0, nold = 0; + for(int64_t b = 0; b < nblocks; ++b) + { + file_BlockIndexEntry en; + if(!rFile.ReadFullBuffer(&en, sizeof(en), 0)) + { + OutputLine(file, ToTrace, "Didn't manage to read block %lld from file\n", b); + continue; + } + int64_t s = box_ntoh64(en.mEncodedSize); + if(s > 0) + { + nnew++; + BOX_TRACE(std::setw(8) << b << " this s=" << + std::setw(8) << s); + } + else + { + nold++; + BOX_TRACE(std::setw(8) << b << " other i=" << + std::setw(8) << 0 - s); + } + } + BOX_TRACE("======== ===== =========="); +} + diff --git a/lib/backupclient/ClientException.txt b/lib/backupclient/ClientException.txt new file mode 100644 index 00000000..04f88620 --- /dev/null +++ b/lib/backupclient/ClientException.txt @@ -0,0 +1,11 @@ + +# NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. + + +EXCEPTION Client 13 + +Internal 0 +AssertFailed 1 +ClockWentBackwards 2 Invalid (negative) sync period: perhaps your clock is going backwards? +FailedToDeleteStoreObjectInfoFile 3 Failed to delete the StoreObjectInfoFile, backup cannot continue safely. +CorruptStoreObjectInfoFile 4 The store object info file contained an invalid value and is probably corrupt. Try deleting it. diff --git a/lib/backupclient/Makefile.extra b/lib/backupclient/Makefile.extra new file mode 100644 index 00000000..25ceb1e7 --- /dev/null +++ b/lib/backupclient/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_ClientException.h autogen_ClientException.cpp: $(MAKEEXCEPTION) ClientException.txt + $(_PERL) $(MAKEEXCEPTION) ClientException.txt + diff --git a/lib/backupstore/BackgroundTask.h b/lib/backupstore/BackgroundTask.h new file mode 100644 index 00000000..bae9162f --- /dev/null +++ b/lib/backupstore/BackgroundTask.h @@ -0,0 +1,39 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackgroundTask.h +// Purpose: Declares the BackgroundTask interface. +// Created: 2014/04/07 +// +// -------------------------------------------------------------------------- + +#ifndef BACKGROUNDTASK__H +#define BACKGROUNDTASK__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackgroundTask +// Purpose: Provides a RunBackgroundTask() method which allows +// background tasks such as polling the command socket +// to happen while a file is being uploaded. If it +// returns false, the current task should be aborted. +// Created: 2014/04/07 +// +// -------------------------------------------------------------------------- +class BackgroundTask +{ + public: + enum State { + Unknown = 0, + Scanning_Dirs, + Searching_Blocks, + Uploading_Full, + Uploading_Patch, + }; + virtual ~BackgroundTask() { } + virtual bool RunBackgroundTask(State state, uint64_t progress, + uint64_t maximum) = 0; +}; + +#endif // BACKGROUNDTASK__H diff --git a/lib/backupstore/BackupAccountControl.cpp b/lib/backupstore/BackupAccountControl.cpp new file mode 100644 index 00000000..331ef841 --- /dev/null +++ b/lib/backupstore/BackupAccountControl.cpp @@ -0,0 +1,267 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupAccountControl.cpp +// Purpose: Client-side account management for Amazon S3 stores +// Created: 2015/06/27 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <climits> +#include <iostream> + +#include "autogen_CommonException.h" +#include "autogen_BackupStoreException.h" +#include "BackupAccountControl.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreInfo.h" +#include "Configuration.h" +#include "HTTPResponse.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +void BackupAccountControl::CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit) +{ + if(SoftLimit > HardLimit) + { + BOX_FATAL("Soft limit must be less than the hard limit."); + exit(1); + } + if(SoftLimit > ((HardLimit * MAX_SOFT_LIMIT_SIZE) / 100)) + { + BOX_WARNING("We recommend setting the soft limit below " << + MAX_SOFT_LIMIT_SIZE << "% of the hard limit, or " << + HumanReadableSize((HardLimit * MAX_SOFT_LIMIT_SIZE) + / 100) << " in this case."); + } +} + +int64_t BackupAccountControl::SizeStringToBlocks(const char *string, int blockSize) +{ + // Get number + char *endptr = (char*)string; + int64_t number = strtol(string, &endptr, 0); + if(endptr == string || number == LONG_MIN || number == LONG_MAX) + { + BOX_FATAL("'" << string << "' is not a valid number."); + exit(1); + } + + // Check units + switch(*endptr) + { + case 'M': + case 'm': + // Units: Mb + return (number * 1024*1024) / blockSize; + break; + + case 'G': + case 'g': + // Units: Gb + return (number * 1024*1024*1024) / blockSize; + break; + + case 'B': + case 'b': + // Units: Blocks + // Easy! Just return the number specified. + return number; + break; + + default: + BOX_FATAL(string << " has an invalid units specifier " + "(use B for blocks, M for MB, G for GB, eg 2GB)"); + exit(1); + break; + } +} + +std::string BackupAccountControl::BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int BlockSize) +{ + return FormatUsageBar(Blocks, Blocks * BlockSize, MaxBlocks * BlockSize, + mMachineReadableOutput); +} + +int BackupAccountControl::PrintAccountInfo(const BackupStoreInfo& info, + int BlockSize) +{ + // Then print out lots of info + std::cout << FormatUsageLineStart("Account ID", mMachineReadableOutput) << + BOX_FORMAT_ACCOUNT(info.GetAccountID()) << std::endl; + std::cout << FormatUsageLineStart("Account Name", mMachineReadableOutput) << + info.GetAccountName() << std::endl; + std::cout << FormatUsageLineStart("Last object ID", mMachineReadableOutput) << + BOX_FORMAT_OBJECTID(info.GetLastObjectIDUsed()) << std::endl; + std::cout << FormatUsageLineStart("Used", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksUsed(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Current files", + mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksInCurrentFiles(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Old files", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksInOldFiles(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Deleted files", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksInDeletedFiles(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Directories", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksInDirectories(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Soft limit", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksSoftLimit(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Hard limit", mMachineReadableOutput) << + BlockSizeToString(info.GetBlocksHardLimit(), + info.GetBlocksHardLimit(), BlockSize) << std::endl; + std::cout << FormatUsageLineStart("Client store marker", mMachineReadableOutput) << + info.GetClientStoreMarker() << std::endl; + std::cout << FormatUsageLineStart("Current Files", mMachineReadableOutput) << + info.GetNumCurrentFiles() << std::endl; + std::cout << FormatUsageLineStart("Old Files", mMachineReadableOutput) << + info.GetNumOldFiles() << std::endl; + std::cout << FormatUsageLineStart("Deleted Files", mMachineReadableOutput) << + info.GetNumDeletedFiles() << std::endl; + std::cout << FormatUsageLineStart("Directories", mMachineReadableOutput) << + info.GetNumDirectories() << std::endl; + std::cout << FormatUsageLineStart("Enabled", mMachineReadableOutput) << + (info.IsAccountEnabled() ? "yes" : "no") << std::endl; + + return 0; +} + +S3BackupAccountControl::S3BackupAccountControl(const Configuration& config, + bool machineReadableOutput) +: BackupAccountControl(config, machineReadableOutput) +{ + if(!mConfig.SubConfigurationExists("S3Store")) + { + THROW_EXCEPTION_MESSAGE(CommonException, + InvalidConfiguration, + "The S3Store configuration subsection is required " + "when S3Store mode is enabled"); + } + const Configuration s3config = mConfig.GetSubConfiguration("S3Store"); + + mBasePath = s3config.GetKeyValue("BasePath"); + if(mBasePath.size() == 0) + { + mBasePath = "/"; + } + else + { + if(mBasePath[0] != '/' || mBasePath[mBasePath.size() - 1] != '/') + { + THROW_EXCEPTION_MESSAGE(CommonException, + InvalidConfiguration, + "If S3Store.BasePath is not empty then it must start and " + "end with a slash, e.g. '/subdir/', but it currently does not."); + } + } + + mapS3Client.reset(new S3Client( + s3config.GetKeyValue("HostName"), + s3config.GetKeyValueInt("Port"), + s3config.GetKeyValue("AccessKey"), + s3config.GetKeyValue("SecretKey"))); + + mapFileSystem.reset(new S3BackupFileSystem(mConfig, mBasePath, *mapS3Client)); +} + +std::string S3BackupAccountControl::GetFullURL(const std::string ObjectPath) const +{ + const Configuration s3config = mConfig.GetSubConfiguration("S3Store"); + return std::string("http://") + s3config.GetKeyValue("HostName") + ":" + + s3config.GetKeyValue("Port") + GetFullPath(ObjectPath); +} + +int S3BackupAccountControl::CreateAccount(const std::string& name, int32_t SoftLimit, + int32_t HardLimit) +{ + // Try getting the info file. If we get a 200 response then it already + // exists, and we should bail out. If we get a 404 then it's safe to + // continue. Otherwise something else is wrong and we should bail out. + std::string info_url = GetFullURL(S3_INFO_FILE_NAME); + + HTTPResponse response = GetObject(S3_INFO_FILE_NAME); + if(response.GetResponseCode() == HTTPResponse::Code_OK) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, AccountAlreadyExists, + "The BackupStoreInfo file already exists at this URL: " << + info_url); + } + + if(response.GetResponseCode() != HTTPResponse::Code_NotFound) + { + mapS3Client->CheckResponse(response, std::string("Failed to check for an " + "existing BackupStoreInfo file at this URL: ") + info_url); + } + + BackupStoreInfo info(0, // fake AccountID for S3 stores + info_url, // FileName, + SoftLimit, HardLimit); + info.SetAccountName(name); + + // And an empty directory + BackupStoreDirectory rootDir(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); + int64_t rootDirSize = mapFileSystem->PutDirectory(rootDir); + + // Update the store info to reflect the size of the root directory + info.ChangeBlocksUsed(rootDirSize); + info.ChangeBlocksInDirectories(rootDirSize); + info.AdjustNumDirectories(1); + int64_t id = info.AllocateObjectID(); + ASSERT(id == BACKUPSTORE_ROOT_DIRECTORY_ID); + + CollectInBufferStream out; + info.Save(out); + out.SetForReading(); + + response = PutObject(S3_INFO_FILE_NAME, out); + mapS3Client->CheckResponse(response, std::string("Failed to upload the new BackupStoreInfo " + "file to this URL: ") + info_url); + + // Now get the file again, to check that it really worked. + response = GetObject(S3_INFO_FILE_NAME); + mapS3Client->CheckResponse(response, std::string("Failed to download the new BackupStoreInfo " + "file that we just created: ") + info_url); + + return 0; +} + +std::string S3BackupFileSystem::GetDirectoryURI(int64_t ObjectID) +{ + std::ostringstream out; + out << mBasePath << "dirs/" << BOX_FORMAT_OBJECTID(ObjectID) << ".dir"; + return out.str(); +} + +std::auto_ptr<HTTPResponse> S3BackupFileSystem::GetDirectory(BackupStoreDirectory& rDir) +{ + std::string uri = GetDirectoryURI(rDir.GetObjectID()); + HTTPResponse response = mrClient.GetObject(uri); + mrClient.CheckResponse(response, + std::string("Failed to download directory: ") + uri); + return std::auto_ptr<HTTPResponse>(new HTTPResponse(response)); +} + +int S3BackupFileSystem::PutDirectory(BackupStoreDirectory& rDir) +{ + CollectInBufferStream out; + rDir.WriteToStream(out); + out.SetForReading(); + + std::string uri = GetDirectoryURI(rDir.GetObjectID()); + HTTPResponse response = mrClient.PutObject(uri, out); + mrClient.CheckResponse(response, + std::string("Failed to upload directory: ") + uri); + + int blocks = (out.GetSize() + S3_NOTIONAL_BLOCK_SIZE - 1) / S3_NOTIONAL_BLOCK_SIZE; + return blocks; +} + diff --git a/lib/backupstore/BackupAccountControl.h b/lib/backupstore/BackupAccountControl.h new file mode 100644 index 00000000..bc041794 --- /dev/null +++ b/lib/backupstore/BackupAccountControl.h @@ -0,0 +1,94 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupAccountControl.h +// Purpose: Client-side account management for Amazon S3 stores +// Created: 2015/06/27 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPACCOUNTCONTROL__H +#define BACKUPACCOUNTCONTROL__H + +#include <string> + +#include "BackupStoreAccountDatabase.h" +#include "HTTPResponse.h" +#include "NamedLock.h" +#include "S3Client.h" + +class BackupStoreDirectory; +class BackupStoreInfo; +class Configuration; + +class BackupAccountControl +{ +protected: + const Configuration& mConfig; + bool mMachineReadableOutput; + +public: + BackupAccountControl(const Configuration& config, + bool machineReadableOutput = false) + : mConfig(config), + mMachineReadableOutput(machineReadableOutput) + { } + void CheckSoftHardLimits(int64_t SoftLimit, int64_t HardLimit); + int64_t SizeStringToBlocks(const char *string, int BlockSize); + std::string BlockSizeToString(int64_t Blocks, int64_t MaxBlocks, int BlockSize); + int PrintAccountInfo(const BackupStoreInfo& info, int BlockSize); +}; + +class S3BackupFileSystem +{ +private: + const Configuration& mConfig; + std::string mBasePath; + S3Client& mrClient; +public: + S3BackupFileSystem(const Configuration& config, const std::string& BasePath, + S3Client& rClient) + : mConfig(config), + mBasePath(BasePath), + mrClient(rClient) + { } + std::string GetDirectoryURI(int64_t ObjectID); + std::auto_ptr<HTTPResponse> GetDirectory(BackupStoreDirectory& rDir); + int PutDirectory(BackupStoreDirectory& rDir); +}; + +class S3BackupAccountControl : public BackupAccountControl +{ +private: + std::string mBasePath; + std::auto_ptr<S3Client> mapS3Client; + std::auto_ptr<S3BackupFileSystem> mapFileSystem; +public: + S3BackupAccountControl(const Configuration& config, + bool machineReadableOutput = false); + std::string GetFullPath(const std::string ObjectPath) const + { + return mBasePath + ObjectPath; + } + std::string GetFullURL(const std::string ObjectPath) const; + int CreateAccount(const std::string& name, int32_t SoftLimit, int32_t HardLimit); + int GetBlockSize() { return 4096; } + HTTPResponse GetObject(const std::string& name) + { + return mapS3Client->GetObject(GetFullPath(name)); + } + HTTPResponse PutObject(const std::string& name, IOStream& rStreamToSend, + const char* pContentType = NULL) + { + return mapS3Client->PutObject(GetFullPath(name), rStreamToSend, + pContentType); + } +}; + +// max size of soft limit as percent of hard limit +#define MAX_SOFT_LIMIT_SIZE 97 +#define S3_INFO_FILE_NAME "boxbackup.info" +#define S3_NOTIONAL_BLOCK_SIZE 1048576 + +#endif // BACKUPACCOUNTCONTROL__H + diff --git a/lib/backupstore/BackupClientFileAttributes.cpp b/lib/backupstore/BackupClientFileAttributes.cpp new file mode 100644 index 00000000..7ec6f478 --- /dev/null +++ b/lib/backupstore/BackupClientFileAttributes.cpp @@ -0,0 +1,1237 @@ +// -------------------------------------------------------------------------- +// +// 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; + uint32_t UID; + uint32_t GID; + uint64_t ModificationTime; + uint64_t AttrModificationTime; + uint32_t UserDefinedFlags; + uint32_t FileGenerationNumber; + uint16_t Mode; + // Symbolic link filename may follow + // Extended attribute (xattr) information may follow, format is: + // uint32_t Size of extended attribute block (excluding this word) + // For each of NumberOfAttributes (sorted by AttributeName): + // uint16_t AttributeNameLength + // char AttributeName[AttributeNameLength] + // uint32_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(uint64_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(uint64_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); + + uint64_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(uint32_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(uint16_t)+attrKey.size()+1+sizeof(uint32_t)>static_cast<unsigned int>(xattrBufferSize)) + { + xattrBufferSize = (xattrBufferSize+sizeof(uint16_t)+attrKey.size()+1+sizeof(uint32_t))*2; + outputBlock.ResizeBlock(xattrBufferSize); + buffer = static_cast<unsigned char*>(outputBlock.GetBuffer()); + } + + // Store length and text for attibute name + uint16_t keyLength = htons(attrKey.size()+1); + std::memcpy(buffer+xattrSize, &keyLength, sizeof(uint16_t)); + xattrSize += sizeof(uint16_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(uint32_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 + uint32_t valueLength = htonl(valueSize); + std::memcpy(buffer+valueSizeOffset, &valueLength, sizeof(uint32_t)); + } + + // Fill in attribute block size + uint32_t xattrBlockLength = htonl(xattrSize-xattrBlockSizeOffset-sizeof(uint32_t)); + std::memcpy(buffer+xattrBlockSizeOffset, &xattrBlockLength, sizeof(uint32_t)); + + outputBlock.ResizeBlock(xattrSize); + } + else if(listSize<0) + { + if(errno == EOPNOTSUPP || errno == EACCES +#if HAVE_DECL_ENOTSUP + // NetBSD uses ENOTSUP instead + // https://mail-index.netbsd.org/tech-kern/2011/12/13/msg012185.html + || errno == ENOTSUP +#endif + ) + { + // Not supported by OS, or not on this filesystem + BOX_TRACE(BOX_SYS_ERRNO_MESSAGE(errno, + BOX_FILE_MESSAGE(Filename, "Failed to " + "list extended attributes"))); + } + else if(errno == ERANGE) + { + BOX_ERROR("Failed to list extended " + "attributes of '" << Filename << "': " + "buffer too small, not backed up"); + } + else if(errno == ENOENT) + { + BOX_ERROR("Failed to list extended " + "attributes of '" << Filename << "': " + "file no longer exists"); + } + else + { + THROW_SYS_FILE_ERROR("Failed to list extended " + "attributes for unknown reason", Filename, + 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(uint32_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()); + + uint32_t xattrBlockLength = 0; + std::memcpy(&xattrBlockLength, buffer+xattrOffset, sizeof(uint32_t)); + int xattrBlockSize = ntohl(xattrBlockLength); + xattrOffset += sizeof(uint32_t); + + int xattrEnd = xattrOffset+xattrBlockSize; + if(xattrEnd>mpClearAttributes->GetSize()) + { + // Too small + THROW_EXCEPTION(BackupStoreException, AttributesNotLoaded); + } + + while(xattrOffset<xattrEnd) + { + uint16_t keyLength = 0; + std::memcpy(&keyLength, buffer+xattrOffset, sizeof(uint16_t)); + int keySize = ntohs(keyLength); + xattrOffset += sizeof(uint16_t); + + const char* key = buffer+xattrOffset; + xattrOffset += keySize; + + uint32_t valueLength = 0; + std::memcpy(&valueLength, buffer+xattrOffset, sizeof(uint32_t)); + int valueSize = ntohl(valueLength); + xattrOffset += sizeof(uint32_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..22ef0215 --- /dev/null +++ b/lib/backupstore/BackupCommands.cpp @@ -0,0 +1,1037 @@ +// -------------------------------------------------------------------------- +// +// 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: BackupProtocolMessage::HandleException(BoxException& e) +// Purpose: Return an error message appropriate to the passed-in +// exception, or rethrow it. +// Created: 2014/09/14 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupProtocolMessage> BackupProtocolReplyable::HandleException(BoxException& e) const +{ + if(e.GetType() == RaidFileException::ExceptionType && + e.GetSubType() == RaidFileException::RaidFileDoesntExist) + { + return PROTOCOL_ERROR(Err_DoesNotExist); + } + else if (e.GetType() == BackupStoreException::ExceptionType) + { + if(e.GetSubType() == BackupStoreException::AddedFileDoesNotVerify) + { + return PROTOCOL_ERROR(Err_FileDoesNotVerify); + } + else if(e.GetSubType() == BackupStoreException::AddedFileExceedsStorageLimit) + { + return PROTOCOL_ERROR(Err_StorageLimitExceeded); + } + else if(e.GetSubType() == BackupStoreException::MultiplyReferencedObject) + { + return PROTOCOL_ERROR(Err_MultiplyReferencedObject); + } + else if(e.GetSubType() == BackupStoreException::CouldNotFindEntryInDirectory) + { + return PROTOCOL_ERROR(Err_DoesNotExistInDirectory); + } + else if(e.GetSubType() == BackupStoreException::NameAlreadyExistsInDirectory) + { + return PROTOCOL_ERROR(Err_TargetNameExists); + } + else if(e.GetSubType() == BackupStoreException::ObjectDoesNotExist) + { + return PROTOCOL_ERROR(Err_DoesNotExist); + } + else if(e.GetSubType() == BackupStoreException::PatchChainInfoBadInDirectory) + { + return PROTOCOL_ERROR(Err_PatchConsistencyError); + } + } + + throw; +} + + +// -------------------------------------------------------------------------- +// +// 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 +{ + // can be called in any phase + + BOX_NOTICE("Session finished for Client ID " << + BOX_FORMAT_ACCOUNT(rContext.GetClientID()) << " " + "(name=" << rContext.GetAccountName() << ")"); + + // Let the context know about it + rContext.ReceivedFinishCommand(); + + 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); + + // 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 */); + + 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, + IOStream& rDataStream) 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); + } + } + + // Ask the context to store it + int64_t id = rContext.AddFile(rDataStream, mDirectoryObjectID, + mModificationTime, mAttributesHash, mDiffFromFileID, + mFilename, + true /* mark files with same name as old versions */); + + // 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 PROTOCOL_ERROR(Err_DoesNotExist); + } + + // 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.GetAccountRoot() << ".recombinetemp." << p; + std::string tempFn = + RaidFileController::DiscSetPathToFileSystemPath( + rContext.GetStoreDiscSet(), fs.str(), + p + 16); + + // Open the temporary file + std::auto_ptr<IOStream> combined( + new InvisibleTempFileStream( + tempFn, O_RDWR | O_CREAT | O_EXCL | + O_BINARY | O_TRUNC)); + + // 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, + IOStream& rDataStream) const +{ + return BackupProtocolCreateDirectory2(mContainingDirectoryID, + mAttributesModTime, 0 /* ModificationTime */, + mDirectoryName).DoCommand(rProtocol, rContext, rDataStream); +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolCreateDirectory2::DoCommand(Protocol &, BackupStoreContext &) +// Purpose: Create directory command, with a specific +// modification time. +// Created: 2014/02/11 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupProtocolMessage> BackupProtocolCreateDirectory2::DoCommand( + BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext, + IOStream& rDataStream) const +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Collect the attributes -- do this now so no matter what the outcome, + // the data has been absorbed. + StreamableMemBlock attr; + attr.Set(rDataStream, 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, mModificationTime, + 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, + IOStream& rDataStream) const +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Collect the attributes -- do this now so no matter what the outcome, + // the data has been absorbed. + StreamableMemBlock attr; + attr.Set(rDataStream, 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, + IOStream& rDataStream) const +{ + CHECK_PHASE(Phase_Commands) + CHECK_WRITEABLE_SESSION + + // Collect the attributes -- do this now so no matter what the outcome, + // the data has been absorbed. + StreamableMemBlock attr; + attr.Set(rDataStream, 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 + rContext.DeleteDirectory(mObjectID); + + // 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... + rContext.MoveObject(mObjectID, mMoveFromDirectory, mMoveToDirectory, + mNewFilename, (mFlags & Flags_MoveAllWithSameName) == Flags_MoveAllWithSameName, + (mFlags & Flags_AllowMoveOverDeletedObject) == Flags_AllowMoveOverDeletedObject); + + // 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 *pDir; + + try + { + pDir = &rContext.GetDirectory(dirID); + } + catch(BackupStoreException &e) + { + if(e.GetSubType() == BackupStoreException::ObjectDoesNotExist) + { + // If this can't be found, then there is a problem... + // tell the caller it can't be found. + return std::auto_ptr<BackupProtocolMessage>( + new BackupProtocolObjectName( + BackupProtocolObjectName::NumNameElements_ObjectDoesntExist, + 0, 0, 0)); + } + + throw; + } + + const BackupStoreDirectory& rdir(*pDir); + + // 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()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupProtocolGetAccountUsage2::DoCommand(BackupProtocolReplyable &, BackupStoreContext &) +// Purpose: Return the amount of disc space used +// Created: 26/12/13 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupProtocolMessage> BackupProtocolGetAccountUsage2::DoCommand( + BackupProtocolReplyable &rProtocol, BackupStoreContext &rContext) const +{ + CHECK_PHASE(Phase_Commands) + + // Get store info from context + const BackupStoreInfo &info(rContext.GetBackupStoreInfo()); + + // Find block size + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(info.GetDiscSetNumber())); + + // Return info + BackupProtocolAccountUsage2* usage = new BackupProtocolAccountUsage2(); + std::auto_ptr<BackupProtocolMessage> reply(usage); + #define COPY(name) usage->Set ## name (info.Get ## name ()) + COPY(AccountName); + usage->SetAccountEnabled(info.IsAccountEnabled()); + COPY(ClientStoreMarker); + usage->SetBlockSize(rdiscSet.GetBlockSize()); + COPY(LastObjectIDUsed); + COPY(BlocksUsed); + COPY(BlocksInCurrentFiles); + COPY(BlocksInOldFiles); + COPY(BlocksInDeletedFiles); + COPY(BlocksInDirectories); + COPY(BlocksSoftLimit); + COPY(BlocksHardLimit); + COPY(NumCurrentFiles); + COPY(NumOldFiles); + COPY(NumDeletedFiles); + COPY(NumDirectories); + #undef COPY + + return reply; +} diff --git a/lib/backupstore/BackupConstants.h b/lib/backupstore/BackupConstants.h new file mode 100644 index 00000000..195dc621 --- /dev/null +++ b/lib/backupstore/BackupConstants.h @@ -0,0 +1,24 @@ +// -------------------------------------------------------------------------- +// +// 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) + +// Time to wait for retry after a backup error +#define BACKUP_ERROR_RETRY_SECONDS 100 + +// 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/BackupProtocol.h b/lib/backupstore/BackupProtocol.h new file mode 100644 index 00000000..d9070c73 --- /dev/null +++ b/lib/backupstore/BackupProtocol.h @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupProtocol.h +// Purpose: A thin wrapper around autogen_BackupProtocol.h +// Created: 2014/01/05 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPPROTOCOL__H +#define BACKUPPROTOCOL__H + +#include <autogen_BackupProtocol.h> +#include <BackupStoreConstants.h> +#include <BackupStoreContext.h> + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupProtocolLocal2 +// Purpose: BackupProtocolLocal with a few more IQ points +// Created: 2014/09/20 +// +// -------------------------------------------------------------------------- +class BackupProtocolLocal2 : public BackupProtocolLocal +{ +private: + BackupStoreContext mContext; + int32_t mAccountNumber; + bool mReadOnly; + +protected: + BackupStoreContext& GetContext() { return mContext; } + +public: + BackupProtocolLocal2(int32_t AccountNumber, + const std::string& ConnectionDetails, + const std::string& AccountRootDir, int DiscSetNumber, + bool ReadOnly) + // This is rather ugly: the BackupProtocolLocal constructor must not + // touch the Context, because it's not initialised yet! + : BackupProtocolLocal(mContext), + mContext(AccountNumber, (HousekeepingInterface *)NULL, + ConnectionDetails), + mAccountNumber(AccountNumber), + mReadOnly(ReadOnly) + { + mContext.SetClientHasAccount(AccountRootDir, DiscSetNumber); + QueryVersion(BACKUP_STORE_SERVER_VERSION); + QueryLogin(AccountNumber, + ReadOnly ? BackupProtocolLogin::Flags_ReadOnly : 0); + } + virtual ~BackupProtocolLocal2() { } + + std::auto_ptr<BackupProtocolFinished> Query(const BackupProtocolFinished &rQuery) + { + std::auto_ptr<BackupProtocolFinished> finished = + BackupProtocolLocal::Query(rQuery); + mContext.ReleaseWriteLock(); + return finished; + } + + void Reopen() + { + QueryVersion(BACKUP_STORE_SERVER_VERSION); + QueryLogin(mAccountNumber, + mReadOnly ? BackupProtocolLogin::Flags_ReadOnly : 0); + } +}; + +#endif // BACKUPPROTOCOL__H diff --git a/lib/backupstore/BackupStoreAccountDatabase.cpp b/lib/backupstore/BackupStoreAccountDatabase.cpp new file mode 100644 index 00000000..c5f012fc --- /dev/null +++ b/lib/backupstore/BackupStoreAccountDatabase.cpp @@ -0,0 +1,374 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccountDatabase.cpp +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <string> +#include <map> +#include <stdio.h> +#include <sys/stat.h> + +#include "BackupStoreAccountDatabase.h" +#include "Guards.h" +#include "FdGetLine.h" +#include "BackupStoreException.h" +#include "CommonException.h" +#include "FileModificationTime.h" + +#include "MemLeakFindOn.h" + +class _BackupStoreAccountDatabase +{ +public: + std::string mFilename; + std::map<int32_t, BackupStoreAccountDatabase::Entry> mDatabase; + box_time_t mModificationTime; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::BackupStoreAccountDatabase(const char *) +// Purpose: Constructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::BackupStoreAccountDatabase(const std::string& Filename) + : pImpl(new _BackupStoreAccountDatabase) +{ + pImpl->mFilename = Filename; + pImpl->mModificationTime = 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::~BackupStoreAccountDatabase() +// Purpose: Destructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::~BackupStoreAccountDatabase() +{ + delete pImpl; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry() +// Purpose: Default constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry() + : mID(-1), + mDiscSet(-1) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry(int32_t, int) +// Purpose: Constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry(int32_t ID, int DiscSet) + : mID(ID), + mDiscSet(DiscSet) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::Entry(const Entry &) +// Purpose: Copy constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::Entry(const Entry &rEntry) + : mID(rEntry.mID), + mDiscSet(rEntry.mDiscSet) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Entry::~Entry() +// Purpose: Destructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry::~Entry() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Read(const char *) +// Purpose: Read in a database from disc +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreAccountDatabase> BackupStoreAccountDatabase::Read(const std::string& Filename) +{ + // Database object to use + std::auto_ptr<BackupStoreAccountDatabase> db(new BackupStoreAccountDatabase(Filename)); + + // Read in the file + db->ReadFile(); + + // Return to called + return db; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::ReadFile() +// Purpose: Read the file off disc +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::ReadFile() const +{ + // Open file + FileHandleGuard<> file(pImpl->mFilename.c_str()); + + // Clear existing entries + pImpl->mDatabase.clear(); + + // Read in lines + FdGetLine getLine(file); + + while(!getLine.IsEOF()) + { + // Read and split up line + std::string l(getLine.GetLine(true)); + + if(!l.empty()) + { + // Check... + int32_t id; + int discSet; + if(::sscanf(l.c_str(), "%x:%d", &id, &discSet) != 2) + { + THROW_EXCEPTION(BackupStoreException, BadAccountDatabaseFile) + } + + // Make a new entry + pImpl->mDatabase[id] = Entry(id, discSet); + } + } + + // Store the modification time of the file + pImpl->mModificationTime = GetDBFileModificationTime(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::CheckUpToDate() +// Purpose: Private. Ensure that the in memory database matches the one on disc +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::CheckUpToDate() const +{ + if(pImpl->mModificationTime != GetDBFileModificationTime()) + { + // File has changed -- load it in again + ReadFile(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetDBFileModificationTime() +// Purpose: Get the current modification time of the database +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +box_time_t BackupStoreAccountDatabase::GetDBFileModificationTime() const +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(pImpl->mFilename.c_str(), &st) == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + + return FileModificationTime(st); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::Write() +// Purpose: Write the database back to disc after modifying it +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::Write() +{ + // Open file for writing + // Would use this... + // FileHandleGuard<O_WRONLY | O_TRUNC> file(pImpl->mFilename.c_str()); + // but gcc fails randomly on it on some platforms. Weird. + + int file = ::open(pImpl->mFilename.c_str(), O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(file == -1) + { + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + + try + { + // Then write each entry + for(std::map<int32_t, BackupStoreAccountDatabase::Entry>::const_iterator i(pImpl->mDatabase.begin()); + i != pImpl->mDatabase.end(); ++i) + { + // Write out the entry + char line[256]; // more than enough for a couple of integers in string form + int s = ::snprintf(line, sizeof(line), "%x:%d\n", + i->second.GetID(), i->second.GetDiscSet()); + if(::write(file, line, s) != s) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } + + ::close(file); + } + catch(...) + { + ::close(file); + throw; + } + + // Done. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::EntryExists(int32_t) +// Purpose: Does an entry exist in the database? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool BackupStoreAccountDatabase::EntryExists(int32_t ID) const +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + return pImpl->mDatabase.find(ID) != pImpl->mDatabase.end(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetEntry(int32_t) +// Purpose: Retrieve an entry +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::GetEntry( + int32_t ID) const +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::const_iterator i(pImpl->mDatabase.find(ID)); + if(i == pImpl->mDatabase.end()) + { + THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) + } + + return i->second; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::AddEntry(int32_t, int) +// Purpose: Add a new entry to the database +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccountDatabase::Entry BackupStoreAccountDatabase::AddEntry( + int32_t ID, int DiscSet) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + pImpl->mDatabase[ID] = Entry(ID, DiscSet); + return pImpl->mDatabase[ID]; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::DeleteEntry(int32_t) +// Purpose: Delete an entry from the database +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::DeleteEntry(int32_t ID) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::iterator i(pImpl->mDatabase.find(ID)); + if(i == pImpl->mDatabase.end()) + { + THROW_EXCEPTION(BackupStoreException, AccountDatabaseNoSuchEntry) + } + + pImpl->mDatabase.erase(i); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccountDatabase::GetAllAccountIDs(std::vector<int32_t>) +// Purpose: +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreAccountDatabase::GetAllAccountIDs(std::vector<int32_t> &rIDsOut) +{ + // Check that we're using the latest version of the database + CheckUpToDate(); + + // Delete everything in the output list + rIDsOut.clear(); + + std::map<int32_t, BackupStoreAccountDatabase::Entry>::iterator i(pImpl->mDatabase.begin()); + for(; i != pImpl->mDatabase.end(); ++i) + { + rIDsOut.push_back(i->first); + } +} + + + + diff --git a/lib/backupstore/BackupStoreAccountDatabase.h b/lib/backupstore/BackupStoreAccountDatabase.h new file mode 100644 index 00000000..f9665c7d --- /dev/null +++ b/lib/backupstore/BackupStoreAccountDatabase.h @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccountDatabase.h +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREACCOUNTDATABASE__H +#define BACKUPSTOREACCOUNTDATABASE__H + +#include <memory> +#include <vector> + +#include "BoxTime.h" + +class _BackupStoreAccountDatabase; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreAccountDatabase +// Purpose: Database of accounts for the backup store +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +class BackupStoreAccountDatabase +{ +public: + friend class _BackupStoreAccountDatabase; // to stop compiler warnings + ~BackupStoreAccountDatabase(); +private: + BackupStoreAccountDatabase(const std::string& Filename); + BackupStoreAccountDatabase(const BackupStoreAccountDatabase &); +public: + + static std::auto_ptr<BackupStoreAccountDatabase> Read(const std::string& Filename); + void Write(); + + class Entry + { + public: + Entry(); + Entry(int32_t ID, int DiscSet); + Entry(const Entry &rEntry); + ~Entry(); + + int32_t GetID() const {return mID;} + int GetDiscSet() const {return mDiscSet;} + + private: + int32_t mID; + int mDiscSet; + }; + + bool EntryExists(int32_t ID) const; + Entry GetEntry(int32_t ID) const; + Entry AddEntry(int32_t ID, int DiscSet); + void DeleteEntry(int32_t ID); + + // This interface should change in the future. But for now it'll do. + void GetAllAccountIDs(std::vector<int32_t> &rIDsOut); + +private: + void ReadFile() const; // const in concept only + void CheckUpToDate() const; // const in concept only + box_time_t GetDBFileModificationTime() const; + +private: + mutable _BackupStoreAccountDatabase *pImpl; +}; + +#endif // BACKUPSTOREACCOUNTDATABASE__H + diff --git a/lib/backupstore/BackupStoreAccounts.cpp b/lib/backupstore/BackupStoreAccounts.cpp new file mode 100644 index 00000000..7955b3c4 --- /dev/null +++ b/lib/backupstore/BackupStoreAccounts.cpp @@ -0,0 +1,595 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccounts.cpp +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <algorithm> +#include <climits> +#include <cstdio> +#include <cstring> +#include <iostream> + +#include "BackupStoreAccounts.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreCheck.h" +#include "BackupStoreConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreInfo.h" +#include "BackupStoreRefCountDatabase.h" +#include "BoxPortsAndFiles.h" +#include "HousekeepStoreAccount.h" +#include "NamedLock.h" +#include "RaidFileController.h" +#include "RaidFileWrite.h" +#include "StoreStructure.h" +#include "UnixUser.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &) +// Purpose: Constructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +BackupStoreAccounts::BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase) + : mrDatabase(rDatabase) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::~BackupStoreAccounts() +// Purpose: Destructor +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +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. +// If rAsUsername is not empty, then the account information will be written under the +// username specified. +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccounts::Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, int64_t SizeHardLimit, const std::string &rAsUsername) +{ + // Create the entry in the database + BackupStoreAccountDatabase::Entry Entry(mrDatabase.AddEntry(ID, + DiscSet)); + + { + // Become the user specified in the config file? + std::auto_ptr<UnixUser> user; + if(!rAsUsername.empty()) + { + // Username specified, change... + user.reset(new UnixUser(rAsUsername.c_str())); + user->ChangeProcessUser(true /* temporary */); + // Change will be undone at the end of this function + } + + // Get directory name + std::string dirName(MakeAccountRootDir(ID, DiscSet)); + + // Create a directory on disc + RaidFileWrite::CreateDirectory(DiscSet, dirName, true /* recursive */); + + // Create an info file + BackupStoreInfo::CreateNew(ID, dirName, DiscSet, SizeSoftLimit, SizeHardLimit); + + // And an empty directory + BackupStoreDirectory rootDir(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); + int64_t rootDirSize = 0; + // Write it, knowing the directory scheme + { + RaidFileWrite rf(DiscSet, dirName + "o01"); + rf.Open(); + rootDir.WriteToStream(rf); + rootDirSize = rf.GetDiscUsageInBlocks(); + rf.Commit(true); + } + + // Update the store info to reflect the size of the root directory + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, dirName, DiscSet, false /* ReadWrite */)); + info->ChangeBlocksUsed(rootDirSize); + info->ChangeBlocksInDirectories(rootDirSize); + info->AdjustNumDirectories(1); + + // Save it back + info->Save(); + + // Create the refcount database + BackupStoreRefCountDatabase::Create(Entry)->Commit(); + } + + // As the original user... + // Write the database back + mrDatabase.Write(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::GetAccountRoot(int32_t, std::string &, int &) +// Purpose: Gets the root of an account, returning the info via references +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void BackupStoreAccounts::GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const +{ + // Find the account + const BackupStoreAccountDatabase::Entry &en(mrDatabase.GetEntry(ID)); + + rRootDirOut = MakeAccountRootDir(ID, en.GetDiscSet()); + rDiscSetOut = en.GetDiscSet(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::MakeAccountRootDir(int32_t, int) +// Purpose: Private. Generates a root directory name for the account +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +std::string BackupStoreAccounts::MakeAccountRootDir(int32_t ID, int DiscSet) +{ + char accid[64]; // big enough! + ::snprintf(accid, sizeof(accid) - 1, "%08x" DIRECTORY_SEPARATOR, ID); + return std::string(BOX_RAIDFILE_ROOT_BBSTORED DIRECTORY_SEPARATOR) + + accid; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreAccounts::AccountExists(int32_t) +// Purpose: Does an account exist? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool BackupStoreAccounts::AccountExists(int32_t ID) +{ + return mrDatabase.EntryExists(ID); +} + +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 " << BOX_FORMAT_ACCOUNT(ID)); + } +} + +int BackupStoreAccountsControl::BlockSizeOfDiscSet(int discSetNum) +{ + // Get controller, check disc set number + RaidFileController &controller(RaidFileController::GetController()); + if(discSetNum < 0 || discSetNum >= controller.GetNumDiscSets()) + { + BOX_FATAL("Disc set " << discSetNum << " does not exist."); + exit(1); + } + + // Return block size + return controller.GetDiscSet(discSetNum).GetBlockSize(); +} + +int BackupStoreAccountsControl::SetLimit(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(!OpenAccount(ID, rootDir, discSetNum, user, &writeLock)) + { + 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 */)); + + // Change the limits + int blocksize = BlockSizeOfDiscSet(discSetNum); + int64_t softlimit = SizeStringToBlocks(SoftLimitStr, blocksize); + int64_t hardlimit = SizeStringToBlocks(HardLimitStr, blocksize); + 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 BackupStoreAccountsControl::SetAccountName(int32_t ID, const std::string& rNewAccountName) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + NamedLock writeLock; + + if(!OpenAccount(ID, rootDir, discSetNum, user, &writeLock)) + { + 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, discSetNum, false /* Read/Write */)); + + info->SetAccountName(rNewAccountName); + + // Save + info->Save(); + + BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << + " name changed to " << rNewAccountName); + + return 0; +} + +int BackupStoreAccountsControl::PrintAccountInfo(int32_t ID) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + + if(!OpenAccount(ID, rootDir, discSetNum, user, + NULL /* no write lock needed for this read-only operation */)) + { + BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) + << " to display info."); + return 1; + } + + // Load it in + std::auto_ptr<BackupStoreInfo> info(BackupStoreInfo::Load(ID, + rootDir, discSetNum, true /* ReadOnly */)); + + return BackupAccountControl::PrintAccountInfo(*info, + BlockSizeOfDiscSet(discSetNum)); +} + +int BackupStoreAccountsControl::SetAccountEnabled(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(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 BackupStoreAccountsControl::DeleteAccount(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(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) + { + BOX_WARNING("Really delete account " << + BOX_FORMAT_ACCOUNT(ID) << "? (type 'yes' to confirm)"); + char response[256]; + if(::fgets(response, sizeof(response), stdin) == 0 || ::strcmp(response, "yes\n") != 0) + { + BOX_NOTICE("Deletion cancelled."); + return 0; + } + } + + // Back to original user, but write lock is maintained + user.reset(); + + std::auto_ptr<BackupStoreAccountDatabase> db( + BackupStoreAccountDatabase::Read( + mConfig.GetKeyValue("AccountDatabase"))); + + // Delete from account database + db->DeleteEntry(ID); + + // Write back to disc + db->Write(); + + // Remove the store files... + + // First, become the user specified in the config file + std::string username; + { + const Configuration &rserverConfig(mConfig.GetSubConfiguration("Server")); + if(rserverConfig.KeyExists("User")) + { + username = rserverConfig.GetKeyValue("User"); + } + } + + // Become the right user + if(!username.empty()) + { + // Username specified, change... + user.reset(new UnixUser(username)); + user->ChangeProcessUser(true /* temporary */); + // 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()); + RaidFileDiscSet discSet(rcontroller.GetDiscSet(discSetNum)); + for(RaidFileDiscSet::const_iterator i(discSet.begin()); i != discSet.end(); ++i) + { + if(std::find(toDelete.begin(), toDelete.end(), *i) == toDelete.end()) + { + toDelete.push_back((*i) + DIRECTORY_SEPARATOR + rootDir); + } + } + + // NamedLock will throw an exception if it can't delete the lockfile, + // which it can't if it doesn't exist. Now that we've deleted the account, + // nobody can open it anyway, so it's safe to unlock. + writeLock.ReleaseLock(); + + int retcode = 0; + + // Thirdly, delete the directories... + for(std::vector<std::string>::const_iterator d(toDelete.begin()); d != toDelete.end(); ++d) + { + BOX_NOTICE("Deleting store directory " << (*d) << "..."); + // Just use the rm command to delete the files +#ifdef WIN32 + std::string cmd("rmdir /s/q "); + std::string dir = *d; + + // rmdir doesn't understand forward slashes, so replace them all. + for(std::string::iterator i = dir.begin(); i != dir.end(); i++) + { + if(*i == '/') + { + *i = '\\'; + } + } + cmd += dir; +#else + std::string cmd("rm -rf "); + cmd += *d; +#endif + // Run command + if(::system(cmd.c_str()) != 0) + { + BOX_ERROR("Failed to delete files in " << (*d) << + ", delete them manually."); + retcode = 1; + } + } + + // Success! + return retcode; +} + +bool BackupStoreAccountsControl::OpenAccount(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( + mConfig.GetKeyValue("AccountDatabase"))); + + // Exists? + if(!db->EntryExists(ID)) + { + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " does not exist."); + return false; + } + + // Get info from the database + BackupStoreAccounts acc(*db); + acc.GetAccountRoot(ID, rRootDirOut, rDiscSetOut); + + // Get the user under which the daemon runs + std::string username; + { + const Configuration &rserverConfig(mConfig.GetSubConfiguration("Server")); + if(rserverConfig.KeyExists("User")) + { + username = rserverConfig.GetKeyValue("User"); + } + } + + // Become the right user + if(!username.empty()) + { + // Username specified, change... + 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 BackupStoreAccountsControl::CheckAccount(int32_t ID, bool FixErrors, bool Quiet, + bool ReturnNumErrorsFound) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + NamedLock writeLock; + + if(!OpenAccount(ID, rootDir, discSetNum, user, + FixErrors ? &writeLock : NULL)) // don't need a write lock if not making changes + { + BOX_ERROR("Failed to open account " << BOX_FORMAT_ACCOUNT(ID) + << " for checking."); + return 1; + } + + // Check it + BackupStoreCheck check(rootDir, discSetNum, ID, FixErrors, Quiet); + check.Check(); + + if(ReturnNumErrorsFound) + { + return check.GetNumErrorsFound(); + } + else + { + return check.ErrorsFound() ? 1 : 0; + } +} + +int BackupStoreAccountsControl::CreateAccount(int32_t ID, int32_t DiscNumber, + int32_t SoftLimit, int32_t HardLimit) +{ + // Load in the account database + std::auto_ptr<BackupStoreAccountDatabase> db( + BackupStoreAccountDatabase::Read( + mConfig.GetKeyValue("AccountDatabase"))); + + // Already exists? + if(db->EntryExists(ID)) + { + BOX_ERROR("Account " << BOX_FORMAT_ACCOUNT(ID) << + " already exists."); + return 1; + } + + // Get the user under which the daemon runs + std::string username; + { + const Configuration &rserverConfig(mConfig.GetSubConfiguration("Server")); + if(rserverConfig.KeyExists("User")) + { + username = rserverConfig.GetKeyValue("User"); + } + } + + // Create it. + BackupStoreAccounts acc(*db); + acc.Create(ID, DiscNumber, SoftLimit, HardLimit, username); + + BOX_NOTICE("Account " << BOX_FORMAT_ACCOUNT(ID) << " created."); + + return 0; +} + +int BackupStoreAccountsControl::HousekeepAccountNow(int32_t ID) +{ + std::string rootDir; + int discSetNum; + std::auto_ptr<UnixUser> user; // used to reset uid when we return + + if(!OpenAccount(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; + } +} + diff --git a/lib/backupstore/BackupStoreAccounts.h b/lib/backupstore/BackupStoreAccounts.h new file mode 100644 index 00000000..bcc3cf1c --- /dev/null +++ b/lib/backupstore/BackupStoreAccounts.h @@ -0,0 +1,85 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreAccounts.h +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREACCOUNTS__H +#define BACKUPSTOREACCOUNTS__H + +#include <string> + +#include "BackupStoreAccountDatabase.h" +#include "BackupAccountControl.h" +#include "NamedLock.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreAccounts +// Purpose: Account management for backup store server +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +class BackupStoreAccounts +{ +public: + BackupStoreAccounts(BackupStoreAccountDatabase &rDatabase); + ~BackupStoreAccounts(); +private: + BackupStoreAccounts(const BackupStoreAccounts &rToCopy); + +public: + void Create(int32_t ID, int DiscSet, int64_t SizeSoftLimit, + int64_t SizeHardLimit, const std::string &rAsUsername); + + bool AccountExists(int32_t ID); + void GetAccountRoot(int32_t ID, std::string &rRootDirOut, int &rDiscSetOut) const; + static std::string GetAccountRoot(const + BackupStoreAccountDatabase::Entry &rEntry) + { + return MakeAccountRootDir(rEntry.GetID(), rEntry.GetDiscSet()); + } + void LockAccount(int32_t ID, NamedLock& rNamedLock); + +private: + static std::string MakeAccountRootDir(int32_t ID, int DiscSet); + +private: + BackupStoreAccountDatabase &mrDatabase; +}; + +class Configuration; +class UnixUser; + +class BackupStoreAccountsControl : public BackupAccountControl +{ +public: + BackupStoreAccountsControl(const Configuration& config, + bool machineReadableOutput = false) + : BackupAccountControl(config, machineReadableOutput) + { } + int BlockSizeOfDiscSet(int discSetNum); + bool OpenAccount(int32_t ID, std::string &rRootDirOut, + int &rDiscSetOut, std::auto_ptr<UnixUser> apUser, NamedLock* pLock); + int SetLimit(int32_t ID, const char *SoftLimitStr, + const char *HardLimitStr); + int SetAccountName(int32_t ID, const std::string& rNewAccountName); + int PrintAccountInfo(int32_t ID); + int SetAccountEnabled(int32_t ID, bool enabled); + int DeleteAccount(int32_t ID, bool AskForConfirmation); + int CheckAccount(int32_t ID, bool FixErrors, bool Quiet, + bool ReturnNumErrorsFound = false); + int CreateAccount(int32_t ID, int32_t DiscNumber, int32_t SoftLimit, + int32_t HardLimit); + int HousekeepAccountNow(int32_t ID); +}; + +// max size of soft limit as percent of hard limit +#define MAX_SOFT_LIMIT_SIZE 97 + +#endif // BACKUPSTOREACCOUNTS__H + diff --git a/lib/backupstore/BackupStoreCheck.cpp b/lib/backupstore/BackupStoreCheck.cpp new file mode 100644 index 00000000..b53ebf6d --- /dev/null +++ b/lib/backupstore/BackupStoreCheck.cpp @@ -0,0 +1,1021 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck.cpp +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.h> + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include "autogen_BackupStoreException.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreCheck.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreFile.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreRefCountDatabase.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "RaidFileUtil.h" +#include "RaidFileWrite.h" +#include "StoreStructure.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::BackupStoreCheck(const std::string &, int, int32_t, bool, bool) +// Purpose: Constructor +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet) + : mStoreRoot(rStoreRoot), + mDiscSetNumber(DiscSetNumber), + mAccountID(AccountID), + mFixErrors(FixErrors), + mQuiet(Quiet), + mNumberErrorsFound(0), + mLastIDInInfo(0), + mpInfoLastBlock(0), + mInfoLastBlockEntries(0), + mLostDirNameSerial(0), + mLostAndFoundDirectoryID(0), + mBlocksUsed(0), + mBlocksInCurrentFiles(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0), + mBlocksInDirectories(0), + mNumCurrentFiles(0), + mNumOldFiles(0), + mNumDeletedFiles(0), + mNumDirectories(0) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::~BackupStoreCheck() +// Purpose: Destructor +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::~BackupStoreCheck() +{ + // Clean up + FreeInfo(); + + // Avoid an exception if we forget to discard mapNewRefs + if (mapNewRefs.get()) + { + // Discard() can throw exception, but destructors aren't supposed to do that, so + // just catch and log them. + try + { + mapNewRefs->Discard(); + } + catch(BoxException &e) + { + BOX_ERROR("Error while destroying BackupStoreCheck: discarding " + "the refcount database threw an exception: " << e.what()); + } + + mapNewRefs.reset(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::Check() +// Purpose: Perform the check on the given account. You need to +// hold a lock on the account before calling this! +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::Check() +{ + if(mFixErrors) + { + std::string writeLockFilename; + StoreStructure::MakeWriteLockFilename(mStoreRoot, mDiscSetNumber, writeLockFilename); + ASSERT(FileExists(writeLockFilename)); + } + + if(!mQuiet && mFixErrors) + { + BOX_INFO("Will fix errors encountered during checking."); + } + + BackupStoreAccountDatabase::Entry account(mAccountID, mDiscSetNumber); + mapNewRefs = BackupStoreRefCountDatabase::Create(account); + + // Phase 1, check objects + if(!mQuiet) + { + BOX_INFO("Checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << "..."); + BOX_INFO("Phase 1, check objects..."); + } + CheckObjects(); + + // Phase 2, check directories + if(!mQuiet) + { + BOX_INFO("Phase 2, check directories..."); + } + CheckDirectories(); + + // Phase 3, check root + if(!mQuiet) + { + BOX_INFO("Phase 3, check root..."); + } + CheckRoot(); + + // Phase 4, check unattached objects + if(!mQuiet) + { + BOX_INFO("Phase 4, fix unattached objects..."); + } + CheckUnattachedObjects(); + + // Phase 5, fix bad info + if(!mQuiet) + { + BOX_INFO("Phase 5, fix unrecovered inconsistencies..."); + } + FixDirsWithWrongContainerID(); + FixDirsWithLostDirs(); + + // Phase 6, regenerate store info + if(!mQuiet) + { + BOX_INFO("Phase 6, regenerate store info..."); + } + WriteNewStoreInfo(); + + try + { + std::auto_ptr<BackupStoreRefCountDatabase> apOldRefs = + BackupStoreRefCountDatabase::Load(account, false); + mNumberErrorsFound += mapNewRefs->ReportChangesTo(*apOldRefs); + } + catch(BoxException &e) + { + BOX_WARNING("Reference count database was missing or " + "corrupted, cannot check it for errors."); + mNumberErrorsFound++; + } + + // force file to be saved and closed before releasing the lock below + if(mFixErrors) + { + mapNewRefs->Commit(); + } + else + { + mapNewRefs->Discard(); + } + mapNewRefs.reset(); + + if(mNumberErrorsFound > 0) + { + BOX_WARNING("Finished checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << ": " << + mNumberErrorsFound << " errors found"); + if(!mFixErrors) + { + BOX_WARNING("No changes to the store account " + "have been made."); + } + if(!mFixErrors && mNumberErrorsFound > 0) + { + BOX_WARNING("Run again with fix option to " + "fix these errors"); + } + if(mFixErrors && mNumberErrorsFound > 0) + { + BOX_WARNING("You should now use bbackupquery " + "on the client machine to examine the store."); + if(mLostAndFoundDirectoryID != 0) + { + BOX_WARNING("A lost+found directory was " + "created in the account root.\n" + "This contains files and directories " + "which could not be matched to " + "existing directories.\n"\ + "bbackupd will delete this directory " + "in a few days time."); + } + } + } + else + { + BOX_NOTICE("Finished checking store account ID " << + BOX_FORMAT_ACCOUNT(mAccountID) << ": " + "no errors found"); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: static TwoDigitHexToInt(const char *, int &) +// Purpose: Convert a two digit hex string to an int, returning whether it's valid or not +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +static inline bool TwoDigitHexToInt(const char *String, int &rNumberOut) +{ + int n = 0; + // Char 0 + if(String[0] >= '0' && String[0] <= '9') + { + n = (String[0] - '0') << 4; + } + else if(String[0] >= 'a' && String[0] <= 'f') + { + n = ((String[0] - 'a') + 0xa) << 4; + } + else + { + return false; + } + // Char 1 + if(String[1] >= '0' && String[1] <= '9') + { + n |= String[1] - '0'; + } + else if(String[1] >= 'a' && String[1] <= 'f') + { + n |= (String[1] - 'a') + 0xa; + } + else + { + return false; + } + + // Return a valid number + rNumberOut = n; + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjects() +// Purpose: Read in the contents of the directory, recurse to other levels, +// checking objects for sanity and readability +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckObjects() +{ + // Maximum start ID of directories -- worked out by looking at disc contents, not trusting anything + int64_t maxDir = 0; + + // Find the maximum directory starting ID + { + // Make sure the starting root dir doesn't end with '/'. + std::string start(mStoreRoot); + if(start.size() > 0 && ( + start[start.size() - 1] == '/' || + start[start.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR)) + { + start.resize(start.size() - 1); + } + + maxDir = CheckObjectsScanDir(0, 1, start); + BOX_TRACE("Max dir starting ID is " << + BOX_FORMAT_OBJECTID(maxDir)); + } + + // Then go through and scan all the objects within those directories + for(int64_t d = 0; d <= maxDir; d += (1<<STORE_ID_SEGMENT_LENGTH)) + { + CheckObjectsDir(d); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjectsScanDir(int64_t, int, int, const std::string &) +// Purpose: Read in the contents of the directory, recurse to other levels, +// return the maximum starting ID of any directory found. +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckObjectsScanDir(int64_t StartID, int Level, const std::string &rDirName) +{ + //TRACE2("Scan directory for max dir starting ID %s, StartID %lld\n", rDirName.c_str(), StartID); + + int64_t maxID = StartID; + + // Read in all the directories, and recurse downwards + { + // 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); + EMU_STRUCT_STAT st; + + if(EMU_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); + } + } + } + } + + std::vector<std::string> dirs; + RaidFileRead::ReadDirectoryContents(mDiscSetNumber, rDirName, + RaidFileRead::DirReadType_DirsOnly, dirs); + + for(std::vector<std::string>::const_iterator i(dirs.begin()); i != dirs.end(); ++i) + { + // Check to see if it's the right name + int n = 0; + if((*i).size() == 2 && TwoDigitHexToInt((*i).c_str(), n) + && n < (1<<STORE_ID_SEGMENT_LENGTH)) + { + // Next level down + int64_t mi = CheckObjectsScanDir(StartID | (n << (Level * STORE_ID_SEGMENT_LENGTH)), Level + 1, + rDirName + DIRECTORY_SEPARATOR + *i); + // Found a greater starting ID? + if(mi > maxID) + { + maxID = mi; + } + } + else + { + BOX_ERROR("Spurious or invalid directory " << + rDirName << DIRECTORY_SEPARATOR << + (*i) << " found, " << + (mFixErrors?"deleting":"delete manually")); + ++mNumberErrorsFound; + } + } + } + + return maxID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckObjectsDir(int64_t) +// Purpose: Check all the files within this directory which has +// the given starting ID. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckObjectsDir(int64_t StartID) +{ + // Make directory name -- first generate the filename of an entry in it + std::string dirName; + StoreStructure::MakeObjectFilename(StartID, mStoreRoot, mDiscSetNumber, dirName, false /* don't make sure the dir exists */); + // Check expectations + ASSERT(dirName.size() > 4 && + dirName[dirName.size() - 4] == DIRECTORY_SEPARATOR_ASCHAR); + // Remove the filename from it + dirName.resize(dirName.size() - 4); // four chars for "/o00" + + // Check directory exists + if(!RaidFileRead::DirectoryExists(mDiscSetNumber, dirName)) + { + BOX_WARNING("RaidFile dir " << dirName << " does not exist"); + return; + } + + // Read directory contents + std::vector<std::string> files; + RaidFileRead::ReadDirectoryContents(mDiscSetNumber, dirName, + RaidFileRead::DirReadType_FilesOnly, files); + + // Array of things present + bool idsPresent[(1<<STORE_ID_SEGMENT_LENGTH)]; + for(int l = 0; l < (1<<STORE_ID_SEGMENT_LENGTH); ++l) + { + idsPresent[l] = false; + } + + // Parse each entry, building up a list of object IDs which are present in the dir. + // This is done so that whatever order is retured from the directory, objects are scanned + // in order. + // Filename must begin with a 'o' and be three characters long, otherwise it gets deleted. + for(std::vector<std::string>::const_iterator i(files.begin()); i != files.end(); ++i) + { + bool fileOK = true; + int n = 0; + if((*i).size() == 3 && (*i)[0] == 'o' && TwoDigitHexToInt((*i).c_str() + 1, n) + && n < (1<<STORE_ID_SEGMENT_LENGTH)) + { + // Filename is valid, mark as existing + idsPresent[n] = true; + } + // 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" || + *i == "refcount.rdb" || *i == "refcount.rdbX") + { + fileOK = true; + } + else + { + fileOK = false; + } + + if(!fileOK) + { + // Unexpected or bad file, delete it + BOX_ERROR("Spurious file " << dirName << + DIRECTORY_SEPARATOR << (*i) << " found" << + (mFixErrors?", deleting":"")); + ++mNumberErrorsFound; + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, dirName + DIRECTORY_SEPARATOR + *i); + del.Delete(); + } + } + } + + // Check all the objects found in this directory + for(int i = 0; i < (1<<STORE_ID_SEGMENT_LENGTH); ++i) + { + if(idsPresent[i]) + { + // Check the object is OK, and add entry + char leaf[8]; + ::snprintf(leaf, sizeof(leaf), + DIRECTORY_SEPARATOR "o%02x", i); + if(!CheckAndAddObject(StartID | i, dirName + leaf)) + { + // File was bad, delete it + BOX_ERROR("Corrupted file " << dirName << + leaf << " found" << + (mFixErrors?", deleting":"")); + ++mNumberErrorsFound; + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, dirName + leaf); + del.Delete(); + } + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// 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) +{ + // Info on object... + bool isFile = true; + int64_t containerID = -1; + int64_t size = -1; + + try + { + // Open file + 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 + uint32_t signature; + if(file->Read(&signature, sizeof(signature)) != sizeof(signature)) + { + // Too short, can't read signature from it + return false; + } + // Seek back to beginning + file->Seek(0, IOStream::SeekType_Absolute); + + // Then... check depending on the type + switch(ntohl(signature)) + { + case OBJECTMAGIC_FILE_MAGIC_VALUE_V1: +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + case OBJECTMAGIC_FILE_MAGIC_VALUE_V0: +#endif + // File... check + containerID = CheckFile(ObjectID, *file); + break; + + case OBJECTMAGIC_DIR_MAGIC_VALUE: + isFile = false; + containerID = CheckDirInitial(ObjectID, *file); + break; + + default: + // Unknown signature. Bad file. Very bad file. + return false; + break; + } + } + catch(...) + { + // Error caught, not a good file then, let it be deleted + return false; + } + + // Got a container ID? (ie check was successful) + if(containerID == -1) + { + return false; + } + + // 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 */); + } + } + } + + // Report success + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckFile(int64_t, IOStream &) +// Purpose: Do check on file, return original container ID +// if OK, or -1 on error +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckFile(int64_t ObjectID, IOStream &rStream) +{ + // Check that it's not the root directory ID. Having a file as + // the root directory would be bad. + if(ObjectID == BACKUPSTORE_ROOT_DIRECTORY_ID) + { + // Get that dodgy thing deleted! + BOX_ERROR("Have file as root directory. This is bad."); + return -1; + } + + // Check the format of the file, and obtain the container ID + int64_t originalContainerID = -1; + if(!BackupStoreFile::VerifyEncodedFileFormat(rStream, + 0 /* don't want diffing from ID */, + &originalContainerID)) + { + // Didn't verify + return -1; + } + + return originalContainerID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckDirInitial(int64_t, IOStream &) +// Purpose: Do initial check on directory, return container ID +// if OK, or -1 on error +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::CheckDirInitial(int64_t ObjectID, IOStream &rStream) +{ + // Simply attempt to read in the directory + BackupStoreDirectory dir; + dir.ReadFromStream(rStream, IOStream::TimeOutInfinite); + + // Check object ID + if(dir.GetObjectID() != ObjectID) + { + // Wrong object ID + return -1; + } + + // Return container ID + return dir.GetContainerID(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckDirectories() +// Purpose: Check the directories +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckDirectories() +{ + // Phase 1 did this: + // Checked that all the directories are readable + // Built a list of all directories and files which exist on the store + // + // This phase will check all the files in the directories, make + // a note of all directories which are missing, and do initial fixing. + + // 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. + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + if(flags & Flags_IsDir) + { + // Found a directory. Read it in. + std::string filename; + StoreStructure::MakeObjectFilename(pblock->mID[e], mStoreRoot, mDiscSetNumber, filename, false /* no dir creation */); + BackupStoreDirectory dir; + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Flag for modifications + 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 */); + } + + CountDirectoryEntries(dir); + } + } + } +} + +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 every entry 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; +} + +// Count valid remaining entries and the number of blocks in them. +void BackupStoreCheck::CountDirectoryEntries(BackupStoreDirectory& dir) +{ + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + int32_t iIndex; + IDBlock *piBlock = LookupID(en->GetObjectID(), iIndex); + bool badEntry = false; + bool wasAlreadyContained = false; + + 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); + wasAlreadyContained = (iflags & Flags_IsContained); + SetFlags(piBlock, iIndex, iflags | Flags_IsContained); + } + + if(wasAlreadyContained) + { + // don't double-count objects that are + // contained by another directory as well. + } + else 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 file + { + // Add to sizes? + // If piBlock was zero, then wasAlreadyContained + // might be uninitialized; but we only process + // files here, and if a file's piBlock was zero + // then badEntry would be set above, so we + // wouldn't be here. + ASSERT(!badEntry) + + // It can be both old and deleted. + // If neither, then it's current. + if(en->IsDeleted()) + { + mNumDeletedFiles++; + mBlocksInDeletedFiles += en->GetSizeInBlocks(); + } + + if(en->IsOld()) + { + mNumOldFiles++; + mBlocksInOldFiles += en->GetSizeInBlocks(); + } + + if(!en->IsDeleted() && !en->IsOld()) + { + mNumCurrentFiles++; + mBlocksInCurrentFiles += en->GetSizeInBlocks(); + } + } + + mapNewRefs->AddReference(en->GetObjectID()); + } +} + +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); + + // 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."); + ++mNumberErrorsFound; + return false; // remove this entry + } + + // Check that the entry is not already contained. + if(iflags & Flags_IsContained) + { + BOX_ERROR("Directory ID " << + BOX_FORMAT_OBJECTID(DirectoryID) << + " references object " << + BOX_FORMAT_OBJECTID(rEntry.GetObjectID()) << + " which is already contained."); + ++mNumberErrorsFound; + return false; // remove this entry + } + + // 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_INFO("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(rEntry.GetSizeInBlocks() != piBlock->mObjectSizeInBlocks[IndexInDirBlock]) + { + // Wrong size, correct it. + BOX_ERROR("Directory " << BOX_FORMAT_OBJECTID(DirectoryID) << + " entry for " << BOX_FORMAT_OBJECTID(rEntry.GetObjectID()) << + " has wrong size " << rEntry.GetSizeInBlocks() << + ", should be " << piBlock->mObjectSizeInBlocks[IndexInDirBlock]); + + rEntry.SetSizeInBlocks(piBlock->mObjectSizeInBlocks[IndexInDirBlock]); + + // Mark as changed + rIsModified = true; + ++mNumberErrorsFound; + } + + return true; // don't delete this entry +} diff --git a/lib/backupstore/BackupStoreCheck.h b/lib/backupstore/BackupStoreCheck.h new file mode 100644 index 00000000..5353c968 --- /dev/null +++ b/lib/backupstore/BackupStoreCheck.h @@ -0,0 +1,221 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck.h +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTORECHECK__H +#define BACKUPSTORECHECK__H + +#include <string> +#include <map> +#include <vector> +#include <set> + +#include "NamedLock.h" +#include "BackupStoreDirectory.h" + +class IOStream; +class BackupStoreFilename; +class BackupStoreRefCountDatabase; + +/* + +The following problems can be fixed: + + * Spurious files deleted + * Corrupted files deleted + * Root ID as file, deleted + * Dirs with wrong object id in header, deleted + * Doubly referenced files have second reference deleted + * Wrong directory container IDs fixed + * Missing root recreated + * Reattach files which exist, but aren't referenced + - files go into directory original directory, if it still exists + - missing directories are inferred, and recreated + - or if all else fails, go into lost+found + - file dir entries take the original name and mod time + - directories go into lost+found + * Container IDs on directories corrected + * Inside directories, + - only one object per name has old version clear + - IDs aren't duplicated + - entries pointing to non-existant files are deleted + - patches depending on non-existent objects are deleted + * Bad store info and refcount files regenerated + * Bad sizes of files in directories fixed + +*/ + + +// Size of blocks in the list of IDs +#ifdef BOX_RELEASE_BUILD + #define BACKUPSTORECHECK_BLOCK_SIZE (64*1024) +#else + #define BACKUPSTORECHECK_BLOCK_SIZE 8 +#endif + +// The object ID type -- can redefine to uint32_t to produce a lower memory version for smaller stores +typedef int64_t BackupStoreCheck_ID_t; +// Can redefine the size type for lower memory usage too +typedef int64_t BackupStoreCheck_Size_t; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreCheck +// Purpose: Check a store for consistency +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +class BackupStoreCheck +{ +public: + BackupStoreCheck(const std::string &rStoreRoot, int DiscSetNumber, int32_t AccountID, bool FixErrors, bool Quiet); + ~BackupStoreCheck(); +private: + // no copying + BackupStoreCheck(const BackupStoreCheck &); + BackupStoreCheck &operator=(const BackupStoreCheck &); +public: + + // Do the exciting things + void Check(); + + bool ErrorsFound() {return mNumberErrorsFound > 0;} + inline int64_t GetNumErrorsFound() + { + return mNumberErrorsFound; + } + +private: + enum + { + // Bit mask + Flags_IsDir = 1, + Flags_IsContained = 2, + // Mask + Flags__MASK = 3, + // Number of bits + Flags__NumFlags = 2, + // Items per uint8_t + Flags__NumItemsPerEntry = 4 // ie 8 / 2 + }; + + typedef struct + { + // Note use arrays within the block, rather than the more obvious array of + // objects, to be more memory efficient -- think alignment of the byte values. + uint8_t mFlags[BACKUPSTORECHECK_BLOCK_SIZE * Flags__NumFlags / Flags__NumItemsPerEntry]; + BackupStoreCheck_ID_t mID[BACKUPSTORECHECK_BLOCK_SIZE]; + BackupStoreCheck_ID_t mContainer[BACKUPSTORECHECK_BLOCK_SIZE]; + BackupStoreCheck_Size_t mObjectSizeInBlocks[BACKUPSTORECHECK_BLOCK_SIZE]; + } IDBlock; + + // Phases of the check + void CheckObjects(); + void CheckDirectories(); + void CheckRoot(); + void CheckUnattachedObjects(); + void FixDirsWithWrongContainerID(); + void FixDirsWithLostDirs(); + void WriteNewStoreInfo(); + + // Checking functions + 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); + bool CheckDirectory(BackupStoreDirectory& dir); + bool CheckDirectoryEntry(BackupStoreDirectory::Entry& rEntry, + int64_t DirectoryID, bool& rIsModified); + void CountDirectoryEntries(BackupStoreDirectory& dir); + int64_t CheckFile(int64_t ObjectID, IOStream &rStream); + int64_t CheckDirInitial(int64_t ObjectID, IOStream &rStream); + + // Fixing functions + bool TryToRecreateDirectory(int64_t MissingDirectoryID); + void InsertObjectIntoDirectory(int64_t ObjectID, int64_t DirectoryID, bool IsDirectory); + int64_t GetLostAndFoundDirID(); + void CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID); + + // Data handling + void FreeInfo(); + void AddID(BackupStoreCheck_ID_t ID, BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile); + IDBlock *LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut); + inline void SetFlags(IDBlock *pBlock, int32_t Index, uint8_t Flags) + { + ASSERT(pBlock != 0); + ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); + ASSERT(Flags < (1 << Flags__NumFlags)); + + pBlock->mFlags[Index / Flags__NumItemsPerEntry] + |= (Flags << ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)); + } + inline uint8_t GetFlags(IDBlock *pBlock, int32_t Index) + { + ASSERT(pBlock != 0); + ASSERT(Index < BACKUPSTORECHECK_BLOCK_SIZE); + + return (pBlock->mFlags[Index / Flags__NumItemsPerEntry] >> ((Index % Flags__NumItemsPerEntry) * Flags__NumFlags)) & Flags__MASK; + } + +#ifndef BOX_RELEASE_BUILD + void DumpObjectInfo(); + #define DUMP_OBJECT_INFO DumpObjectInfo(); +#else + #define DUMP_OBJECT_INFO +#endif + +private: + std::string mStoreRoot; + int mDiscSetNumber; + int32_t mAccountID; + std::string mAccountName; + bool mFixErrors; + bool mQuiet; + + int64_t mNumberErrorsFound; + + // Lock for the store account + NamedLock mAccountLock; + + // Storage for ID data + typedef std::map<BackupStoreCheck_ID_t, IDBlock*> Info_t; + Info_t mInfo; + BackupStoreCheck_ID_t mLastIDInInfo; + IDBlock *mpInfoLastBlock; + int32_t mInfoLastBlockEntries; + + // List of stuff to fix + std::vector<BackupStoreCheck_ID_t> mDirsWithWrongContainerID; + // This is a map of lost dir ID -> existing dir ID + std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t> + mDirsWhichContainLostDirs; + + // Set of extra directories added + std::set<BackupStoreCheck_ID_t> mDirsAdded; + + // The refcount database, being reconstructed as the check/fix progresses + std::auto_ptr<BackupStoreRefCountDatabase> mapNewRefs; + + // Misc stuff + int32_t mLostDirNameSerial; + int64_t mLostAndFoundDirectoryID; + + // Usage + int64_t mBlocksUsed; + int64_t mBlocksInCurrentFiles; + int64_t mBlocksInOldFiles; + int64_t mBlocksInDeletedFiles; + int64_t mBlocksInDirectories; + int64_t mNumCurrentFiles; + int64_t mNumOldFiles; + int64_t mNumDeletedFiles; + int64_t mNumDirectories; +}; + +#endif // BACKUPSTORECHECK__H + diff --git a/lib/backupstore/BackupStoreCheck2.cpp b/lib/backupstore/BackupStoreCheck2.cpp new file mode 100644 index 00000000..13831a09 --- /dev/null +++ b/lib/backupstore/BackupStoreCheck2.cpp @@ -0,0 +1,970 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheck2.cpp +// Purpose: More backup store checking +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.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 "BackupStoreRefCountDatabase.h" +#include "MemBlockStream.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "StoreStructure.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckRoot() +// Purpose: Check the root directory exists. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckRoot() +{ + int32_t index = 0; + IDBlock *pblock = LookupID(BACKUPSTORE_ROOT_DIRECTORY_ID, index); + + if(pblock != 0) + { + // Found it. Which is lucky. Mark it as contained. + SetFlags(pblock, index, Flags_IsContained); + } + else + { + BOX_WARNING("Root directory doesn't exist"); + + ++mNumberErrorsFound; + + if(mFixErrors) + { + // Create a new root directory + CreateBlankDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, BACKUPSTORE_ROOT_DIRECTORY_ID); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CreateBlankDirectory(int64_t, int64_t) +// Purpose: Creates a blank directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CreateBlankDirectory(int64_t DirectoryID, int64_t ContainingDirID) +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + BackupStoreDirectory dir(DirectoryID, ContainingDirID); + + // Serialise to disc + std::string filename; + StoreStructure::MakeObjectFilename(DirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); + RaidFileWrite obj(mDiscSetNumber, filename); + obj.Open(false /* don't allow overwriting */); + dir.WriteToStream(obj); + int64_t size = obj.GetDiscUsageInBlocks(); + obj.Commit(true /* convert to raid now */); + + // Record the fact we've done this + mDirsAdded.insert(DirectoryID); + + // Add to sizes + mBlocksUsed += size; + mBlocksInDirectories += size; +} + +class BackupStoreDirectoryFixer +{ + private: + BackupStoreDirectory mDirectory; + std::string mFilename; + std::string mStoreRoot; + int mDiscSetNumber; + + public: + BackupStoreDirectoryFixer(std::string storeRoot, int discSetNumber, + int64_t ID); + void InsertObject(int64_t ObjectID, bool IsDirectory, + int32_t lostDirNameSerial); + ~BackupStoreDirectoryFixer(); +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::CheckUnattachedObjects() +// Purpose: Check for objects which aren't attached to anything +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::CheckUnattachedObjects() +{ + typedef std::map<int64_t, BackupStoreDirectoryFixer*> fixers_t; + typedef std::pair<int64_t, BackupStoreDirectoryFixer*> fixer_pair_t; + fixers_t fixers; + + // Scan all objects, finding ones which have no container + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + if((flags & Flags_IsContained) == 0) + { + // Unattached object... + int64_t ObjectID = pblock->mID[e]; + BOX_ERROR("Object " << + BOX_FORMAT_OBJECTID(ObjectID) << + " is unattached."); + ++mNumberErrorsFound; + + // What's to be done? + int64_t putIntoDirectoryID = 0; + + if((flags & Flags_IsDir) == Flags_IsDir) + { + // Directory. Just put into lost and found. + // (It doesn't contain its filename, so we + // can't recreate the entry in the parent) + putIntoDirectoryID = GetLostAndFoundDirID(); + } + else + { + // File. Only attempt to attach it somewhere if it isn't a patch + { + int64_t diffFromObjectID = 0; + std::string filename; + StoreStructure::MakeObjectFilename(ObjectID, + mStoreRoot, mDiscSetNumber, filename, + false /* don't attempt to make sure the dir exists */); + + // 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. + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + BackupStoreFile::VerifyEncodedFileFormat(*file, &diffFromObjectID); + } + + // If not zero, then it depends on another file, which may or may not be available. + // Just delete it to be safe. + if(diffFromObjectID != 0) + { + BOX_WARNING("Object " << BOX_FORMAT_OBJECTID(ObjectID) << " is unattached, and is a patch. Deleting, cannot reliably recover."); + + // Delete this object instead + if(mFixErrors) + { + RaidFileWrite del(mDiscSetNumber, filename); + del.Delete(); + } + + mBlocksUsed -= pblock->mObjectSizeInBlocks[e]; + + // Move on to next item + continue; + } + } + + // Files contain their original filename, so perhaps the orginal directory still exists, + // or we can infer the existance of a directory? + // Look for a matching entry in the mDirsWhichContainLostDirs map. + // Can't do this with a directory, because the name just wouldn't be known, which is + // pretty useless as bbackupd would just delete it. So better to put it in lost+found + // where the admin can do something about it. + int32_t dirindex; + IDBlock *pdirblock = LookupID(pblock->mContainer[e], dirindex); + if(pdirblock != 0) + { + // Something with that ID has been found. Is it a directory? + if(GetFlags(pdirblock, dirindex) & Flags_IsDir) + { + // Directory exists, add to that one + putIntoDirectoryID = pblock->mContainer[e]; + } + else + { + // Not a directory. Use lost and found dir + putIntoDirectoryID = GetLostAndFoundDirID(); + } + } + else if(mDirsAdded.find(pblock->mContainer[e]) != mDirsAdded.end() + || TryToRecreateDirectory(pblock->mContainer[e])) + { + // The directory reappeared, or was created somehow elsewhere + putIntoDirectoryID = pblock->mContainer[e]; + } + else + { + putIntoDirectoryID = GetLostAndFoundDirID(); + } + } + ASSERT(putIntoDirectoryID != 0); + + if (!mFixErrors) + { + continue; + } + + BackupStoreDirectoryFixer* pFixer; + fixers_t::iterator fi = + fixers.find(putIntoDirectoryID); + if (fi == fixers.end()) + { + // no match, create a new one + pFixer = new BackupStoreDirectoryFixer( + mStoreRoot, mDiscSetNumber, + putIntoDirectoryID); + fixers.insert(fixer_pair_t( + putIntoDirectoryID, pFixer)); + } + else + { + pFixer = fi->second; + } + + int32_t lostDirNameSerial = 0; + + if(flags & Flags_IsDir) + { + lostDirNameSerial = mLostDirNameSerial++; + } + + // Add it to the directory + pFixer->InsertObject(ObjectID, + ((flags & Flags_IsDir) == Flags_IsDir), + lostDirNameSerial); + mapNewRefs->AddReference(ObjectID); + } + } + } + + // clean up all the fixers. Deleting them commits them automatically. + for (fixers_t::iterator i = fixers.begin(); i != fixers.end(); i++) + { + BackupStoreDirectoryFixer* pFixer = i->second; + delete pFixer; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::TryToRecreateDirectory(int64_t) +// Purpose: Recreate a missing directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreCheck::TryToRecreateDirectory(int64_t MissingDirectoryID) +{ + // During the directory checking phase, a map of "missing directory" to + // containing directory was built. If we can find it here, then it's + // something which can be recreated! + std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t>::iterator missing( + mDirsWhichContainLostDirs.find(MissingDirectoryID)); + if(missing == mDirsWhichContainLostDirs.end()) + { + // Not a missing directory, can't recreate. + return false; + } + + // Can recreate this! Wooo! + if(!mFixErrors) + { + BOX_WARNING("Missing directory " << + BOX_FORMAT_OBJECTID(MissingDirectoryID) << + " could be recreated."); + mDirsAdded.insert(MissingDirectoryID); + return true; + } + + BOX_WARNING("Recreating missing directory " << + BOX_FORMAT_OBJECTID(MissingDirectoryID)); + + // Create a blank directory + BackupStoreDirectory dir(MissingDirectoryID, missing->second /* containing dir ID */); + // Note that this directory already contains a directory entry pointing to + // this dir, so it doesn't have to be added. + + // Serialise to disc + std::string filename; + StoreStructure::MakeObjectFilename(MissingDirectoryID, mStoreRoot, mDiscSetNumber, filename, true /* make sure the dir exists */); + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(false /* don't allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + + // Record the fact we've done this + mDirsAdded.insert(MissingDirectoryID); + + // Remove the entry from the map, so this doesn't happen again + mDirsWhichContainLostDirs.erase(missing); + + return true; +} + +BackupStoreDirectoryFixer::BackupStoreDirectoryFixer(std::string storeRoot, + int discSetNumber, int64_t ID) +: mStoreRoot(storeRoot), + mDiscSetNumber(discSetNumber) +{ + // Generate filename + StoreStructure::MakeObjectFilename(ID, mStoreRoot, mDiscSetNumber, + mFilename, false /* don't make sure the dir exists */); + + // Read it in + std::auto_ptr<RaidFileRead> file( + RaidFileRead::Open(mDiscSetNumber, mFilename)); + mDirectory.ReadFromStream(*file, IOStream::TimeOutInfinite); +} + +void BackupStoreDirectoryFixer::InsertObject(int64_t ObjectID, bool IsDirectory, + int32_t lostDirNameSerial) +{ + // Data for the object + BackupStoreFilename objectStoreFilename; + int64_t modTime = 100; // something which isn't zero or a special time + int32_t sizeInBlocks = 0; // suitable for directories + + if(IsDirectory) + { + // Directory -- simply generate a name for it. + char name[32]; + ::snprintf(name, sizeof(name), "dir%08x", lostDirNameSerial); + objectStoreFilename.SetAsClearFilename(name); + } + else + { + // Files require a little more work... + // Open file + std::string fileFilename; + StoreStructure::MakeObjectFilename(ObjectID, mStoreRoot, + mDiscSetNumber, fileFilename, + false /* don't make sure the dir exists */); + std::auto_ptr<RaidFileRead> file( + RaidFileRead::Open(mDiscSetNumber, fileFilename)); + + // Fill in size information + sizeInBlocks = file->GetDiscUsageInBlocks(); + + // Read in header + file_StreamFormat hdr; + if(file->Read(&hdr, sizeof(hdr)) != sizeof(hdr) || + (ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V1 +#ifndef BOX_DISABLE_BACKWARDS_COMPATIBILITY_BACKUPSTOREFILE + && ntohl(hdr.mMagicValue) != OBJECTMAGIC_FILE_MAGIC_VALUE_V0 +#endif + )) + { + // This should never happen, everything has been + // checked before. + THROW_EXCEPTION(BackupStoreException, Internal) + } + // This tells us nice things + modTime = box_ntoh64(hdr.mModificationTime); + // And the filename comes next + objectStoreFilename.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Add a new entry in an appropriate place + mDirectory.AddUnattachedObject(objectStoreFilename, modTime, + ObjectID, sizeInBlocks, + IsDirectory?(BackupStoreDirectory::Entry::Flags_Dir):(BackupStoreDirectory::Entry::Flags_File)); +} + +BackupStoreDirectoryFixer::~BackupStoreDirectoryFixer() +{ + // Fix any flags which have been broken, which there's a good chance of doing + mDirectory.CheckAndFix(); + + // Write it out + RaidFileWrite root(mDiscSetNumber, mFilename); + root.Open(true /* allow overwriting */); + mDirectory.WriteToStream(root); + root.Commit(true /* convert to raid now */); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::GetLostAndFoundDirID() +// Purpose: Returns the ID of the lost and found directory, creating it if necessary +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreCheck::GetLostAndFoundDirID() +{ + // Already allocated it? + if(mLostAndFoundDirectoryID != 0) + { + return mLostAndFoundDirectoryID; + } + + if(!mFixErrors) + { + // The result will never be used anyway if errors aren't being fixed + return 1; + } + + // Load up the root directory + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(BACKUPSTORE_ROOT_DIRECTORY_ID, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Find a suitable name + BackupStoreFilename lostAndFound; + int n = 0; + while(true) + { + char name[32]; + ::snprintf(name, sizeof(name), "lost+found%d", n++); + lostAndFound.SetAsClearFilename(name); + if(!dir.NameInUse(lostAndFound)) + { + // Found a name which can be used + BOX_WARNING("Lost and found dir has name " << name); + break; + } + } + + // Allocate an ID + int64_t id = mLastIDInInfo + 1; + + // Create a blank directory + CreateBlankDirectory(id, BACKUPSTORE_ROOT_DIRECTORY_ID); + + // Add an entry for it + dir.AddEntry(lostAndFound, 0, id, 0, BackupStoreDirectory::Entry::Flags_Dir, 0); + + // Write out root dir + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + + // Store + mLostAndFoundDirectoryID = id; + + // Tell caller + return mLostAndFoundDirectoryID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FixDirsWithWrongContainerID() +// Purpose: Rewrites container IDs where required +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FixDirsWithWrongContainerID() +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + // Run through things which need fixing + for(std::vector<BackupStoreCheck_ID_t>::iterator i(mDirsWithWrongContainerID.begin()); + i != mDirsWithWrongContainerID.end(); ++i) + { + int32_t index = 0; + IDBlock *pblock = LookupID(*i, index); + if(pblock == 0) continue; + + // Load in + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(*i, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Adjust container ID + dir.SetContainerID(pblock->mContainer[index]); + + // Write it out + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FixDirsWithLostDirs() +// Purpose: Fix directories +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FixDirsWithLostDirs() +{ + if(!mFixErrors) + { + // Don't do anything if we're not supposed to fix errors + return; + } + + // Run through things which need fixing + for(std::map<BackupStoreCheck_ID_t, BackupStoreCheck_ID_t>::iterator i(mDirsWhichContainLostDirs.begin()); + i != mDirsWhichContainLostDirs.end(); ++i) + { + int32_t index = 0; + IDBlock *pblock = LookupID(i->second, index); + if(pblock == 0) continue; + + // Load in + BackupStoreDirectory dir; + std::string filename; + StoreStructure::MakeObjectFilename(i->second, mStoreRoot, mDiscSetNumber, filename, false /* don't make sure the dir exists */); + { + std::auto_ptr<RaidFileRead> file(RaidFileRead::Open(mDiscSetNumber, filename)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + } + + // Delete the dodgy entry + dir.DeleteEntry(i->first); + + // Fix it up + dir.CheckAndFix(); + + // Write it out + RaidFileWrite root(mDiscSetNumber, filename); + root.Open(true /* allow overwriting */); + dir.WriteToStream(root); + root.Commit(true /* convert to raid now */); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::WriteNewStoreInfo() +// Purpose: Regenerate store info +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::WriteNewStoreInfo() +{ + // Attempt to load the existing store info file + 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_INFO("Current files: " << mNumCurrentFiles << ", " + "old files: " << mNumOldFiles << ", " + "deleted files: " << mNumDeletedFiles << ", " + "directories: " << mNumDirectories); + + // Minimum soft and hard limits to ensure that nothing gets deleted + // by housekeeping. + int64_t minSoft = ((mBlocksUsed * 11) / 10) + 1024; + int64_t minHard = ((minSoft * 11) / 10) + 1024; + + int64_t softLimit = pOldInfo.get() ? pOldInfo->GetBlocksSoftLimit() : minSoft; + int64_t hardLimit = pOldInfo.get() ? pOldInfo->GetBlocksHardLimit() : minHard; + + if(mNumberErrorsFound && pOldInfo.get()) + { + if(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->GetBlocksHardLimit() > minHard) + { + hardLimit = pOldInfo->GetBlocksHardLimit(); + } + else + { + BOX_WARNING("Hard limit for account changed to ensure " + "housekeeping doesn't delete files on next run."); + } + } + + // Object ID + int64_t lastObjID = mLastIDInInfo; + if(mLostAndFoundDirectoryID != 0) + { + mLastIDInInfo++; + } + + // Build a new store info + 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, + mStoreRoot, + mDiscSetNumber, + lastObjID, + mBlocksUsed, + mBlocksInCurrentFiles, + mBlocksInOldFiles, + mBlocksInDeletedFiles, + mBlocksInDirectories, + softLimit, + hardLimit, + (pOldInfo.get() ? pOldInfo->IsAccountEnabled() : true), + *extra_data)); + info->AdjustNumCurrentFiles(mNumCurrentFiles); + info->AdjustNumOldFiles(mNumOldFiles); + info->AdjustNumDeletedFiles(mNumDeletedFiles); + info->AdjustNumDirectories(mNumDirectories); + + // If there are any errors (apart from wrong block counts), then we + // should reset the ClientStoreMarker to zero, which + // CreateForRegeneration does. But if there are no major errors, then + // we should maintain the old ClientStoreMarker, to avoid invalidating + // the client's directory cache. + if (pOldInfo.get() && !mNumberErrorsFound) + { + BOX_INFO("No major errors found, preserving old " + "ClientStoreMarker: " << + pOldInfo->GetClientStoreMarker()); + info->SetClientStoreMarker(pOldInfo->GetClientStoreMarker()); + } + + if(pOldInfo.get()) + { + mNumberErrorsFound += info->ReportChangesTo(*pOldInfo); + } + + // Save to disc? + if(mFixErrors) + { + info->Save(); + BOX_INFO("New store info file written successfully."); + } +} + +#define FMT_OID(x) BOX_FORMAT_OBJECTID(x) +#define FMT_i BOX_FORMAT_OBJECTID((*i)->GetObjectID()) + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::CheckAndFix() +// Purpose: Check the directory for obvious logical problems, and fix them. +// Return true if the directory was changed. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreDirectory::CheckAndFix() +{ + bool changed = false; + + // Check that if a file depends on a new version, that version is in this directory + bool restart; + + do + { + restart = false; + + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + int64_t dependsNewer = (*i)->GetDependsNewer(); + if(dependsNewer != 0) + { + BackupStoreDirectory::Entry *newerEn = FindEntryByID(dependsNewer); + if(newerEn == 0) + { + // Depends on something, but it isn't there. + BOX_WARNING("Entry id " << FMT_i << + " removed because depends " + "on newer version " << + FMT_OID(dependsNewer) << + " which doesn't exist"); + + // Remove + delete *i; + mEntries.erase(i); + + // Mark as changed + changed = true; + + // Start again at the beginning of the vector, the iterator is now invalid + restart = true; + break; + } + else + { + // Check that newerEn has it marked + if(newerEn->GetDependsOlder() != (*i)->GetObjectID()) + { + // Wrong entry + BOX_TRACE("Entry id " << + FMT_OID(dependsNewer) << + ", correcting DependsOlder to " << + FMT_i << + ", was " << + FMT_OID(newerEn->GetDependsOlder())); + newerEn->SetDependsOlder((*i)->GetObjectID()); + // Mark as changed + changed = true; + } + } + } + } + } + while(restart); + + // Check that if a file has a dependency marked, it exists, and remove it if it doesn't + { + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + int64_t dependsOlder = (*i)->GetDependsOlder(); + if(dependsOlder != 0 && FindEntryByID(dependsOlder) == 0) + { + // Has an older version marked, but this doesn't exist. Remove this mark + BOX_TRACE("Entry id " << FMT_i << + " was marked as depended on by " << + FMT_OID(dependsOlder) << ", " + "which doesn't exist, dependency " + "info cleared"); + + (*i)->SetDependsOlder(0); + + // Mark as changed + changed = true; + } + } + } + + bool ch = false; + do + { + // Reset change marker + ch = false; + + // Search backwards -- so see newer versions first + std::vector<Entry*>::iterator i(mEntries.end()); + if(i == mEntries.begin()) + { + // Directory is empty, stop now + return changed; // changed flag + } + + // Records of things seen + std::set<int64_t> idsEncountered; + std::set<std::string> filenamesEncountered; + + do + { + // Look at previous + --i; + + bool removeEntry = false; + if((*i) == 0) + { + BOX_TRACE("Remove because null pointer found"); + removeEntry = true; + } + else + { + // Check mutually exclusive flags + if((*i)->IsDir() && (*i)->IsFile()) + { + // Bad! Unset the file flag + BOX_TRACE("Entry " << FMT_i << + ": File flag and dir flag both set"); + (*i)->RemoveFlags(Entry::Flags_File); + changed = true; + } + + // Check... + if(idsEncountered.find((*i)->GetObjectID()) != idsEncountered.end()) + { + // ID already seen, or type doesn't match + BOX_TRACE("Entry " << FMT_i << + ": Remove because ID already seen"); + removeEntry = true; + } + else + { + // Haven't already seen this ID, remember it + idsEncountered.insert((*i)->GetObjectID()); + + // Check to see if the name has already been encountered -- if not, then it + // needs to have the old version flag set + if(filenamesEncountered.find((*i)->GetName().GetEncodedFilename()) != filenamesEncountered.end()) + { + // Seen before -- check old version flag set + if(((*i)->GetFlags() & Entry::Flags_OldVersion) != Entry::Flags_OldVersion + && ((*i)->GetFlags() & Entry::Flags_Deleted) == 0) + { + // Not set, set it + BOX_TRACE("Entry " << FMT_i << + ": Set old flag"); + (*i)->AddFlags(Entry::Flags_OldVersion); + changed = true; + } + } + else + { + // Check old version flag NOT set + if(((*i)->GetFlags() & Entry::Flags_OldVersion) == Entry::Flags_OldVersion) + { + // Set, unset it + BOX_TRACE("Entry " << FMT_i << + ": Old flag unset"); + (*i)->RemoveFlags(Entry::Flags_OldVersion); + changed = true; + } + + // Remember filename + filenamesEncountered.insert((*i)->GetName().GetEncodedFilename()); + } + } + } + + if(removeEntry) + { + // Mark something as changed, in loop + ch = true; + + // Mark something as globally changed + changed = true; + + // erase the thing from the list + Entry *pentry = (*i); + mEntries.erase(i); + + // And delete the entry object + delete pentry; + + // Stop going around this loop, as the iterator is now invalid + break; + } + } while(i != mEntries.begin()); + + } while(ch != false); + + return changed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::AddUnattachedObject(...) +// Purpose: Adds an object which is currently unattached. Assume that CheckAndFix() will be called afterwards. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreDirectory::AddUnattachedObject(const BackupStoreFilename &rName, + box_time_t ModificationTime, int64_t ObjectID, int64_t SizeInBlocks, int16_t Flags) +{ + Entry *pnew = new Entry(rName, ModificationTime, ObjectID, SizeInBlocks, Flags, + ModificationTime /* use as attr mod time too */); + try + { + // Want to order this just before the first object which has a higher ID, + // which is the place it's most likely to be correct. + std::vector<Entry*>::iterator i(mEntries.begin()); + for(; i != mEntries.end(); ++i) + { + if((*i)->GetObjectID() > ObjectID) + { + // Found a good place to insert it + break; + } + } + if(i == mEntries.end()) + { + mEntries.push_back(pnew); + } + else + { + mEntries.insert(i, 1 /* just the one copy */, pnew); + } + } + catch(...) + { + delete pnew; + throw; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDirectory::NameInUse(const BackupStoreFilename &) +// Purpose: Returns true if the name is currently in use in the directory +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +bool BackupStoreDirectory::NameInUse(const BackupStoreFilename &rName) +{ + for(std::vector<Entry*>::iterator i(mEntries.begin()); i != mEntries.end(); ++i) + { + if((*i)->GetName() == rName) + { + return true; + } + } + + return false; +} + + diff --git a/lib/backupstore/BackupStoreCheckData.cpp b/lib/backupstore/BackupStoreCheckData.cpp new file mode 100644 index 00000000..ec606d52 --- /dev/null +++ b/lib/backupstore/BackupStoreCheckData.cpp @@ -0,0 +1,203 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreCheckData.cpp +// Purpose: Data handling for store checking +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <memory> + +#include "BackupStoreCheck.h" +#include "autogen_BackupStoreException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::FreeInfo() +// Purpose: Free all the data stored +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::FreeInfo() +{ + // Free all the blocks + for(Info_t::iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + ::free(i->second); + } + + // Clear the contents of the map + mInfo.clear(); + + // Reset the last ID, just in case + mpInfoLastBlock = 0; + mInfoLastBlockEntries = 0; + mLastIDInInfo = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::AddID(BackupStoreCheck_ID_t, BackupStoreCheck_ID_t, bool) +// Purpose: Add an ID to the list +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::AddID(BackupStoreCheck_ID_t ID, + BackupStoreCheck_ID_t Container, BackupStoreCheck_Size_t ObjectSize, bool IsFile) +{ + // Check ID is OK. + if(ID <= mLastIDInInfo) + { + THROW_EXCEPTION(BackupStoreException, InternalAlgorithmErrorCheckIDNotMonotonicallyIncreasing) + } + + // Can this go in the current block? + if(mpInfoLastBlock == 0 || mInfoLastBlockEntries >= BACKUPSTORECHECK_BLOCK_SIZE) + { + // No. Allocate a new one + IDBlock *pblk = (IDBlock*)calloc(1, sizeof(IDBlock)); + if(pblk == 0) + { + throw std::bad_alloc(); + } + // Store in map + mInfo[ID] = pblk; + // Allocated and stored OK, setup for use + mpInfoLastBlock = pblk; + mInfoLastBlockEntries = 0; + } + ASSERT(mpInfoLastBlock != 0 && mInfoLastBlockEntries < BACKUPSTORECHECK_BLOCK_SIZE); + + // Add to block + mpInfoLastBlock->mID[mInfoLastBlockEntries] = ID; + mpInfoLastBlock->mContainer[mInfoLastBlockEntries] = Container; + mpInfoLastBlock->mObjectSizeInBlocks[mInfoLastBlockEntries] = ObjectSize; + SetFlags(mpInfoLastBlock, mInfoLastBlockEntries, IsFile?(0):(Flags_IsDir)); + + // Increment size + ++mInfoLastBlockEntries; + + // Store last ID + mLastIDInInfo = ID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::LookupID(BackupStoreCheck_ID_t, int32_t +// Purpose: Look up an ID. Return the block it's in, or zero if not found, and the +// index within that block if the thing is found. +// Created: 21/4/04 +// +// -------------------------------------------------------------------------- +BackupStoreCheck::IDBlock *BackupStoreCheck::LookupID(BackupStoreCheck_ID_t ID, int32_t &rIndexOut) +{ + IDBlock *pblock = 0; + + // Find the lower matching block who's first entry is not less than ID + Info_t::const_iterator ib(mInfo.lower_bound(ID)); + + // Was there a block + if(ib == mInfo.end()) + { + // Block wasn't found... could be in last block + pblock = mpInfoLastBlock; + } + else + { + // Found it as first entry? + if(ib->first == ID) + { + rIndexOut = 0; + return ib->second; + } + + // Go back one block as it's not the first entry in this one + if(ib == mInfo.begin()) + { + // Was first block, can't go back + return 0; + } + // Go back... + --ib; + + // So, the ID will be in this block, if it's in anything + pblock = ib->second; + } + + if(pblock == 0) return 0; + ASSERT(pblock != 0); + + // How many entries are there in the block + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + + // Do binary search within block + int high = bentries; + int low = -1; + while(high - low > 1) + { + int i = (high + low) / 2; + if(ID <= pblock->mID[i]) + { + high = i; + } + else + { + low = i; + } + } + if(ID == pblock->mID[high]) + { + // Found + rIndexOut = high; + return pblock; + } + + // Not found + return 0; +} + + +#ifndef BOX_RELEASE_BUILD +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreCheck::DumpObjectInfo() +// Purpose: Debug only. Trace out all object info. +// Created: 22/4/04 +// +// -------------------------------------------------------------------------- +void BackupStoreCheck::DumpObjectInfo() +{ + for(Info_t::const_iterator i(mInfo.begin()); i != mInfo.end(); ++i) + { + IDBlock *pblock = i->second; + int32_t bentries = (pblock == mpInfoLastBlock)?mInfoLastBlockEntries:BACKUPSTORECHECK_BLOCK_SIZE; + BOX_TRACE("BLOCK @ " << BOX_FORMAT_HEX32(pblock) << + ", " << bentries << " entries"); + + for(int e = 0; e < bentries; ++e) + { + uint8_t flags = GetFlags(pblock, e); + BOX_TRACE(std::hex << + "id " << pblock->mID[e] << + ", c " << pblock->mContainer[e] << + ", " << ((flags & Flags_IsDir)?"dir":"file") << + ", " << ((flags & Flags_IsContained) ? + "contained":"unattached")); + } + } +} +#endif + diff --git a/lib/backupstore/BackupStoreConfigVerify.cpp b/lib/backupstore/BackupStoreConfigVerify.cpp new file mode 100644 index 00000000..921adfa4 --- /dev/null +++ b/lib/backupstore/BackupStoreConfigVerify.cpp @@ -0,0 +1,51 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreConfigVerify.h +// Purpose: Configuration definition for the backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BackupStoreConfigVerify.h" +#include "ServerTLS.h" +#include "BoxPortsAndFiles.h" + +#include "MemLeakFindOn.h" + +static const ConfigurationVerifyKey verifyserverkeys[] = +{ + SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) + // no default listen addresses +}; + +static const ConfigurationVerify verifyserver[] = +{ + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +static const ConfigurationVerifyKey verifyrootkeys[] = +{ + ConfigurationVerifyKey("AccountDatabase", ConfigTest_Exists), + ConfigurationVerifyKey("TimeBetweenHousekeeping", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("ExtendedLogging", ConfigTest_IsBool, false), + // make value "yes" to enable in config file + ConfigurationVerifyKey("RaidFileConf", ConfigTest_LastEntry) +}; + +const ConfigurationVerify BackupConfigFileVerify = +{ + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; diff --git a/lib/backupstore/BackupStoreConfigVerify.h b/lib/backupstore/BackupStoreConfigVerify.h new file mode 100644 index 00000000..815cfaed --- /dev/null +++ b/lib/backupstore/BackupStoreConfigVerify.h @@ -0,0 +1,18 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreConfigVerify.h +// Purpose: Configuration definition for the backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTORECONFIGVERIFY__H +#define BACKUPSTORECONFIGVERIFY__H + +#include "Configuration.h" + +extern const ConfigurationVerify BackupConfigFileVerify; + +#endif // BACKUPSTORECONFIGVERIFY__H + 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..1a782df4 --- /dev/null +++ b/lib/backupstore/BackupStoreContext.cpp @@ -0,0 +1,1945 @@ +// -------------------------------------------------------------------------- +// +// 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. In tests, we set the cache size to zero +// to ensure that it's always flushed, which is very inefficient but helps to +// catch programming errors (use of freed data). +#ifdef BOX_RELEASE_BUILD + #define MAX_CACHE_SIZE 32 +#else + #define MAX_CACHE_SIZE 0 +#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* pHousekeeping, const std::string& rConnectionDetails) +: mConnectionDetails(rConnectionDetails), + mClientID(ClientID), + mpHousekeeping(pHousekeeping), + mProtocolPhase(Phase_START), + mClientHasAccount(false), + mStoreDiscSet(-1), + mReadOnly(true), + mSaveStoreInfoDelay(STORE_INFO_SAVE_DELAY), + mpTestHook(NULL) +// If you change the initialisers, be sure to update +// BackupStoreContext::ReceivedFinishCommand as well! +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreContext::~BackupStoreContext() +// Purpose: Destructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreContext::~BackupStoreContext() +{ + ClearDirectoryCache(); +} + + +void BackupStoreContext::ClearDirectoryCache() +{ + // Delete the objects in the cache + for(std::map<int64_t, BackupStoreDirectory*>::iterator i(mDirectoryCache.begin()); + i != mDirectoryCache.end(); ++i) + { + delete (i->second); + } + mDirectoryCache.clear(); +} + + +// -------------------------------------------------------------------------- +// +// 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); + } + + // Just in case someone wants to reuse a local protocol object, + // put the context back to its initial state. + mProtocolPhase = BackupStoreContext::Phase_Version; + + // Avoid the need to check version again, by not resetting + // mClientHasAccount, mAccountRootDir or mStoreDiscSet + + mReadOnly = true; + mSaveStoreInfoDelay = STORE_INFO_SAVE_DELAY; + mpTestHook = NULL; + mapStoreInfo.reset(); + mapRefCount.reset(); + ClearDirectoryCache(); +} + + +// -------------------------------------------------------------------------- +// +// 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(mAccountRootDir, mStoreDiscSet, writeLockFile); + + // Request the lock + bool gotLock = mWriteLock.TryAndGetLock(writeLockFile.c_str(), 0600 /* restrictive file permissions */); + + if(!gotLock && mpHousekeeping) + { + // The housekeeping process might have the thing open -- ask it to stop + char msg[256]; + int msgLen = snprintf(msg, sizeof(msg), "r%x\n", mClientID); + // Send message + mpHousekeeping->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, mAccountRootDir, 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) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, + CorruptReferenceCountDatabase, "Reference count " + "database is missing or corrupted, cannot safely open " + "account. Housekeeping will fix this automatically " + "when it next runs."); + } +} + + +// -------------------------------------------------------------------------- +// +// 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, mAccountRootDir, mStoreDiscSet, rOutput, EnsureDirectoryExists); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreContext::GetDirectoryInternal(int64_t, +// bool) +// Purpose: Return a reference to a directory. Valid only until +// the next time a function which affects directories +// is called. Mainly this function, and creation of +// files. Private version of this, which returns +// non-const directories. Unless called with +// AllowFlushCache == false, the cache may be flushed, +// invalidating all directory references that you may +// be holding, so beware. +// Created: 2003/09/02 +// +// -------------------------------------------------------------------------- +BackupStoreDirectory &BackupStoreContext::GetDirectoryInternal(int64_t ObjectID, + bool AllowFlushCache) +{ + // Get the filename + std::string filename; + MakeObjectFilename(ObjectID, filename); + int64_t oldRevID = 0, newRevID = 0; + + // Already in cache? + std::map<int64_t, BackupStoreDirectory*>::iterator item(mDirectoryCache.find(ObjectID)); + if(item != mDirectoryCache.end()) { +#ifndef BOX_RELEASE_BUILD // it might be in the cache, but invalidated + // in which case, delete it instead of returning it. + if(!item->second->IsInvalidated()) +#else + if(true) +#endif + { + oldRevID = item->second->GetRevisionID(); + + // Check the revision ID of the file -- does it need refreshing? + if(!RaidFileRead::FileExists(mStoreDiscSet, filename, &newRevID)) + { + THROW_EXCEPTION(BackupStoreException, DirectoryHasBeenDeleted) + } + + if(newRevID == oldRevID) + { + // Looks good... return the cached object + BOX_TRACE("Returning object " << + BOX_FORMAT_OBJECTID(ObjectID) << + " from cache, modtime = " << newRevID) + return *(item->second); + } + } + + // 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 && AllowFlushCache) + { + // Very simple. Just delete everything! But in debug builds, + // leave the entries in the cache and invalidate them instead, + // so that any attempt to access them will cause an assertion + // failure that helps to track down the error. +#ifdef BOX_RELEASE_BUILD + ClearDirectoryCache(); +#else + for(std::map<int64_t, BackupStoreDirectory*>::iterator + i = mDirectoryCache.begin(); + i != mDirectoryCache.end(); i++) + { + i->second->Invalidate(); + } +#endif + } + + // Get a RaidFileRead to read it + std::auto_ptr<RaidFileRead> objectFile(RaidFileRead::Open(mStoreDiscSet, + filename, &newRevID)); + + ASSERT(newRevID != 0); + + if (oldRevID == 0) + { + BOX_TRACE("Loading object " << BOX_FORMAT_OBJECTID(ObjectID) << + " with modtime " << newRevID); + } + else + { + BOX_TRACE("Refreshing object " << BOX_FORMAT_OBJECTID(ObjectID) << + " in cache, modtime changed from " << oldRevID << + " to " << newRevID); + } + + // Read it from the stream, then set it's revision ID + BufferedStream buf(*objectFile); + std::auto_ptr<BackupStoreDirectory> dir(new BackupStoreDirectory(buf)); + dir->SetRevisionID(newRevID); + + // 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; + BackupStoreInfo::Adjustment adjustment = {}; + + try + { + RaidFileWrite storeFile(mStoreDiscSet, fn); + storeFile.Open(false /* no overwriting */); + + 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; + + adjustment.mBlocksUsed -= spaceSavedByConversionToPatch; + // The code below will change the patch from a + // Current file to an Old file, so we need to + // account for it as a Current file here. + adjustment.mBlocksInCurrentFiles -= + spaceSavedByConversionToPatch; + + // Don't adjust anything else here. We'll do it + // when we update the directory just below, + // which also accounts for non-diff replacements. + + // 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(); + adjustment.mBlocksUsed += newObjectBlocksUsed; + adjustment.mBlocksInCurrentFiles += newObjectBlocksUsed; + adjustment.mNumCurrentFiles++; + + // Exceeds the hard limit? + int64_t newTotalBlocksUsed = mapStoreInfo->GetBlocksUsed() + + adjustment.mBlocksUsed; + if(newTotalBlocksUsed > 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 + try + { + // Adjust the entry for the object that we replaced with a + // patch, above. + BackupStoreDirectory::Entry *poldEntry = NULL; + + if(DiffFromFileID != 0) + { + // Get old version entry + poldEntry = dir.FindEntryByID(DiffFromFileID); + ASSERT(poldEntry != 0); + + // Adjust size of old entry + int64_t oldSize = poldEntry->GetSizeInBlocks(); + poldEntry->SetSizeInBlocks(oldVersionNewBlocksUsed); + } + + 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 + adjustment.mBlocksInOldFiles += e->GetSizeInBlocks(); + adjustment.mBlocksInCurrentFiles -= e->GetSizeInBlocks(); + adjustment.mNumOldFiles++; + adjustment.mNumCurrentFiles--; + } + } + } + } + + // Then the new entry + BackupStoreDirectory::Entry *pnewEntry = dir.AddEntry(rFilename, + ModificationTime, id, newObjectBlocksUsed, + BackupStoreDirectory::Entry::Flags_File, + AttributesHash); + + // Adjust dependency info of file? + if(DiffFromFileID && poldEntry && !reversedDiffIsCompletelyDifferent) + { + poldEntry->SetDependsNewer(id); + pnewEntry->SetDependsOlder(DiffFromFileID); + } + + // Write the directory back to disc + SaveDirectory(dir); + + // 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 + mapStoreInfo->AdjustNumCurrentFiles(adjustment.mNumCurrentFiles); + mapStoreInfo->AdjustNumOldFiles(adjustment.mNumOldFiles); + mapStoreInfo->AdjustNumDeletedFiles(adjustment.mNumDeletedFiles); + mapStoreInfo->AdjustNumDirectories(adjustment.mNumDirectories); + mapStoreInfo->ChangeBlocksUsed(adjustment.mBlocksUsed); + mapStoreInfo->ChangeBlocksInCurrentFiles(adjustment.mBlocksInCurrentFiles); + mapStoreInfo->ChangeBlocksInOldFiles(adjustment.mBlocksInOldFiles); + mapStoreInfo->ChangeBlocksInDeletedFiles(adjustment.mBlocksInDeletedFiles); + mapStoreInfo->ChangeBlocksInDirectories(adjustment.mBlocksInDirectories); + + // 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 by name, 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 + + 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->IsDeleted()); + // Set deleted flag + e->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted); + // Mark as made a change + madeChanges = true; + + int64_t blocks = e->GetSizeInBlocks(); + mapStoreInfo->AdjustNumDeletedFiles(1); + mapStoreInfo->ChangeBlocksInDeletedFiles(blocks); + + // We're marking all old versions as deleted. + // This is how a file can be old and deleted + // at the same time. So we don't subtract from + // number or size of old files. But if it was + // a current file, then it's not any more, so + // we do need to adjust the current counts. + if(!e->IsOld()) + { + mapStoreInfo->AdjustNumCurrentFiles(-1); + mapStoreInfo->ChangeBlocksInCurrentFiles(-blocks); + } + + // 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); + 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); + + // 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 &) +// Purpose: Save directory back to disc, update time in cache +// Created: 2003/09/04 +// +// -------------------------------------------------------------------------- +void BackupStoreContext::SaveDirectory(BackupStoreDirectory &rDir) +{ + if(mapStoreInfo.get() == 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotLoaded) + } + + int64_t ObjectID = rDir.GetObjectID(); + + try + { + // Write to disc, adjust size in store info + std::string dirfn; + MakeObjectFilename(ObjectID, dirfn); + int64_t old_dir_size = rDir.GetUserInfo1_SizeInBlocks(); + + { + 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) + } + + BOX_TRACE("Saved directory " << + BOX_FORMAT_OBJECTID(ObjectID) << + ", modtime = " << revid); + + rDir.SetRevisionID(revid); + } + + // Update the directory entry in the grandparent, to ensure + // that it reflects the current size of the parent directory. + int64_t new_dir_size = rDir.GetUserInfo1_SizeInBlocks(); + if(new_dir_size != old_dir_size && + ObjectID != BACKUPSTORE_ROOT_DIRECTORY_ID) + { + int64_t ContainerID = rDir.GetContainerID(); + BackupStoreDirectory& parent( + GetDirectoryInternal(ContainerID)); + // rDir is now invalid + BackupStoreDirectory::Entry* en = + parent.FindEntryByID(ObjectID); + if(!en) + { + BOX_ERROR("Missing entry for directory " << + BOX_FORMAT_OBJECTID(ObjectID) << + " in directory " << + BOX_FORMAT_OBJECTID(ContainerID) << + " while trying to update dir size in parent"); + } + else + { + ASSERT(en->GetSizeInBlocks() == old_dir_size); + en->SetSizeInBlocks(new_dir_size); + SaveDirectory(parent); + } + } + } + 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, + int64_t ModificationTime, + 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 */); + int64_t dirSize; + + { + 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 + dirSize = dirFile.GetDiscUsageInBlocks(); + + // Exceeds the hard limit? + int64_t newTotalBlocksUsed = mapStoreInfo->GetBlocksUsed() + + dirSize; + if(newTotalBlocksUsed > mapStoreInfo->GetBlocksHardLimit()) + { + THROW_EXCEPTION(BackupStoreException, AddedFileExceedsStorageLimit) + // The file will be deleted automatically by the RaidFile object + } + + // 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, ModificationTime, id, dirSize, + BackupStoreDirectory::Entry::Flags_Dir, + 0 /* attributes hash */); + SaveDirectory(dir); + + // 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; + + 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, 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); + + // Done + break; + } + } + + // Update blocks deleted count + 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, 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, 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) + { + // Keep count of the deleted blocks + if(en->IsFile()) + { + int64_t size = en->GetSizeInBlocks(); + ASSERT(en->IsDeleted() == Undelete); + // Don't adjust counters for old files, + // because it can be both old and deleted. + if(!en->IsOld()) + { + mapStoreInfo->ChangeBlocksInCurrentFiles(Undelete ? size : -size); + mapStoreInfo->AdjustNumCurrentFiles(Undelete ? 1 : -1); + } + mapStoreInfo->ChangeBlocksInDeletedFiles(Undelete ? -size : size); + mapStoreInfo->AdjustNumDeletedFiles(Undelete ? -1 : 1); + } + + // Add/remove the deleted flags + if(Undelete) + { + en->RemoveFlags(BackupStoreDirectory::Entry::Flags_Deleted); + } + else + { + en->AddFlags(BackupStoreDirectory::Entry::Flags_Deleted); + } + + // Did something + changesMade = true; + } + + // Save the directory + if(changesMade) + { + SaveDirectory(dir); + } + } + } + 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); + } + 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); + } + 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 + uint32_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? + uint32_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); + } + 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 guarantee 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); + } + + // 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); + } + 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); + + // 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); + } + } + 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..48448360 --- /dev/null +++ b/lib/backupstore/BackupStoreContext.h @@ -0,0 +1,235 @@ +// -------------------------------------------------------------------------- +// +// 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* mpHousekeeping, + 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(); + + // Not really an API, but useful for BackupProtocolLocal2. + void ReleaseWriteLock() + { + if(mWriteLock.GotLock()) + { + mWriteLock.ReleaseLock(); + } + } + + void SetClientHasAccount(const std::string &rStoreRoot, int StoreDiscSet) {mClientHasAccount = true; mAccountRootDir = rStoreRoot; mStoreDiscSet = StoreDiscSet;} + bool GetClientHasAccount() const {return mClientHasAccount;} + const std::string &GetAccountRoot() const {return mAccountRootDir;} + 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 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, + int64_t ModificationTime, + 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, + bool AllowFlushCache = true); + void SaveDirectory(BackupStoreDirectory &rDir); + void RemoveDirectoryFromCache(int64_t ObjectID); + void ClearDirectoryCache(); + void DeleteDirectoryRecurse(int64_t ObjectID, bool Undelete); + int64_t AllocateObjectID(); + + std::string mConnectionDetails; + int32_t mClientID; + HousekeepingInterface *mpHousekeeping; + int mProtocolPhase; + bool mClientHasAccount; + std::string mAccountRootDir; // 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..6946f06e --- /dev/null +++ b/lib/backupstore/BackupStoreDirectory.cpp @@ -0,0 +1,611 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreDirectory.cpp +// 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 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() +: +#ifndef BOX_RELEASE_BUILD + mInvalidated(false), +#endif + mRevisionID(0), + mObjectID(0), + mContainerID(0), + mAttributesModTime(0), + mUserInfo1(0) +{ + ASSERT(sizeof(uint64_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) +: +#ifndef BOX_RELEASE_BUILD + mInvalidated(false), +#endif + 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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 for directory: expected " << + BOX_FORMAT_HEX32(OBJECTMAGIC_DIR_MAGIC_VALUE) << + " but found " << + BOX_FORMAT_HEX32(ntohl(hdr.mMagicValue)) << " in " << + rStream.ToString()); + } + + // 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 +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + 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_MESSAGE(BackupStoreException, CouldNotFindEntryInDirectory, + "Failed to find entry " << BOX_FORMAT_OBJECTID(ObjectID) << + " in directory " << BOX_FORMAT_OBJECTID(mObjectID)); +} + + +// -------------------------------------------------------------------------- +// +// 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 +{ + ASSERT(!mInvalidated); // Compiled out of release builds + 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() +: +#ifndef BOX_RELEASE_BUILD + mInvalidated(false), +#endif + 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) +: +#ifndef BOX_RELEASE_BUILD + mInvalidated(false), +#endif + 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) +: +#ifndef BOX_RELEASE_BUILD + mInvalidated(false), +#endif + 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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 +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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) +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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 +{ + ASSERT(!mInvalidated); // Compiled out of release builds + // 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..788a3ad0 --- /dev/null +++ b/lib/backupstore/BackupStoreDirectory.h @@ -0,0 +1,491 @@ +// -------------------------------------------------------------------------- +// +// 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 +{ +private: +#ifndef BOX_RELEASE_BUILD + bool mInvalidated; +#endif + +public: +#ifndef BOX_RELEASE_BUILD + void Invalidate() + { + mInvalidated = true; + for (std::vector<Entry*>::iterator i = mEntries.begin(); + i != mEntries.end(); i++) + { + (*i)->Invalidate(); + } + } +#endif + + typedef enum + { + Option_DependencyInfoPresent = 1 + } dir_StreamFormatOptions; + + BackupStoreDirectory(); + BackupStoreDirectory(int64_t ObjectID, int64_t ContainerID); + // Convenience constructor from a stream + BackupStoreDirectory(IOStream& rStream, + int Timeout = IOStream::TimeOutInfinite) +#ifndef BOX_RELEASE_BUILD + : mInvalidated(false) +#endif + { + ReadFromStream(rStream, Timeout); + } + BackupStoreDirectory(std::auto_ptr<IOStream> apStream, + int Timeout = IOStream::TimeOutInfinite) +#ifndef BOX_RELEASE_BUILD + : mInvalidated(false) +#endif + { + ReadFromStream(*apStream, Timeout); + } +private: + // Copying not allowed + BackupStoreDirectory(const BackupStoreDirectory &rToCopy); +public: + ~BackupStoreDirectory(); + + class Entry + { + private: +#ifndef BOX_RELEASE_BUILD + bool mInvalidated; +#endif + + public: +#ifndef BOX_RELEASE_BUILD + void Invalidate() { mInvalidated = true; } +#endif + + 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 + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mName; + } + box_time_t GetModificationTime() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mModificationTime; + } + int64_t GetObjectID() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mObjectID; + } + // SetObjectID is dangerous! It should only be used when + // creating a snapshot. + void SetObjectID(int64_t NewObjectID) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mObjectID = NewObjectID; + } + int64_t GetSizeInBlocks() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mSizeInBlocks; + } + int16_t GetFlags() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mFlags; + } + void AddFlags(int16_t Flags) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mFlags |= Flags; + } + void RemoveFlags(int16_t Flags) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mFlags &= ~Flags; + } + + // Some things can be changed + void SetName(const BackupStoreFilename &rNewName) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mName = rNewName; + } + void SetSizeInBlocks(int64_t SizeInBlocks) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mSizeInBlocks = SizeInBlocks; + } + + // Attributes + bool HasAttributes() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return !mAttributes.IsEmpty(); + } + void SetAttributes(const StreamableMemBlock &rAttr, uint64_t AttributesHash) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mAttributes.Set(rAttr); + mAttributesHash = AttributesHash; + } + const StreamableMemBlock &GetAttributes() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mAttributes; + } + uint64_t GetAttributesHash() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mAttributesHash; + } + + // Marks + // The lowest mark number a version of a file of this name has ever had + uint32_t GetMinMarkNumber() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mMinMarkNumber; + } + // The mark number on this file + uint32_t GetMarkNumber() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + 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() + { + ASSERT(!mInvalidated); // Compiled out of release builds + return GetFlags() & Flags_Dir; + } + bool inline IsFile() + { + ASSERT(!mInvalidated); // Compiled out of release builds + return GetFlags() & Flags_File; + } + bool inline IsOld() + { + ASSERT(!mInvalidated); // Compiled out of release builds + return GetFlags() & Flags_OldVersion; + } + bool inline IsDeleted() + { + ASSERT(!mInvalidated); // Compiled out of release builds + return GetFlags() & Flags_Deleted; + } + bool inline MatchesFlags(int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) + { + ASSERT(!mInvalidated); // Compiled out of release builds + return ((FlagsMustBeSet == Flags_INCLUDE_EVERYTHING) || ((mFlags & FlagsMustBeSet) == FlagsMustBeSet)) + && ((mFlags & FlagsNotToBeSet) == 0); + }; + + // Get dependency info + // new version this depends on + int64_t GetDependsNewer() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mDependsNewer; + } + void SetDependsNewer(int64_t ObjectID) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mDependsNewer = ObjectID; + } + // older version which depends on this + int64_t GetDependsOlder() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mDependsOlder; + } + void SetDependsOlder(int64_t ObjectID) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mDependsOlder = ObjectID; + } + + // Dependency info saving + bool HasDependencies() + { + ASSERT(!mInvalidated); // Compiled out of release builds + 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 + }; + +#ifndef BOX_RELEASE_BUILD + bool IsInvalidated() + { + return mInvalidated; + } +#endif // !BOX_RELEASE_BUILD + + 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 + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mObjectID; + } + + int64_t GetContainerID() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mContainerID; + } + // Need to be able to update the container ID when moving objects + void SetContainerID(int64_t ContainerID) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mContainerID = ContainerID; + } + + // Purely for use of server -- not serialised into streams + int64_t GetRevisionID() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mRevisionID; + } + void SetRevisionID(int64_t RevisionID) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mRevisionID = RevisionID; + } + + unsigned int GetNumberOfEntries() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mEntries.size(); + } + + // User info -- not serialised into streams + int64_t GetUserInfo1_SizeInBlocks() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mUserInfo1; + } + void SetUserInfo1_SizeInBlocks(int64_t UserInfo1) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mUserInfo1 = UserInfo1; + } + + // Attributes + bool HasAttributes() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return !mAttributes.IsEmpty(); + } + void SetAttributes(const StreamableMemBlock &rAttr, + box_time_t AttributesModTime) + { + ASSERT(!mInvalidated); // Compiled out of release builds + mAttributes.Set(rAttr); + mAttributesModTime = AttributesModTime; + } + const StreamableMemBlock &GetAttributes() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mAttributes; + } + box_time_t GetAttributesModTime() const + { + ASSERT(!mInvalidated); // Compiled out of release builds + return mAttributesModTime; + } + + class Iterator + { + public: + Iterator(const BackupStoreDirectory &rDir) + : mrDir(rDir), i(rDir.mEntries.begin()) + { + ASSERT(!mrDir.mInvalidated); // Compiled out of release builds + } + + BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING) + { + ASSERT(!mrDir.mInvalidated); // Compiled out of release builds + // 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) + { + ASSERT(!mrDir.mInvalidated); // Compiled out of release builds + // 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()) + { + ASSERT(!mrDir.mInvalidated); // Compiled out of release builds + } + + BackupStoreDirectory::Entry *Next(int16_t FlagsMustBeSet = Entry::Flags_INCLUDE_EVERYTHING, int16_t FlagsNotToBeSet = Entry::Flags_EXCLUDE_NOTHING) + { + ASSERT(!mrDir.mInvalidated); // Compiled out of release builds + // 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..efdbcf68 --- /dev/null +++ b/lib/backupstore/BackupStoreException.txt @@ -0,0 +1,76 @@ +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 +CorruptReferenceCountDatabase 70 The account's refcount database is corrupt and must be rebuilt by housekeeping. +CancelledByBackgroundTask 71 The current task was cancelled on request by the background task. +ObjectDoesNotExist 72 The specified object ID does not exist in the store. +AccountAlreadyExists 73 Tried to create an account that already exists. diff --git a/lib/backupstore/BackupStoreFile.cpp b/lib/backupstore/BackupStoreFile.cpp new file mode 100644 index 00000000..99562685 --- /dev/null +++ b/lib/backupstore/BackupStoreFile.cpp @@ -0,0 +1,1950 @@ +// -------------------------------------------------------------------------- +// +// 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 "BackupClientFileAttributes.h" +#include "BackupStoreConstants.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileCryptVar.h" +#include "BackupStoreFileEncodeStream.h" +#include "BackupStoreFileWire.h" +#include "BackupStoreFilename.h" +#include "BackupStoreInfo.h" +#include "BackupStoreObjectMagic.h" +#include "CipherAES.h" +#include "CipherBlowfish.h" +#include "CipherContext.h" +#include "CollectInBufferStream.h" +#include "Compress.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "Guards.h" +#include "IOStream.h" +#include "Logging.h" +#include "MD5Digest.h" +#include "Random.h" +#include "ReadGatherStream.h" +#include "RollingChecksum.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, + BackgroundTask* pBackgroundTask) +{ + // 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, + pBackgroundTask); + + // 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. This is more efficient than +// BackupStoreFile::VerifyStream() when the file data +// already exists on disk and we can Seek() around in +// it, but less efficient if we are reading the stream +// from the network and not intending to Write() it to +// a file first, so we need both unfortunately. +// TODO FIXME: use a modified VerifyStream() which +// repositions the file pointer and Close()s early to +// deduplicate this code. +// 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 to the end of this block + currentBlockStart += blkSize; + } + } + + // Check that there's no empty space + if(currentBlockStart != blockIndexLoc) + { + return false; + } + + // Check that if another file is referenced, then the ID is there, and if one + // isn't then 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::VerifyStream::Write() +// Purpose: Handles writes to the verifying stream. If the write +// completes the current unit, then verify it, copy it +// to mpCopyToStream if not NULL, and move on to the +// next unit, otherwise throw an exception. +// Created: 2015/08/07 +// +// -------------------------------------------------------------------------- + +void BackupStoreFile::VerifyStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + // Check that we haven't already written too much to the current unit + size_t BytesToAdd; + if(mState == State_Blocks) + { + // We don't know how many bytes to expect + ASSERT(mCurrentUnitSize == 0); + BytesToAdd = NBytes; + } + else + { + ASSERT(mCurrentUnitData.GetSize() < mCurrentUnitSize); + size_t BytesLeftInCurrentUnit = mCurrentUnitSize - + mCurrentUnitData.GetSize(); + // Add however many bytes are needed/available to the current unit's buffer. + BytesToAdd = std::min(BytesLeftInCurrentUnit, (size_t)NBytes); + } + + // We must make progress here, or we could have infinite recursion. + ASSERT(BytesToAdd > 0); + + CollectInBufferStream* pCurrentBuffer = (mCurrentBufferIsAlternate ? + &mAlternateData : &mCurrentUnitData); + pCurrentBuffer->Write(pBuffer, BytesToAdd, Timeout); + if(mpCopyToStream) + { + mpCopyToStream->Write(pBuffer, BytesToAdd, Timeout); + } + + pBuffer = (uint8_t *)pBuffer + BytesToAdd; + NBytes -= BytesToAdd; + mCurrentPosition += BytesToAdd; + + if(mState == State_Blocks) + { + // The block index follows the blocks themselves, but without seeing the + // index we don't know how big the blocks are. So we just have to keep + // reading, holding the last mBlockIndexSize bytes in two buffers, until + // we reach the end of the file (when Close() is called) when we can look + // back over those buffers and extract the block index from them. + if(pCurrentBuffer->GetSize() >= mBlockIndexSize) + { + // Time to swap buffers, and clear the one we're about to + // overwrite. + mCurrentBufferIsAlternate = !mCurrentBufferIsAlternate; + pCurrentBuffer = (mCurrentBufferIsAlternate ? + &mAlternateData : &mCurrentUnitData); + pCurrentBuffer->Reset(); + } + + // We don't want to move data into the finished buffer while we're in this + // state, and we don't need to call ourselves recursively, so just return. + return; + } + + ASSERT(mState != State_Blocks); + + // If the current unit is not complete, just return now. + if(mCurrentUnitData.GetSize() < mCurrentUnitSize) + { + return; + } + + ASSERT(mCurrentUnitData.GetSize() == mCurrentUnitSize); + mCurrentUnitData.SetForReading(); + CollectInBufferStream finished(mCurrentUnitData); + + // This should leave mCurrentUnitData empty in write phase + ASSERT(!mCurrentUnitData.StreamClosed()); + ASSERT(mCurrentUnitData.GetSize() == 0); + + // Advance automatically to next state (to reduce code duplication) if the current + // state is anything but State_Blocks. We remain in that state for more than one + // read (processing one block at a time), and exit it manually. + int oldState = mState; + if(mState != State_Blocks) + { + mState++; + } + + // Process and complete the current unit, depending what it is. + if(oldState == State_Header) + { + // Get the header... + file_StreamFormat hdr; + ASSERT(finished.GetSize() == sizeof(hdr)); + memcpy(&hdr, finished.GetBuffer(), sizeof(hdr)); + + // 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_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid header magic in stream: expected " << + BOX_FORMAT_HEX32(OBJECTMAGIC_FILE_MAGIC_VALUE_V1) << + " or " << + BOX_FORMAT_HEX32(OBJECTMAGIC_FILE_MAGIC_VALUE_V0) << + " but found " << + BOX_FORMAT_HEX32(ntohl(hdr.mMagicValue))); + } + + mNumBlocks = box_ntoh64(hdr.mNumBlocks); + mBlockIndexSize = (mNumBlocks * sizeof(file_BlockIndexEntry)) + + sizeof(file_BlockIndexHeader); + mContainerID = box_ntoh64(hdr.mContainerID); + + ASSERT(mState == State_FilenameHeader); + mCurrentUnitSize = 2; + } + else if(oldState == State_FilenameHeader) + { + // Check that the encoding is an accepted value. + unsigned int encoding = + BACKUPSTOREFILENAME_GET_ENCODING( + (uint8_t *)finished.GetBuffer()); + if(encoding < BackupStoreFilename::Encoding_Min || + encoding > BackupStoreFilename::Encoding_Max) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid encoding in filename: " << encoding); + } + + ASSERT(mState == State_Filename); + mCurrentUnitSize = BACKUPSTOREFILENAME_GET_SIZE( + (uint8_t *)finished.GetBuffer()); + // Copy the first two bytes back into the new buffer, to be used by the + // completed filename. + finished.CopyStreamTo(mCurrentUnitData); + } + else if(oldState == State_Filename) + { + BackupStoreFilename fn; + fn.ReadFromStream(finished, IOStream::TimeOutInfinite); + ASSERT(mState == State_AttributesSize); + mCurrentUnitSize = sizeof(int32_t); + } + else if(oldState == State_AttributesSize) + { + ASSERT(mState == State_Attributes); + mCurrentUnitSize = ntohl(*(int32_t *)finished.GetBuffer()); + } + else if(oldState == State_Attributes) + { + // Skip the attributes -- because they're encrypted, the server can't tell + // whether they're OK or not. + ASSERT(mState == State_Blocks); + mBlockDataPosition = mCurrentPosition; + mCurrentUnitSize = 0; + } + else if(oldState == State_Blocks) + { + // The block index follows the blocks themselves, but without seeing the + // index we don't know how big the blocks are. So we just have to keep + // reading, holding the last mBlockIndexSize bytes in two buffers, until + // we reach the end of the file (when Close() is called) when we can look + // back over those buffers and extract the block index from them. + ASSERT(mState == State_Blocks); + if(pCurrentBuffer->GetSize() >= mBlockIndexSize) + { + // Time to swap buffers, and clear the one we're about to + // overwrite. + mCurrentBufferIsAlternate = !mCurrentBufferIsAlternate; + pCurrentBuffer = (mCurrentBufferIsAlternate ? + &mAlternateData : &mCurrentUnitData); + pCurrentBuffer->Reset(); + } + } + + if(NBytes > 0) + { + // Still some data to process, so call recursively to deal with it. + Write(pBuffer, NBytes, Timeout); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreFile::VerifyStream::Write() +// Purpose: Handles closing the verifying stream, which tells us +// that there is no more incoming data, at which point +// we can extract the block index from the data most +// recently read, and verify it. +// Created: 2015/08/07 +// +// -------------------------------------------------------------------------- + + +void BackupStoreFile::VerifyStream::Close(bool CloseCopyStream) +{ + if(mpCopyToStream && CloseCopyStream) + { + mpCopyToStream->Close(); + } + + if(mState != State_Blocks) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Stream closed too early to be a valid BackupStoreFile: was in " + "state " << mState << " instead of " << State_Blocks); + } + + // Find the last mBlockIndexSize bytes. + CollectInBufferStream finished; + + CollectInBufferStream* pCurrentBuffer = mCurrentBufferIsAlternate ? + &mAlternateData : &mCurrentUnitData; + CollectInBufferStream* pPreviousBuffer = mCurrentBufferIsAlternate ? + &mCurrentUnitData : &mAlternateData; + + int64_t BytesFromCurrentBuffer = std::min(mBlockIndexSize, + (int64_t)pCurrentBuffer->GetSize()); + int64_t BytesFromPreviousBuffer = mBlockIndexSize - BytesFromCurrentBuffer; + + if(pPreviousBuffer->GetSize() < BytesFromPreviousBuffer) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Not enough bytes for block index: expected " << + mBlockIndexSize << " but had collected " << + (pPreviousBuffer->GetSize() + pCurrentBuffer->GetSize()) << + " at " << mCurrentPosition << " bytes into file"); + } + + size_t PreviousBufferOffset = pPreviousBuffer->GetSize() - + BytesFromPreviousBuffer; + size_t CurrentBufferOffset = pCurrentBuffer->GetSize() - + BytesFromCurrentBuffer; + + file_BlockIndexHeader blkhdr; + finished.Write((uint8_t *)pPreviousBuffer->GetBuffer() + PreviousBufferOffset, + BytesFromPreviousBuffer); + finished.Write((uint8_t *)pCurrentBuffer->GetBuffer() + CurrentBufferOffset, + BytesFromCurrentBuffer); + ASSERT(finished.GetSize() == mBlockIndexSize); + + // Load the block index header + memcpy(&blkhdr, finished.GetBuffer(), sizeof(blkhdr)); + + 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_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid block index magic in stream: expected " << + BOX_FORMAT_HEX32(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1) << + " or " << + BOX_FORMAT_HEX32(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V0) << + " but found " << + BOX_FORMAT_HEX32(ntohl(blkhdr.mMagicValue))); + } + + if((int64_t)box_ntoh64(blkhdr.mNumBlocks) != mNumBlocks) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid block index size in stream: expected " << + BOX_FORMAT_OBJECTID(mNumBlocks) << + " but found " << + BOX_FORMAT_OBJECTID(box_ntoh64(blkhdr.mNumBlocks))); + } + + // Flag for recording whether a block is referenced from another file + mBlockFromOtherFileReferenced = false; + int64_t blockIndexLoc = mCurrentPosition - mBlockIndexSize; + + // Read the index, checking that the length values all make sense + int64_t currentBlockStart = mBlockDataPosition; + file_BlockIndexEntry* pBlk = (file_BlockIndexEntry *) + ((uint8_t *)finished.GetBuffer() + sizeof(blkhdr)); + for(int64_t b = 0; b < mNumBlocks; ++b) + { + // Check size and location + int64_t blkSize = box_ntoh64(pBlk[b].mEncodedSize); + if(blkSize <= 0) + { + // Mark that this file references another file + mBlockFromOtherFileReferenced = true; + } + else + { + // This block is actually in this file + if((currentBlockStart + blkSize) > blockIndexLoc) + { + // Encoded size makes the block run over the index + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid block index: entry " << b << " makes " + "total size of block data exceed the available " + "space"); + } + + // Move the current block start to the end of this block + currentBlockStart += blkSize; + } + } + + // Check that there's no empty space + if(currentBlockStart != blockIndexLoc) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid block index: total size of blocks does not match the " + "actual amount of block data in the stream: expected " << + (blockIndexLoc - mBlockDataPosition) << " but found " << + (currentBlockStart - mBlockDataPosition)); + } + + // Check that if another file is referenced, then the ID is there, and if one + // isn't then there is no ID. + int64_t otherID = box_ntoh64(blkhdr.mOtherFileID); + if((otherID != 0 && mBlockFromOtherFileReferenced == false) + || (otherID == 0 && mBlockFromOtherFileReferenced == true)) + { + // Doesn't look good! + THROW_EXCEPTION_MESSAGE(BackupStoreException, BadBackupStoreFile, + "Invalid block index header: actual dependency status does not " + "match the file header: expected to depend on file object " << + BOX_FORMAT_OBJECTID(otherID)); + } + + mDiffFromObjectID = otherID; +} + + +// -------------------------------------------------------------------------- +// +// 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, + int Timeout) +{ + 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((((uint64_t)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((((uint64_t)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() +{ +} + +// Shortcut interface +int64_t BackupStoreFile::QueryStoreFileDiff(BackupProtocolCallable& protocol, + const std::string& LocalFilename, int64_t DirectoryObjectID, + int64_t DiffFromFileID, int64_t AttributesHash, + const BackupStoreFilenameClear& StoreFilename, int Timeout, + DiffTimer *pDiffTimer, ReadLoggingStream::Logger* pLogger, + RunStatusProvider* pRunStatusProvider) +{ + int64_t ModificationTime; + std::auto_ptr<BackupStoreFileEncodeStream> pStream; + + if(DiffFromFileID) + { + // Fetch the block index for this one + std::auto_ptr<BackupProtocolSuccess> getblockindex = + protocol.QueryGetBlockIndexByName(DirectoryObjectID, + StoreFilename); + ASSERT(getblockindex->GetObjectID() == DiffFromFileID); + std::auto_ptr<IOStream> blockIndexStream(protocol.ReceiveStream()); + + pStream = EncodeFileDiff(LocalFilename, + DirectoryObjectID, StoreFilename, DiffFromFileID, + *(blockIndexStream.get()), Timeout, pDiffTimer, + &ModificationTime, NULL // pIsCompletelyDifferent + ); + } + else + { + pStream = BackupStoreFile::EncodeFile(LocalFilename, + DirectoryObjectID, StoreFilename, &ModificationTime); + } + + std::auto_ptr<IOStream> upload(pStream.release()); + return protocol.QueryStoreFile(DirectoryObjectID, + ModificationTime, AttributesHash, DiffFromFileID, + StoreFilename, upload)->GetObjectID(); +} + diff --git a/lib/backupstore/BackupStoreFile.h b/lib/backupstore/BackupStoreFile.h new file mode 100644 index 00000000..fe69caeb --- /dev/null +++ b/lib/backupstore/BackupStoreFile.h @@ -0,0 +1,309 @@ +// -------------------------------------------------------------------------- +// +// 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 "autogen_BackupProtocol.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreFileWire.h" +#include "BackupStoreFilename.h" +#include "CollectInBufferStream.h" +#include "IOStream.h" +#include "ReadLoggingStream.h" + +typedef struct +{ + int64_t mBytesInEncodedFiles; + int64_t mBytesAlreadyOnServer; + int64_t mTotalFileStreamSize; +} BackupStoreFileStats; + +class BackgroundTask; +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 BackupStoreFileEncodeStream; + +// -------------------------------------------------------------------------- +// +// 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 +// 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, + int Timeout = IOStream::TimeOutInfinite); + 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 + }; + + class VerifyStream : public IOStream + { + private: + enum + { + State_Header = 0, + State_FilenameHeader, + State_Filename, + State_AttributesSize, + State_Attributes, + State_Blocks, + }; + + int mState; + IOStream* mpCopyToStream; + CollectInBufferStream mCurrentUnitData; + size_t mCurrentUnitSize; + int64_t mNumBlocks; + int64_t mBlockIndexSize; + int64_t mCurrentPosition; + int64_t mBlockDataPosition; + bool mCurrentBufferIsAlternate; + CollectInBufferStream mAlternateData; + bool mBlockFromOtherFileReferenced; + int64_t mContainerID; + int64_t mDiffFromObjectID; + + public: + VerifyStream(IOStream* pCopyToStream = NULL) + : mState(State_Header), + mpCopyToStream(pCopyToStream), + mCurrentUnitSize(sizeof(file_StreamFormat)), + mNumBlocks(0), + mBlockIndexSize(0), + mCurrentPosition(0), + mBlockDataPosition(0), + mCurrentBufferIsAlternate(false), + mBlockFromOtherFileReferenced(false), + mContainerID(0), + mDiffFromObjectID(0) + { } + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) + { + THROW_EXCEPTION(CommonException, NotSupported); + } + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void Close(bool CloseCopyStream = true); + virtual bool StreamDataLeft() + { + THROW_EXCEPTION(CommonException, NotSupported); + } + virtual bool StreamClosed() + { + THROW_EXCEPTION(CommonException, NotSupported); + } + }; + + // 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, + BackgroundTask* pBackgroundTask = 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, + BackgroundTask* pBackgroundTask = NULL + ); + // Shortcut interface + static int64_t QueryStoreFileDiff(BackupProtocolCallable& protocol, + const std::string& LocalFilename, int64_t DirectoryObjectID, + int64_t DiffFromFileID, int64_t AttributesHash, + const BackupStoreFilenameClear& StoreFilename, + int Timeout = IOStream::TimeOutInfinite, + DiffTimer *pDiffTimer = NULL, + ReadLoggingStream::Logger* pLogger = NULL, + RunStatusProvider* pRunStatusProvider = NULL); + + 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..0eec3872 --- /dev/null +++ b/lib/backupstore/BackupStoreFileCmbIdx.cpp @@ -0,0 +1,326 @@ +// -------------------------------------------------------------------------- +// +// 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, + int Timeout = IOStream::TimeOutInfinite); + 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, + int Timeout) +{ + 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..1d83d854 --- /dev/null +++ b/lib/backupstore/BackupStoreFileDiff.cpp @@ -0,0 +1,1062 @@ +// -------------------------------------------------------------------------- +// +// 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, + BackgroundTask* pBackgroundTask) +{ + // 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, + NULL, // ReadLoggingStream::Logger + NULL, // RunStatusProvider + pBackgroundTask); // BackgroundTask + } + } + + // 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, + NULL, // ReadLoggingStream::Logger + NULL, // RunStatusProvider + pBackgroundTask); // BackgroundTask + } + + // 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, + NULL, // ReadLoggingStream::Logger + NULL, // RunStatusProvider + pBackgroundTask); + 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 smaller blocks + // 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 of " << Sizes[s] << " bytes with hash " << hash << " 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 + { + // Too many to log + // BOX_TRACE("False alarm match of " << Sizes[s] << " bytes with hash " << hash << " 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 + snprintf(b, sizeof(b), "%8I64d", (int64_t) + (rRecipe[e].mpStartBlock - pIndex)); +#else + snprintf(b, sizeof(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..83333a5b --- /dev/null +++ b/lib/backupstore/BackupStoreFileEncodeStream.cpp @@ -0,0 +1,741 @@ +// -------------------------------------------------------------------------- +// +// 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 "BackgroundTask.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), + mpBackgroundTask(NULL), + mStatus(Status_Header), + mSendData(true), + mTotalBlocks(0), + mBytesToUpload(0), + mBytesUploaded(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, + BackgroundTask* pBackgroundTask) +{ + // 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; + mBytesToUpload += (*pRecipe)[inst].mSpaceBefore; + // 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; + mpBackgroundTask = pBackgroundTask; +} + + +// -------------------------------------------------------------------------- +// +// 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); + } + + if(mpBackgroundTask) + { + BackgroundTask::State state = (mpRecipe->at(0).mBlocks == 0) + ? BackgroundTask::Uploading_Full + : BackgroundTask::Uploading_Patch; + if(!mpBackgroundTask->RunBackgroundTask(state, mBytesUploaded, + mBytesToUpload)) + { + THROW_EXCEPTION(BackupStoreException, + CancelledByBackgroundTask); + } + } + + 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); + + mBytesUploaded += blockRawSize; + + //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, + int Timeout) +{ + 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..5b9b4a61 --- /dev/null +++ b/lib/backupstore/BackupStoreFileEncodeStream.h @@ -0,0 +1,144 @@ +// -------------------------------------------------------------------------- +// +// 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, + BackgroundTask* pBackgroundTask = NULL); + + virtual int Read(void *pBuffer, int NBytes, int Timeout); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + int64_t GetBytesToUpload() { return mBytesToUpload; } + int64_t GetTotalBytesSent() { return mTotalBytesSent; } + + static void CalculateBlockSizes(int64_t DataSize, int64_t &rNumBlocksOut, + int32_t &rBlockSizeOut, int32_t &rLastBlockSizeOut); + +private: + enum + { + Status_Header = 0, + Status_Blocks = 1, + Status_BlockListing = 2, + Status_Finished = 3 + }; + + void EncodeCurrentBlock(); + void SkipPreviousBlocksInInstruction(); + void SetForInstruction(); + void StoreBlockIndexEntry(int64_t WncSizeOrBlkIndex, int32_t ClearSize, uint32_t WeakChecksum, uint8_t *pStrongChecksum); + + Recipe *mpRecipe; + IOStream *mpFile; // source file + CollectInBufferStream mData; // buffer for header and index entries + IOStream *mpLogging; + RunStatusProvider* mpRunStatusProvider; + BackgroundTask* mpBackgroundTask; + 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 mBytesToUpload; // Total number of clear bytes to encode and upload + int64_t mBytesUploaded; // Total number of clear bytes already encoded + // excluding reused blocks already on the server. + 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..b7cf555f --- /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 new file mode 100644 index 00000000..efe3f7bb --- /dev/null +++ b/lib/backupstore/BackupStoreInfo.cpp @@ -0,0 +1,753 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreInfo.cpp +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <algorithm> + +#include "Archive.h" +#include "BackupStoreInfo.h" +#include "BackupStoreException.h" +#include "RaidFileWrite.h" +#include "RaidFileRead.h" + +#include "MemLeakFindOn.h" + +#ifdef BOX_RELEASE_BUILD + #define NUM_DELETED_DIRS_BLOCK 256 +#else + #define NUM_DELETED_DIRS_BLOCK 2 +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::BackupStoreInfo() +// Purpose: Default constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreInfo::BackupStoreInfo() +: mAccountID(-1), + mDiscSet(-1), + mReadOnly(true), + mIsModified(false), + mClientStoreMarker(0), + mLastObjectIDUsed(-1), + mBlocksUsed(0), + mBlocksInCurrentFiles(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0), + mBlocksInDirectories(0), + mBlocksSoftLimit(0), + mBlocksHardLimit(0), + mNumCurrentFiles(0), + mNumOldFiles(0), + mNumDeletedFiles(0), + mNumDirectories(0), + mAccountEnabled(true) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::~BackupStoreInfo +// Purpose: Destructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreInfo::~BackupStoreInfo() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CreateNew(int32_t, const std::string &, int) +// Purpose: Create a new info file on disc +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, int64_t BlockSoftLimit, int64_t BlockHardLimit) +{ + 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); +} + +BackupStoreInfo::BackupStoreInfo(int32_t AccountID, const std::string &FileName, + int64_t BlockSoftLimit, int64_t BlockHardLimit) +: mAccountID(AccountID), + mDiscSet(-1), + mFilename(FileName), + mReadOnly(false), + mIsModified(false), + mClientStoreMarker(0), + mLastObjectIDUsed(0), + mBlocksUsed(0), + mBlocksInCurrentFiles(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0), + mBlocksInDirectories(0), + mBlocksSoftLimit(BlockSoftLimit), + mBlocksHardLimit(BlockHardLimit), + mNumCurrentFiles(0), + mNumOldFiles(0), + mNumDeletedFiles(0), + mNumDirectories(0), + mAccountEnabled(true) +{ + mExtraData.SetForReading(); // extra data is empty in this case +} + +// -------------------------------------------------------------------------- +// +// Function +// 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)); + std::auto_ptr<BackupStoreInfo> info = Load(*rf, fn, ReadOnly); + + // Check it + if(info->GetAccountID() != AccountID) + { + THROW_FILE_ERROR("Found wrong account ID in store info", + fn, BackupStoreException, BadStoreInfoOnLoad); + } + + info->mDiscSet = DiscSet; + return info; +} + +std::auto_ptr<BackupStoreInfo> BackupStoreInfo::Load(IOStream& rStream, + const std::string FileName, bool ReadOnly) +{ + // Read in format and version + int32_t magic; + if(!rStream.ReadFullBuffer(&magic, sizeof(magic), 0)) + { + THROW_FILE_ERROR("Failed to read store info file: " + "short read of magic number", FileName, + 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)), + FileName, BackupStoreException, BadStoreInfoOnLoad); + } + + // Make new object + std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo); + + // Put in basic location info + info->mFilename = FileName; + info->mReadOnly = ReadOnly; + int64_t numDelObj = 0; + + if (v1) + { + // Read in a header + info_StreamFormat_1 hdr; + rStream.Seek(0, IOStream::SeekType_Absolute); + + if(!rStream.ReadFullBuffer(&hdr, sizeof(hdr), + 0 /* not interested in bytes read if this fails */)) + { + THROW_FILE_ERROR("Failed to read store info header", + FileName, BackupStoreException, CouldNotLoadStoreInfo); + } + + // Insert info from file + info->mAccountID = ntohl(hdr.mAccountID); + 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(rStream, IOStream::TimeOutInfinite); + + // Check it + archive.Read(info->mAccountID); + 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->mNumCurrentFiles); + archive.Read(info->mNumOldFiles); + archive.Read(info->mNumDeletedFiles); + archive.Read(info->mNumDirectories); + archive.Read(numDelObj); + } + + // Then load the list of deleted directories + if(numDelObj > 0) + { + int64_t objs[NUM_DELETED_DIRS_BLOCK]; + + int64_t toload = numDelObj; + while(toload > 0) + { + // How many in this one? + int b = (toload > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(toload)); + + if(!rStream.ReadFullBuffer(objs, b * sizeof(int64_t), 0 /* not interested in bytes read if this fails */)) + { + THROW_EXCEPTION(BackupStoreException, CouldNotLoadStoreInfo) + } + + // Add them + for(int t = 0; t < b; ++t) + { + info->mDeletedDirectories.push_back(box_ntoh64(objs[t])); + } + + // Number loaded + toload -= b; + } + } + + // Final check + if(static_cast<int64_t>(info->mDeletedDirectories.size()) != numDelObj) + { + THROW_EXCEPTION(BackupStoreException, BadStoreInfoOnLoad) + } + + if(v2) + { + Archive archive(rStream, 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 = rStream.BytesLeftToRead(); + if (bytesLeft > 0) + { + rStream.CopyStreamTo(info->mExtraData); + } + info->mExtraData.SetForReading(); + + // return it to caller + return info; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CreateForRegeneration(...) +// Purpose: Return an object which can be used to save for regeneration. +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- +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); + + // Make new object + std::auto_ptr<BackupStoreInfo> info(new BackupStoreInfo); + + // Put in basic info + info->mAccountID = AccountID; + 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; + info->mBlocksInOldFiles = BlocksInOldFiles; + info->mBlocksInDeletedFiles = BlocksInDeletedFiles; + info->mBlocksInDirectories = BlocksInDirectories; + info->mBlocksSoftLimit = BlockSoftLimit; + info->mBlocksHardLimit = BlockHardLimit; + info->mAccountEnabled = AccountEnabled; + + ExtraData.CopyStreamTo(info->mExtraData); + info->mExtraData.SetForReading(); + + // return it to caller + return info; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::Save(bool allowOverwrite) +// Purpose: Save modified info back to disc +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::Save(bool allowOverwrite) +{ + // Make sure we're initialised (although should never come to this) + if(mFilename.empty() || mAccountID == -1 || mDiscSet == -1) + { + THROW_EXCEPTION(BackupStoreException, Internal) + } + + // Can we do this? + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + // Then... open a write file + RaidFileWrite rf(mDiscSet, mFilename); + rf.Open(allowOverwrite); + Save(rf); + + // Commit it to disc, converting it to RAID now + rf.Commit(true); +} + +void BackupStoreInfo::Save(IOStream& rOutStream) +{ + // Make header + int32_t magic = htonl(INFO_MAGIC_VALUE_2); + rOutStream.Write(&magic, sizeof(magic)); + Archive archive(rOutStream, 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(mNumCurrentFiles); + archive.Write(mNumOldFiles); + archive.Write(mNumDeletedFiles); + archive.Write(mNumDirectories); + + int64_t numDelObj = mDeletedDirectories.size(); + archive.Write(numDelObj); + + // Write the deleted object list + if(mDeletedDirectories.size() > 0) + { + int64_t objs[NUM_DELETED_DIRS_BLOCK]; + + int tosave = mDeletedDirectories.size(); + std::vector<int64_t>::iterator i(mDeletedDirectories.begin()); + while(tosave > 0) + { + // How many in this one? + int b = (tosave > NUM_DELETED_DIRS_BLOCK)?NUM_DELETED_DIRS_BLOCK:((int)(tosave)); + + // Add them + for(int t = 0; t < b; ++t) + { + ASSERT(i != mDeletedDirectories.end()); + objs[t] = box_hton64((*i)); + i++; + } + + // Write + rOutStream.Write(objs, b * sizeof(int64_t)); + + // Number saved + tosave -= b; + } + } + + archive.Write(mAccountEnabled); + + mExtraData.Seek(0, IOStream::SeekType_Absolute); + mExtraData.CopyStreamTo(rOutStream); + mExtraData.Seek(0, IOStream::SeekType_Absolute); + + // Mark is as not modified + mIsModified = false; +} + +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(BlocksUsed); + COMPARE(BlocksInCurrentFiles); + COMPARE(BlocksInOldFiles); + COMPARE(BlocksInDeletedFiles); + COMPARE(BlocksInDirectories); + COMPARE(BlocksSoftLimit); + COMPARE(BlocksHardLimit); + COMPARE(NumCurrentFiles); + COMPARE(NumOldFiles); + COMPARE(NumDeletedFiles); + COMPARE(NumDirectories); + + #undef COMPARE + + if (rOldInfo.GetLastObjectIDUsed() != GetLastObjectIDUsed()) + { + BOX_NOTICE("LastObjectIDUsed changed from " << + rOldInfo.GetLastObjectIDUsed() << " to " << + GetLastObjectIDUsed()); + // Not important enough to be an error + // numChanges++; + } + + return numChanges; +} + +void BackupStoreInfo::ApplyDelta(int64_t& field, const std::string& field_name, + const int64_t delta) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly); + } + + if((field + delta) < 0) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, + StoreInfoBlockDeltaMakesValueNegative, + "Failed to reduce " << field_name << " from " << + field << " by " << delta); + } + + field += delta; + mIsModified = true; +} + +#define APPLY_DELTA(field, delta) \ + ApplyDelta(field, #field, delta) + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksUsed(int32_t) +// Purpose: Change number of blocks used, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksUsed(int64_t Delta) +{ + 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); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInOldFiles(int32_t) +// Purpose: Change number of blocks in old files, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInOldFiles(int64_t Delta) +{ + APPLY_DELTA(mBlocksInOldFiles, Delta); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInDeletedFiles(int32_t) +// Purpose: Change number of blocks in deleted files, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInDeletedFiles(int64_t Delta) +{ + APPLY_DELTA(mBlocksInDeletedFiles, Delta); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeBlocksInDirectories(int32_t) +// Purpose: Change number of blocks in directories, by a delta amount +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeBlocksInDirectories(int64_t Delta) +{ + APPLY_DELTA(mBlocksInDirectories, Delta); +} + +void BackupStoreInfo::AdjustNumCurrentFiles(int64_t increase) +{ + APPLY_DELTA(mNumCurrentFiles, 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); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::CorrectAllUsedValues(int64_t, int64_t, int64_t, int64_t) +// Purpose: Set all the usage counts to specific values -- use for correcting in housekeeping +// if something when wrong during the backup connection, and the store info wasn't +// saved back to disc. +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + // Set the values + mBlocksUsed = Used; + mBlocksInOldFiles = InOldFiles; + mBlocksInDeletedFiles = InDeletedFiles; + mBlocksInDirectories = InDirectories; + + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::AddDeletedDirectory(int64_t) +// Purpose: Add a directory ID to the deleted list +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::AddDeletedDirectory(int64_t DirID) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mDeletedDirectories.push_back(DirID); + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::RemovedDeletedDirectory(int64_t) +// Purpose: Remove a directory from the deleted list +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::RemovedDeletedDirectory(int64_t DirID) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + std::vector<int64_t>::iterator i(std::find(mDeletedDirectories.begin(), mDeletedDirectories.end(), DirID)); + if(i == mDeletedDirectories.end()) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoDirNotInList) + } + + mDeletedDirectories.erase(i); + mIsModified = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::ChangeLimits(int64_t, int64_t) +// Purpose: Change the soft and hard limits +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mBlocksSoftLimit = BlockSoftLimit; + mBlocksHardLimit = BlockHardLimit; + + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::AllocateObjectID() +// Purpose: Allocate an ID for a new object in the store. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +int64_t BackupStoreInfo::AllocateObjectID() +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + if(mLastObjectIDUsed < 0) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoNotInitialised) + } + + mIsModified = true; + + // Return the next object ID + return ++mLastObjectIDUsed; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreInfo::SetClientStoreMarker(int64_t) +// Purpose: Sets the client store marker +// Created: 2003/10/29 +// +// -------------------------------------------------------------------------- +void BackupStoreInfo::SetClientStoreMarker(int64_t ClientStoreMarker) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, StoreInfoIsReadOnly) + } + + mClientStoreMarker = ClientStoreMarker; + mIsModified = true; +} + + +// -------------------------------------------------------------------------- +// +// 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; +} + diff --git a/lib/backupstore/BackupStoreInfo.h b/lib/backupstore/BackupStoreInfo.h new file mode 100644 index 00000000..ec6cd2cb --- /dev/null +++ b/lib/backupstore/BackupStoreInfo.h @@ -0,0 +1,214 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreInfo.h +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREINFO__H +#define BACKUPSTOREINFO__H + +#include <memory> +#include <string> +#include <vector> + +#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" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreInfo +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +class BackupStoreInfo +{ + friend class BackupStoreCheck; +public: + ~BackupStoreInfo(); +private: + // Creation through static functions only + BackupStoreInfo(); + // No copying allowed + BackupStoreInfo(const BackupStoreInfo &); + +public: + // Create a New account, saving a blank info object to the disc + static void CreateNew(int32_t AccountID, const std::string &rRootDir, int DiscSet, + int64_t BlockSoftLimit, int64_t BlockHardLimit); + BackupStoreInfo(int32_t AccountID, const std::string &FileName, + int64_t BlockSoftLimit, int64_t BlockHardLimit); + + // Load it from the store + static std::auto_ptr<BackupStoreInfo> Load(int32_t AccountID, const std::string &rRootDir, int DiscSet, bool ReadOnly, int64_t *pRevisionID = 0); + + // Load it from a stream (file or RaidFile) + static std::auto_ptr<BackupStoreInfo> Load(IOStream& rStream, + const std::string FileName, bool ReadOnly); + + // Has info been modified? + bool IsModified() const {return mIsModified;} + + // Save modified infomation back to store + void Save(bool allowOverwrite = true); + void Save(IOStream& rOutStream); + + // Data access functions + int32_t GetAccountID() const {return mAccountID;} + int64_t GetLastObjectIDUsed() const {return mLastObjectIDUsed;} + int64_t GetBlocksUsed() const {return mBlocksUsed;} + int64_t GetBlocksInCurrentFiles() const {return mBlocksInCurrentFiles;} + 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;} + int64_t GetNumCurrentFiles() const {return mNumCurrentFiles;} + 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); + void ChangeBlocksInOldFiles(int64_t Delta); + void ChangeBlocksInDeletedFiles(int64_t Delta); + void ChangeBlocksInDirectories(int64_t Delta); + void CorrectAllUsedValues(int64_t Used, int64_t InOldFiles, int64_t InDeletedFiles, int64_t InDirectories); + void AddDeletedDirectory(int64_t DirID); + void RemovedDeletedDirectory(int64_t DirID); + void ChangeLimits(int64_t BlockSoftLimit, int64_t BlockHardLimit); + void AdjustNumCurrentFiles(int64_t increase); + void AdjustNumOldFiles(int64_t increase); + void AdjustNumDeletedFiles(int64_t increase); + void AdjustNumDirectories(int64_t increase); + + // Object IDs + int64_t AllocateObjectID(); + + // Client marker set and get + 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); + + typedef struct + { + int64_t mLastObjectIDUsed; + int64_t mBlocksUsed; + int64_t mBlocksInCurrentFiles; + int64_t mBlocksInOldFiles; + int64_t mBlocksInDeletedFiles; + int64_t mBlocksInDirectories; + int64_t mNumCurrentFiles; + int64_t mNumOldFiles; + int64_t mNumDeletedFiles; + int64_t mNumDirectories; + } Adjustment; + +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; + int mDiscSet; + std::string mFilename; + bool mReadOnly; + bool mIsModified; + + // Client infomation + int64_t mClientStoreMarker; + + // Account information + int64_t mLastObjectIDUsed; + int64_t mBlocksUsed; + int64_t mBlocksInCurrentFiles; + int64_t mBlocksInOldFiles; + int64_t mBlocksInDeletedFiles; + int64_t mBlocksInDirectories; + int64_t mBlocksSoftLimit; + int64_t mBlocksHardLimit; + int64_t mNumCurrentFiles; + int64_t mNumOldFiles; + int64_t mNumDeletedFiles; + int64_t mNumDirectories; + std::vector<int64_t> mDeletedDirectories; + bool mAccountEnabled; + CollectInBufferStream mExtraData; + + void ApplyDelta(int64_t& field, const std::string& field_name, + const int64_t delta); +}; + +#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 new file mode 100644 index 00000000..b2ea1abd --- /dev/null +++ b/lib/backupstore/BackupStoreRefCountDatabase.cpp @@ -0,0 +1,377 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreRefCountDatabase.cpp +// Purpose: Backup store object reference count database storage +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> + +#include <algorithm> + +#include "BackupStoreRefCountDatabase.h" +#include "BackupStoreException.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "RaidFileController.h" +#include "RaidFileUtil.h" +#include "RaidFileException.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define REFCOUNT_MAGIC_VALUE 0x52656643 // RefC +#define REFCOUNT_FILENAME "refcount" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::BackupStoreRefCountDatabase() +// Purpose: Default constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::BackupStoreRefCountDatabase(const + BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly, + bool Temporary, std::auto_ptr<FileStream> apDatabaseFile) +: mAccount(rAccount), + mFilename(GetFilename(rAccount, Temporary)), + mReadOnly(ReadOnly), + mIsModified(false), + mIsTemporaryFile(Temporary), + mapDatabaseFile(apDatabaseFile) +{ + ASSERT(!(ReadOnly && Temporary)); // being both doesn't make sense +} + +void BackupStoreRefCountDatabase::Commit() +{ + if (!mIsTemporaryFile) + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "Cannot commit a permanent reference count database"); + } + + if (!mapDatabaseFile.get()) + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "Reference count database is already closed"); + } + + mapDatabaseFile->Close(); + mapDatabaseFile.reset(); + + std::string Final_Filename = GetFilename(mAccount, false); + + #ifdef WIN32 + if(FileExists(Final_Filename) && unlink(Final_Filename.c_str()) != 0) + { + THROW_EMU_FILE_ERROR("Failed to delete old permanent refcount " + "database file", mFilename, CommonException, + OSFileError); + } + #endif + + if(rename(mFilename.c_str(), Final_Filename.c_str()) != 0) + { + THROW_EMU_ERROR("Failed to rename temporary refcount database " + "file from " << mFilename << " to " << + Final_Filename, CommonException, OSFileError); + } + + mFilename = Final_Filename; + mIsModified = false; + mIsTemporaryFile = false; +} + +void BackupStoreRefCountDatabase::Discard() +{ + if (!mIsTemporaryFile) + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "Cannot discard a permanent reference count database"); + } + + // Under normal conditions, we should know whether the file is still + // open or not, and not Discard it unless it's open. However if the + // final rename() fails during Commit(), the file will already be + // closed, and we don't want to blow up here in that case. + if (mapDatabaseFile.get()) + { + mapDatabaseFile->Close(); + mapDatabaseFile.reset(); + } + + if(unlink(mFilename.c_str()) != 0) + { + THROW_EMU_FILE_ERROR("Failed to delete temporary refcount " + "database file", mFilename, CommonException, + OSFileError); + } + + mIsModified = false; + mIsTemporaryFile = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase +// Purpose: Destructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::~BackupStoreRefCountDatabase() +{ + if (mIsTemporaryFile) + { + // Don't throw exceptions in a destructor. + BOX_ERROR("BackupStoreRefCountDatabase destroyed without " + "explicit commit or discard"); + try + { + Discard(); + } + catch(BoxException &e) + { + BOX_LOG_SYS_ERROR("Failed to discard BackupStoreRefCountDatabase " + "in destructor: " << e.what()); + } + } +} + +std::string BackupStoreRefCountDatabase::GetFilename(const + BackupStoreAccountDatabase::Entry& rAccount, bool Temporary) +{ + std::string RootDir = BackupStoreAccounts::GetAccountRoot(rAccount); + ASSERT(RootDir[RootDir.size() - 1] == '/' || + RootDir[RootDir.size() - 1] == DIRECTORY_SEPARATOR_ASCHAR); + + std::string fn(RootDir + REFCOUNT_FILENAME ".rdb"); + if(Temporary) + { + fn += "X"; + } + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(rAccount.GetDiscSet())); + return RaidFileUtil::MakeWriteFileName(rdiscSet, fn); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::Create(int32_t, +// const std::string &, int, bool) +// Purpose: Create a blank database, using a temporary file that +// you must Discard() or Commit() to make permanent. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreRefCountDatabase> + BackupStoreRefCountDatabase::Create + (const BackupStoreAccountDatabase::Entry& rAccount) +{ + // Initial header + refcount_StreamFormat hdr; + hdr.mMagicValue = htonl(REFCOUNT_MAGIC_VALUE); + hdr.mAccountID = htonl(rAccount.GetID()); + + std::string Filename = GetFilename(rAccount, true); // temporary + + // Open the file for writing + if (FileExists(Filename)) + { + BOX_WARNING(BOX_FILE_MESSAGE(Filename, "Overwriting existing " + "temporary reference count database")); + if (unlink(Filename.c_str()) != 0) + { + THROW_SYS_FILE_ERROR("Failed to delete old temporary " + "reference count database file", Filename, + CommonException, OSFileError); + } + } + + int flags = O_CREAT | O_BINARY | O_RDWR | O_EXCL; + std::auto_ptr<FileStream> DatabaseFile(new FileStream(Filename, flags)); + + // Write header + DatabaseFile->Write(&hdr, sizeof(hdr)); + + // Make new object + std::auto_ptr<BackupStoreRefCountDatabase> refcount( + new BackupStoreRefCountDatabase(rAccount, false, true, + DatabaseFile)); + + // The root directory must always have one reference for a database + // to be valid, so set that now on the new database. This will leave + // mIsModified set to true. + refcount->SetRefCount(BACKUPSTORE_ROOT_DIRECTORY_ID, 1); + + // return it to caller + return refcount; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::Load(int32_t AccountID, +// BackupStoreAccountDatabase& rAccountDatabase, +// bool ReadOnly); +// Purpose: Loads the info from disc, given the root +// information. Can be marked as read only. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreRefCountDatabase> BackupStoreRefCountDatabase::Load( + const BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly) +{ + // Generate the filename. Cannot open a temporary database, so it must + // be a permanent one. + std::string Filename = GetFilename(rAccount, false); + int flags = ReadOnly ? O_RDONLY : O_RDWR; + + // Open the file for read/write + std::auto_ptr<FileStream> dbfile(new FileStream(Filename, + flags | O_BINARY)); + + // Read in a header + refcount_StreamFormat hdr; + if(!dbfile->ReadFullBuffer(&hdr, sizeof(hdr), 0 /* not interested in bytes read if this fails */)) + { + THROW_FILE_ERROR("Failed to read refcount database: " + "short read", Filename, BackupStoreException, + CouldNotLoadStoreInfo); + } + + // Check it + if(ntohl(hdr.mMagicValue) != REFCOUNT_MAGIC_VALUE || + (int32_t)ntohl(hdr.mAccountID) != rAccount.GetID()) + { + THROW_FILE_ERROR("Failed to read refcount database: " + "bad magic number", Filename, BackupStoreException, + BadStoreInfoOnLoad); + } + + // Make new object + std::auto_ptr<BackupStoreRefCountDatabase> refcount( + new BackupStoreRefCountDatabase(rAccount, ReadOnly, false, + dbfile)); + + // return it to caller + return refcount; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreRefCountDatabase::GetRefCount(int64_t +// ObjectID) +// Purpose: Get the number of references to the specified object +// out of the database +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- +BackupStoreRefCountDatabase::refcount_t +BackupStoreRefCountDatabase::GetRefCount(int64_t ObjectID) const +{ + IOStream::pos_type offset = GetOffset(ObjectID); + + if (GetSize() < offset + GetEntrySize()) + { + THROW_FILE_ERROR("Failed to read refcount database: " + "attempted read of unknown refcount for object " << + BOX_FORMAT_OBJECTID(ObjectID), mFilename, + BackupStoreException, UnknownObjectRefCountRequested); + } + + mapDatabaseFile->Seek(offset, SEEK_SET); + + refcount_t refcount; + if (mapDatabaseFile->Read(&refcount, sizeof(refcount)) != + sizeof(refcount)) + { + THROW_FILE_ERROR("Failed to read refcount database: " + "short read at offset " << offset, mFilename, + BackupStoreException, CouldNotLoadStoreInfo); + } + + return ntohl(refcount); +} + +int64_t BackupStoreRefCountDatabase::GetLastObjectIDUsed() const +{ + return (GetSize() - sizeof(refcount_StreamFormat)) / + sizeof(refcount_t); +} + +void BackupStoreRefCountDatabase::AddReference(int64_t ObjectID) +{ + refcount_t refcount; + + if (ObjectID > GetLastObjectIDUsed()) + { + // new object, assume no previous references + refcount = 0; + } + else + { + // read previous value from database + refcount = GetRefCount(ObjectID); + } + + refcount++; + + SetRefCount(ObjectID, refcount); +} + +void BackupStoreRefCountDatabase::SetRefCount(int64_t ObjectID, + refcount_t NewRefCount) +{ + IOStream::pos_type offset = GetOffset(ObjectID); + mapDatabaseFile->Seek(offset, SEEK_SET); + refcount_t RefCountNetOrder = htonl(NewRefCount); + mapDatabaseFile->Write(&RefCountNetOrder, sizeof(RefCountNetOrder)); + mIsModified = true; +} + +bool BackupStoreRefCountDatabase::RemoveReference(int64_t ObjectID) +{ + refcount_t refcount = GetRefCount(ObjectID); // must exist in database + ASSERT(refcount > 0); + refcount--; + SetRefCount(ObjectID, refcount); + return (refcount > 0); +} + +int BackupStoreRefCountDatabase::ReportChangesTo(BackupStoreRefCountDatabase& rOldRefs) +{ + int ErrorCount = 0; + int64_t MaxOldObjectId = rOldRefs.GetLastObjectIDUsed(); + int64_t MaxNewObjectId = GetLastObjectIDUsed(); + + for (int64_t ObjectID = BACKUPSTORE_ROOT_DIRECTORY_ID; + ObjectID < std::max(MaxOldObjectId, MaxNewObjectId); + ObjectID++) + { + typedef BackupStoreRefCountDatabase::refcount_t refcount_t; + refcount_t OldRefs = (ObjectID <= MaxOldObjectId) ? + rOldRefs.GetRefCount(ObjectID) : 0; + refcount_t NewRefs = (ObjectID <= MaxNewObjectId) ? + this->GetRefCount(ObjectID) : 0; + + if (OldRefs != NewRefs) + { + BOX_WARNING("Reference count of object " << + BOX_FORMAT_OBJECTID(ObjectID) << + " changed from " << OldRefs << + " to " << NewRefs); + ErrorCount++; + } + } + + return ErrorCount; +} diff --git a/lib/backupstore/BackupStoreRefCountDatabase.h b/lib/backupstore/BackupStoreRefCountDatabase.h new file mode 100644 index 00000000..915653a4 --- /dev/null +++ b/lib/backupstore/BackupStoreRefCountDatabase.h @@ -0,0 +1,126 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreRefCountDatabase.h +// Purpose: Main backup store information storage +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREREFCOUNTDATABASE__H +#define BACKUPSTOREREFCOUNTDATABASE__H + +#include <memory> +#include <string> +#include <vector> + +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreConstants.h" +#include "FileStream.h" + +class BackupStoreCheck; +class BackupStoreContext; + +// set packing to one byte +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "BeginStructPackForWire.h" +#else +BEGIN_STRUCTURE_PACKING_FOR_WIRE +#endif + +typedef struct +{ + uint32_t mMagicValue; // also the version number + uint32_t mAccountID; +} refcount_StreamFormat; + +// Use default packing +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "EndStructPackForWire.h" +#else +END_STRUCTURE_PACKING_FOR_WIRE +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreRefCountDatabase +// Purpose: Backup store reference count database storage +// Created: 2009/06/01 +// +// -------------------------------------------------------------------------- +class BackupStoreRefCountDatabase +{ + friend class BackupStoreCheck; + friend class BackupStoreContext; + friend class HousekeepStoreAccount; + +public: + ~BackupStoreRefCountDatabase(); +private: + // Creation through static functions only + BackupStoreRefCountDatabase(const BackupStoreAccountDatabase::Entry& + rAccount, bool ReadOnly, bool Temporary, + std::auto_ptr<FileStream> apDatabaseFile); + // No copying allowed + BackupStoreRefCountDatabase(const BackupStoreRefCountDatabase &); + +public: + // Create a blank database, using a temporary file that you must + // Discard() or Commit() to make permanent. + static std::auto_ptr<BackupStoreRefCountDatabase> Create + (const BackupStoreAccountDatabase::Entry& rAccount); + void Commit(); + void Discard(); + + // Load it from the store + static std::auto_ptr<BackupStoreRefCountDatabase> Load(const + BackupStoreAccountDatabase::Entry& rAccount, bool ReadOnly); + + typedef uint32_t refcount_t; + + // Data access functions + refcount_t GetRefCount(int64_t ObjectID) const; + int64_t GetLastObjectIDUsed() const; + + // Data modification functions + void AddReference(int64_t ObjectID); + // RemoveReference returns false if refcount drops to zero + bool RemoveReference(int64_t ObjectID); + int ReportChangesTo(BackupStoreRefCountDatabase& rOldRefs); + +private: + static std::string GetFilename(const BackupStoreAccountDatabase::Entry& + rAccount, bool Temporary); + + IOStream::pos_type GetSize() const + { + return mapDatabaseFile->GetPosition() + + mapDatabaseFile->BytesLeftToRead(); + } + IOStream::pos_type GetEntrySize() const + { + return sizeof(refcount_t); + } + IOStream::pos_type GetOffset(int64_t ObjectID) const + { + return ((ObjectID - 1) * GetEntrySize()) + + sizeof(refcount_StreamFormat); + } + void SetRefCount(int64_t ObjectID, refcount_t NewRefCount); + + // Location information + BackupStoreAccountDatabase::Entry mAccount; + std::string mFilename; + bool mReadOnly; + bool mIsModified; + bool mIsTemporaryFile; + std::auto_ptr<FileStream> mapDatabaseFile; + + bool NeedsCommitOrDiscard() + { + return mapDatabaseFile.get() && mIsModified && mIsTemporaryFile; + } +}; + +#endif // BACKUPSTOREREFCOUNTDATABASE__H diff --git a/lib/backupstore/HousekeepStoreAccount.cpp b/lib/backupstore/HousekeepStoreAccount.cpp new file mode 100644 index 00000000..f24d7227 --- /dev/null +++ b/lib/backupstore/HousekeepStoreAccount.cpp @@ -0,0 +1,1152 @@ +// -------------------------------------------------------------------------- +// +// 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), + mErrorCount(0), + mBlocksUsed(0), + mBlocksInOldFiles(0), + mBlocksInDeletedFiles(0), + mBlocksInDirectories(0), + mBlocksUsedDelta(0), + mBlocksInOldFilesDelta(0), + mBlocksInDeletedFilesDelta(0), + mBlocksInDirectoriesDelta(0), + mFilesDeleted(0), + mEmptyDirectoriesDeleted(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() +{ + if(mapNewRefs.get()) + { + // Discard() can throw exception, but destructors aren't supposed to do that, so + // just catch and log them. + try + { + mapNewRefs->Discard(); + } + catch(BoxException &e) + { + BOX_ERROR("Failed to destroy housekeeper: discarding the refcount " + "database threw an exception: " << e.what()); + } + } +} + +// -------------------------------------------------------------------------- +// +// 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_INFO("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; + } + + BackupStoreAccountDatabase::Entry account(mAccountID, mStoreDiscSet); + mapNewRefs = BackupStoreRefCountDatabase::Create(account); + + // Scan the directory for potential things to delete + // This will also remove eligible items marked with RemoveASAP + bool continueHousekeeping = ScanDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, + *info); + + if(!continueHousekeeping) + { + // The scan was incomplete, so the new block counts are + // incorrect, we can't rely on them. It's better to discard + // the new info and adjust the old one instead. + info = pOldInfo; + + // We're about to reset counters and exit, so report what + // happened now. + BOX_INFO("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(mAccountID) << " removed " << + (0 - mBlocksUsedDelta) << " blocks (" << + mFilesDeleted << " files, " << + mEmptyDirectoriesDeleted << " dirs) and the directory " + "scan was interrupted"); + } + + // If housekeeping made any changes, such as deleting RemoveASAP files, + // the differences in block counts will be recorded in the deltas. + info->ChangeBlocksUsed(mBlocksUsedDelta); + info->ChangeBlocksInOldFiles(mBlocksInOldFilesDelta); + info->ChangeBlocksInDeletedFiles(mBlocksInDeletedFilesDelta); + + // Reset the delta counts for files, as they will include + // RemoveASAP flagged files deleted during the initial scan. + // keep removeASAPBlocksUsedDelta for reporting + int64_t removeASAPBlocksUsedDelta = mBlocksUsedDelta; + mBlocksUsedDelta = 0; + mBlocksInOldFilesDelta = 0; + mBlocksInDeletedFilesDelta = 0; + + // If scan directory stopped for some reason, probably parent + // instructed to terminate, stop now. + // + // 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.). + + if(!continueHousekeeping) + { + mapNewRefs->Discard(); + info->Save(); + return false; + } + + // Report any UNexpected changes, and consider them to be errors. + // Do this before applying the expected changes below. + mErrorCount += info->ReportChangesTo(*pOldInfo); + info->Save(); + + // Try to load the old reference count database and check whether + // any counts have changed. We want to compare the mapNewRefs to + // apOldRefs before we delete any files, because that will also change + // the reference count in a way that's not an error. + + try + { + std::auto_ptr<BackupStoreRefCountDatabase> apOldRefs = + BackupStoreRefCountDatabase::Load(account, false); + mErrorCount += mapNewRefs->ReportChangesTo(*apOldRefs); + } + catch(BoxException &e) + { + BOX_WARNING("Reference count database was missing or " + "corrupted during housekeeping, cannot check it for " + "errors."); + mErrorCount++; + } + + // Go and delete items from the accounts + bool deleteInterrupted = DeleteFiles(*info); + + // If that wasn't interrupted, remove any empty directories which + // are also marked as deleted in their containing directory + if(!deleteInterrupted) + { + deleteInterrupted = DeleteEmptyDirectories(*info); + } + + // 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":"")); + } + + // 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->Save(); + + // force file to be saved and closed before releasing the lock below + mapNewRefs->Commit(); + mapNewRefs.reset(); + + // 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, + BackupStoreInfo& rBackupStoreInfo) +{ +#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); + dir.SetUserInfo1_SizeInBlocks(originalDirSizeInBlocks); + 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 + mapNewRefs->AddReference(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 + && (en->IsDeleted() || en->IsOld())) + { + // Delete this immediately. + DeleteFile(ObjectID, en->GetObjectID(), dir, + objectFilename, rBackupStoreInfo); + + // 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(en->IsOld()) mBlocksInOldFiles += enSizeInBlocks; + if(en->IsDeleted()) 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(en->IsOld() || en->IsDeleted()) + { + // 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 = en->IsDeleted(); + + // 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) + { + ASSERT(en->IsDir()); + + if(!ScanDirectory(en->GetObjectID(), rBackupStoreInfo)) + { + // 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(BackupStoreInfo& rBackupStoreInfo) +{ + // 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; + { + MakeObjectFilename(i->mInDirectory, dirFilename); + std::auto_ptr<RaidFileRead> dirStream(RaidFileRead::Open(mStoreDiscSet, dirFilename)); + dir.ReadFromStream(*dirStream, IOStream::TimeOutInfinite); + dir.SetUserInfo1_SizeInBlocks(dirStream->GetDiscUsageInBlocks()); + } + + // Delete the file + BackupStoreRefCountDatabase::refcount_t refs = + DeleteFile(i->mInDirectory, i->mObjectID, dir, + dirFilename, rBackupStoreInfo); + if(refs == 0) + { + BOX_INFO("Housekeeping removed " << + (i->mIsFlagDeleted ? "deleted" : "old") << + " file " << BOX_FORMAT_OBJECTID(i->mObjectID) << + " from dir " << BOX_FORMAT_OBJECTID(i->mInDirectory)); + } + else + { + BOX_TRACE("Housekeeping preserved " << + (i->mIsFlagDeleted ? "deleted" : "old") << + " file " << BOX_FORMAT_OBJECTID(i->mObjectID) << + " in dir " << BOX_FORMAT_OBJECTID(i->mInDirectory) << + " with " << refs << " references"); + } + + // 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. Returns the number of references +// remaining. If it's zero, the file was removed from +// disk as unused. +// Created: 15/7/04 +// +// -------------------------------------------------------------------------- + +BackupStoreRefCountDatabase::refcount_t HousekeepStoreAccount::DeleteFile( + int64_t InDirectory, int64_t ObjectID, BackupStoreDirectory &rDirectory, + const std::string &rDirectoryFilename, + BackupStoreInfo& rBackupStoreInfo) +{ + // 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 + { + BackupStoreRefCountDatabase::refcount_t refs = + mapNewRefs->GetRefCount(ObjectID); + + 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"); + mErrorCount++; + return refs; + } + + // Record the flags it's got set + wasDeleted = pentry->IsDeleted(); + wasOldVersion = pentry->IsOld(); + // Check this should be deleted + if(!wasDeleted && !wasOldVersion) + { + // Things changed since we were last around + return refs; + } + + // Record size + deletedFileSizeInBlocks = pentry->GetSizeInBlocks(); + + if(refs > 1) + { + // Not safe to merge patches if someone else has a + // reference to this object, so just remove the + // directory entry and return. + rDirectory.DeleteEntry(ObjectID); + if(wasDeleted) + { + rBackupStoreInfo.AdjustNumDeletedFiles(-1); + } + + if(wasOldVersion) + { + rBackupStoreInfo.AdjustNumOldFiles(-1); + } + + mapNewRefs->RemoveReference(ObjectID); + return refs - 1; + } + + // 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, mapNewRefs->GetRefCount(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->IsDeleted()) + { + mBlocksInDeletedFilesDelta += sizeDelta; + } + if(polder->IsOld()) + { + mBlocksInOldFilesDelta += sizeDelta; + } + polder->SetSizeInBlocks(newSize); + } + + // pentry no longer valid + } + + // Delete it from the directory + rDirectory.DeleteEntry(ObjectID); + + // Save directory back to disc + // BLOCK + { + RaidFileWrite writeDir(mStoreDiscSet, rDirectoryFilename, + mapNewRefs->GetRefCount(InDirectory)); + writeDir.Open(true /* allow overwriting */); + rDirectory.WriteToStream(writeDir); + + // Get the disc usage (must do this before commiting it) + int64_t new_size = writeDir.GetDiscUsageInBlocks(); + + // Commit directory + writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); + + // Adjust block counts if the directory itself changed in size + int64_t original_size = rDirectory.GetUserInfo1_SizeInBlocks(); + int64_t adjust = new_size - original_size; + mBlocksUsedDelta += adjust; + mBlocksInDirectoriesDelta += adjust; + + UpdateDirectorySize(rDirectory, new_size); + } + + // 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. Must now be zero, to delete the file. + bool remaining_refs = mapNewRefs->RemoveReference(ObjectID); + ASSERT(!remaining_refs); + + // Delete from disc + BOX_TRACE("Removing unreferenced object " << + BOX_FORMAT_OBJECTID(ObjectID)); + std::string objFilename; + MakeObjectFilename(ObjectID, objFilename); + RaidFileWrite del(mStoreDiscSet, objFilename, mapNewRefs->GetRefCount(ObjectID)); + del.Delete(); + + // Adjust counts for the file + ++mFilesDeleted; + mBlocksUsedDelta -= deletedFileSizeInBlocks; + + if(wasDeleted) + { + mBlocksInDeletedFilesDelta -= deletedFileSizeInBlocks; + rBackupStoreInfo.AdjustNumDeletedFiles(-1); + } + + if(wasOldVersion) + { + mBlocksInOldFilesDelta -= deletedFileSizeInBlocks; + rBackupStoreInfo.AdjustNumOldFiles(-1); + } + + // 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); + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HousekeepStoreAccount::UpdateDirectorySize( +// BackupStoreDirectory& rDirectory, +// IOStream::pos_type new_size_in_blocks) +// Purpose: Update the directory size, modifying the parent +// directory's entry for this directory if necessary. +// Created: 05/03/14 +// +// -------------------------------------------------------------------------- + +void HousekeepStoreAccount::UpdateDirectorySize( + BackupStoreDirectory& rDirectory, + IOStream::pos_type new_size_in_blocks) +{ +#ifndef NDEBUG + { + std::string dirFilename; + MakeObjectFilename(rDirectory.GetObjectID(), dirFilename); + std::auto_ptr<RaidFileRead> dirStream( + RaidFileRead::Open(mStoreDiscSet, dirFilename)); + ASSERT(new_size_in_blocks == dirStream->GetDiscUsageInBlocks()); + } +#endif + + IOStream::pos_type old_size_in_blocks = + rDirectory.GetUserInfo1_SizeInBlocks(); + + if(new_size_in_blocks == old_size_in_blocks) + { + return; + } + + rDirectory.SetUserInfo1_SizeInBlocks(new_size_in_blocks); + + if (rDirectory.GetObjectID() == BACKUPSTORE_ROOT_DIRECTORY_ID) + { + return; + } + + std::string parentFilename; + MakeObjectFilename(rDirectory.GetContainerID(), parentFilename); + std::auto_ptr<RaidFileRead> parentStream( + RaidFileRead::Open(mStoreDiscSet, parentFilename)); + BackupStoreDirectory parent(*parentStream); + parentStream.reset(); + + BackupStoreDirectory::Entry* en = + parent.FindEntryByID(rDirectory.GetObjectID()); + ASSERT(en); + + if (en->GetSizeInBlocks() != old_size_in_blocks) + { + BOX_WARNING("Directory " << + BOX_FORMAT_OBJECTID(rDirectory.GetObjectID()) << + " entry in directory " << + BOX_FORMAT_OBJECTID(rDirectory.GetContainerID()) << + " had incorrect size " << en->GetSizeInBlocks() << + ", should have been " << old_size_in_blocks); + mErrorCount++; + } + + en->SetSizeInBlocks(new_size_in_blocks); + + RaidFileWrite writeDir(mStoreDiscSet, parentFilename, + mapNewRefs->GetRefCount(rDirectory.GetContainerID())); + writeDir.Open(true /* allow overwriting */); + parent.WriteToStream(writeDir); + writeDir.Commit(BACKUP_STORE_CONVERT_TO_RAID_IMMEDIATELY); +} + +// -------------------------------------------------------------------------- +// +// 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(BackupStoreInfo& rBackupStoreInfo) +{ + 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, rBackupStoreInfo); + } + + // 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, BackupStoreInfo& rBackupStoreInfo) +{ + // 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); + containingDir.SetUserInfo1_SizeInBlocks(containingDirSizeInBlocksOrig); + } + + // Find the entry + BackupStoreDirectory::Entry *pdirentry = + containingDir.FindEntryByID(dir.GetObjectID()); + // TODO FIXME invert test and reduce indentation + if((pdirentry != 0) && pdirentry->IsDeleted()) + { + // 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, + mapNewRefs->GetRefCount(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); + UpdateDirectorySize(containingDir, dirSize); + + // adjust usage counts for this directory + if(dirSize > 0) + { + int64_t adjust = dirSize - containingDirSizeInBlocksOrig; + mBlocksUsedDelta += adjust; + mBlocksInDirectoriesDelta += adjust; + } + + if (mapNewRefs->RemoveReference(dir.GetObjectID())) + { + // Still referenced + BOX_TRACE("Housekeeping spared empty deleted dir " << + BOX_FORMAT_OBJECTID(dirId) << " due to " << + mapNewRefs->GetRefCount(dir.GetObjectID()) << + "remaining references"); + return; + } + + // Delete the directory itself + BOX_INFO("Housekeeping removing empty deleted dir " << + BOX_FORMAT_OBJECTID(dirId)); + RaidFileWrite del(mStoreDiscSet, dirFilename, + mapNewRefs->GetRefCount(dir.GetObjectID())); + del.Delete(); + + // And adjust usage counts for the directory that's + // just been deleted + mBlocksUsedDelta -= dirSizeInBlocks; + mBlocksInDirectoriesDelta -= dirSizeInBlocks; + + // Update count + ++mEmptyDirectoriesDeleted; + rBackupStoreInfo.AdjustNumDirectories(-1); + } +} + diff --git a/lib/backupstore/HousekeepStoreAccount.h b/lib/backupstore/HousekeepStoreAccount.h new file mode 100644 index 00000000..ff9e9ffe --- /dev/null +++ b/lib/backupstore/HousekeepStoreAccount.h @@ -0,0 +1,121 @@ +// -------------------------------------------------------------------------- +// +// 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> + +#include "BackupStoreRefCountDatabase.h" + +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 GetErrorCount() { return mErrorCount; } + +private: + // utility functions + void MakeObjectFilename(int64_t ObjectID, std::string &rFilenameOut); + + bool ScanDirectory(int64_t ObjectID, BackupStoreInfo& rBackupStoreInfo); + bool DeleteFiles(BackupStoreInfo& rBackupStoreInfo); + bool DeleteEmptyDirectories(BackupStoreInfo& rBackupStoreInfo); + void DeleteEmptyDirectory(int64_t dirId, std::vector<int64_t>& rToExamine, + BackupStoreInfo& rBackupStoreInfo); + BackupStoreRefCountDatabase::refcount_t DeleteFile(int64_t InDirectory, + int64_t ObjectID, + BackupStoreDirectory &rDirectory, + const std::string &rDirectoryFilename, + BackupStoreInfo& rBackupStoreInfo); + void UpdateDirectorySize(BackupStoreDirectory &rDirectory, + IOStream::pos_type new_size_in_blocks); + + 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; + + // Count of errors found and fixed + int64_t mErrorCount; + + // 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::auto_ptr<BackupStoreRefCountDatabase> mapNewRefs; + + // 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/StoreStructure.cpp b/lib/backupstore/StoreStructure.cpp new file mode 100644 index 00000000..45a1ce91 --- /dev/null +++ b/lib/backupstore/StoreStructure.cpp @@ -0,0 +1,95 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreStructure.cpp +// Purpose: +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "StoreStructure.h" +#include "RaidFileRead.h" +#include "RaidFileWrite.h" +#include "RaidFileController.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StoreStructure::MakeObjectFilename(int64_t, const std::string &, int, std::string &, bool) +// Purpose: Builds the object filename for a given object, given a root. Optionally ensure that the +// directory exists. +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void StoreStructure::MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists) +{ + const static char *hex = "0123456789abcdef"; + + // Set output to root string + rFilenameOut = rStoreRoot; + + // get the id value from the stored object ID so we can do + // bitwise operations on it. + uint64_t id = (uint64_t)ObjectID; + + // get leafname, shift the bits which make up the leafname off + unsigned int leafname(id & STORE_ID_SEGMENT_MASK); + id >>= STORE_ID_SEGMENT_LENGTH; + + // build pathname + while(id != 0) + { + // assumes that the segments are no bigger than 8 bits + int v = id & STORE_ID_SEGMENT_MASK; + rFilenameOut += hex[(v & 0xf0) >> 4]; + rFilenameOut += hex[v & 0xf]; + rFilenameOut += DIRECTORY_SEPARATOR_ASCHAR; + + // shift the bits we used off the pathname + id >>= STORE_ID_SEGMENT_LENGTH; + } + + // Want to make sure this exists? + if(EnsureDirectoryExists) + { + if(!RaidFileRead::DirectoryExists(DiscSet, rFilenameOut)) + { + // Create it + RaidFileWrite::CreateDirectory(DiscSet, rFilenameOut, true /* recusive */); + } + } + + // append the filename + rFilenameOut += 'o'; + rFilenameOut += hex[(leafname & 0xf0) >> 4]; + rFilenameOut += hex[leafname & 0xf]; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StoreStructure::MakeWriteLockFilename(const std::string &, int, std::string &) +// Purpose: Generate the on disc filename of the write lock file +// Created: 15/12/03 +// +// -------------------------------------------------------------------------- +void StoreStructure::MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut) +{ + // Find the disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet &rdiscSet(rcontroller.GetDiscSet(DiscSet)); + + // Make the filename + std::string writeLockFile(rdiscSet[0] + DIRECTORY_SEPARATOR + rStoreRoot + "write.lock"); + + // Return it to the caller + rFilenameOut = writeLockFile; +} + + diff --git a/lib/backupstore/StoreStructure.h b/lib/backupstore/StoreStructure.h new file mode 100644 index 00000000..ffbe83dd --- /dev/null +++ b/lib/backupstore/StoreStructure.h @@ -0,0 +1,32 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreStructure.h +// Purpose: Functions for placing files in the store +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef STORESTRUCTURE__H +#define STORESTRUCTURE__H + +#include <string> + +#ifdef BOX_RELEASE_BUILD + #define STORE_ID_SEGMENT_LENGTH 8 + #define STORE_ID_SEGMENT_MASK 0xff +#else + // Debug we'll use lots and lots of directories to stress things + #define STORE_ID_SEGMENT_LENGTH 2 + #define STORE_ID_SEGMENT_MASK 0x03 +#endif + + +namespace StoreStructure +{ + void MakeObjectFilename(int64_t ObjectID, const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut, bool EnsureDirectoryExists); + void MakeWriteLockFilename(const std::string &rStoreRoot, int DiscSet, std::string &rFilenameOut); +}; + +#endif // STORESTRUCTURE__H + diff --git a/lib/backupstore/StoreTestUtils.cpp b/lib/backupstore/StoreTestUtils.cpp new file mode 100644 index 00000000..2b773cb1 --- /dev/null +++ b/lib/backupstore/StoreTestUtils.cpp @@ -0,0 +1,300 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreTestUtils.cpp +// Purpose: Utilities for housekeeping and checking a test store +// Created: 18/02/14 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <cstdio> +#include <vector> + +#include "autogen_BackupProtocol.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreInfo.h" +#include "HousekeepStoreAccount.h" +#include "Logging.h" +#include "ServerControl.h" +#include "SocketStreamTLS.h" +#include "StoreTestUtils.h" +#include "TLSContext.h" +#include "Test.h" + +bool create_account(int soft, int hard) +{ + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + BackupStoreAccountsControl control(*config); + + Logger::LevelGuard guard(Logging::GetConsole(), Log::WARNING); + int result = control.CreateAccount(0x01234567, 0, soft, hard); + TEST_EQUAL(0, result); + return (result == 0); +} + +bool delete_account() +{ + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + BackupStoreAccountsControl control(*config); + Logger::LevelGuard guard(Logging::GetConsole(), Log::WARNING); + TEST_THAT_THROWONFAIL(control.DeleteAccount(0x01234567, false) == 0); + return true; +} + +std::vector<uint32_t> ExpectedRefCounts; +int bbstored_pid = 0, bbackupd_pid = 0; + +void set_refcount(int64_t ObjectID, uint32_t RefCount) +{ + if ((int64_t)ExpectedRefCounts.size() <= ObjectID) + { + ExpectedRefCounts.resize(ObjectID + 1, 0); + } + ExpectedRefCounts[ObjectID] = RefCount; + for (size_t i = ExpectedRefCounts.size() - 1; i >= 1; i--) + { + if (ExpectedRefCounts[i] == 0) + { + // BackupStoreCheck and housekeeping will both + // regenerate the refcount DB without any missing + // items at the end, so we need to prune ourselves + // of all items with no references to match. + ExpectedRefCounts.resize(i); + } + } +} + +void init_context(TLSContext& rContext) +{ + rContext.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); +} + +std::auto_ptr<SocketStream> open_conn(const char *hostname, + TLSContext& rContext) +{ + init_context(rContext); + std::auto_ptr<SocketStreamTLS> conn(new SocketStreamTLS); + conn->Open(rContext, Socket::TypeINET, hostname, + BOX_PORT_BBSTORED_TEST); + return static_cast<std::auto_ptr<SocketStream> >(conn); +} + +std::auto_ptr<BackupProtocolCallable> connect_to_bbstored(TLSContext& rContext) +{ + // Make a protocol + std::auto_ptr<BackupProtocolCallable> protocol(new + BackupProtocolClient(open_conn("localhost", rContext))); + + // Check the version + std::auto_ptr<BackupProtocolVersion> serverVersion( + protocol->QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + return protocol; +} + +std::auto_ptr<BackupProtocolCallable> connect_and_login(TLSContext& rContext, + int flags) +{ + // Make a protocol + std::auto_ptr<BackupProtocolCallable> protocol = + connect_to_bbstored(rContext); + + // Login + protocol->QueryLogin(0x01234567, flags); + + return protocol; +} + +bool check_num_files(int files, int old, int deleted, int dirs) +{ + std::auto_ptr<BackupStoreInfo> apInfo = + BackupStoreInfo::Load(0x1234567, + "backup/01234567/", 0, true); + TEST_EQUAL_LINE(files, apInfo->GetNumCurrentFiles(), + "current files"); + TEST_EQUAL_LINE(old, apInfo->GetNumOldFiles(), + "old files"); + TEST_EQUAL_LINE(deleted, apInfo->GetNumDeletedFiles(), + "deleted files"); + TEST_EQUAL_LINE(dirs, apInfo->GetNumDirectories(), + "directories"); + + return (files == apInfo->GetNumCurrentFiles() && + old == apInfo->GetNumOldFiles() && + deleted == apInfo->GetNumDeletedFiles() && + dirs == apInfo->GetNumDirectories()); +} + +bool check_num_blocks(BackupProtocolCallable& Client, int Current, int Old, + int Deleted, int Dirs, int Total) +{ + std::auto_ptr<BackupProtocolAccountUsage2> usage = + Client.QueryGetAccountUsage2(); + TEST_EQUAL_LINE(Total, usage->GetBlocksUsed(), "wrong BlocksUsed"); + TEST_EQUAL_LINE(Current, usage->GetBlocksInCurrentFiles(), + "wrong BlocksInCurrentFiles"); + TEST_EQUAL_LINE(Old, usage->GetBlocksInOldFiles(), + "wrong BlocksInOldFiles"); + TEST_EQUAL_LINE(Deleted, usage->GetBlocksInDeletedFiles(), + "wrong BlocksInDeletedFiles"); + TEST_EQUAL_LINE(Dirs, usage->GetBlocksInDirectories(), + "wrong BlocksInDirectories"); + return (Total == usage->GetBlocksUsed() && + Current == usage->GetBlocksInCurrentFiles() && + Old == usage->GetBlocksInOldFiles() && + Deleted == usage->GetBlocksInDeletedFiles() && + Dirs == usage->GetBlocksInDirectories()); +} + +bool change_account_limits(const char* soft, const char* hard) +{ + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + BackupStoreAccountsControl control(*config); + int result = control.SetLimit(0x01234567, soft, hard); + TEST_EQUAL(0, result); + return (result == 0); +} + +int check_account_for_errors(Log::Level log_level) +{ + Logger::LevelGuard guard(Logging::GetConsole(), log_level); + Logging::Tagger tag("check fix", true); + Logging::ShowTagOnConsole show; + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify("testfiles/bbstored.conf", + &BackupConfigFileVerify, errs)); + BackupStoreAccountsControl control(*config); + int errors_fixed = control.CheckAccount(0x01234567, + true, // FixErrors + false, // Quiet + true); // ReturnNumErrorsFound + return errors_fixed; +} + +bool check_account(Log::Level log_level) +{ + int errors_fixed = check_account_for_errors(log_level); + TEST_EQUAL(0, errors_fixed); + return (errors_fixed == 0); +} + +int64_t run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount) +{ + std::string rootDir = BackupStoreAccounts::GetAccountRoot(rAccount); + int discSet = rAccount.GetDiscSet(); + + // Do housekeeping on this account + HousekeepStoreAccount housekeeping(rAccount.GetID(), rootDir, + discSet, NULL); + TEST_THAT(housekeeping.DoHousekeeping(true /* keep trying forever */)); + return housekeeping.GetErrorCount(); +} + +// 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 + +bool run_housekeeping_and_check_account() +{ + int error_count; + + { + Logging::Tagger tag("", true); + Logging::ShowTagOnConsole show; + std::auto_ptr<BackupStoreAccountDatabase> apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccountDatabase::Entry account = + apAccounts->GetEntry(0x1234567); + error_count = run_housekeeping(account); + } + + TEST_EQUAL_LINE(0, error_count, "housekeeping errors"); + + bool check_account_is_ok = check_account(); + TEST_THAT(check_account_is_ok); + + return error_count == 0 && check_account_is_ok; +} + +bool check_reference_counts() +{ + std::auto_ptr<BackupStoreAccountDatabase> apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccountDatabase::Entry account = + apAccounts->GetEntry(0x1234567); + + std::auto_ptr<BackupStoreRefCountDatabase> apReferences( + BackupStoreRefCountDatabase::Load(account, true)); + TEST_EQUAL(ExpectedRefCounts.size(), + apReferences->GetLastObjectIDUsed() + 1); + + bool counts_ok = true; + + for (unsigned int i = BackupProtocolListDirectory::RootDirectory; + i < ExpectedRefCounts.size(); i++) + { + TEST_EQUAL_LINE(ExpectedRefCounts[i], + apReferences->GetRefCount(i), + "object " << BOX_FORMAT_OBJECTID(i)); + if (ExpectedRefCounts[i] != apReferences->GetRefCount(i)) + { + counts_ok = false; + } + } + + return counts_ok; +} + +bool StartServer() +{ + bbstored_pid = StartDaemon(bbstored_pid, + BBSTORED " " + bbstored_args + " testfiles/bbstored.conf", + "testfiles/bbstored.pid"); + return bbstored_pid != 0; +} + +bool StopServer(bool wait_for_process) +{ + bool result = StopDaemon(bbstored_pid, "testfiles/bbstored.pid", + "bbstored.memleaks", wait_for_process); + bbstored_pid = 0; + return result; +} + +bool StartClient(const std::string& bbackupd_conf_file) +{ + bbackupd_pid = StartDaemon(bbackupd_pid, + BBACKUPD " " + bbackupd_args + " " + bbackupd_conf_file, + "testfiles/bbackupd.pid"); + return bbackupd_pid != 0; +} + +bool StopClient(bool wait_for_process) +{ + bool result = StopDaemon(bbackupd_pid, "testfiles/bbackupd.pid", + "bbackupd.memleaks", wait_for_process); + bbackupd_pid = 0; + return result; +} + diff --git a/lib/backupstore/StoreTestUtils.h b/lib/backupstore/StoreTestUtils.h new file mode 100644 index 00000000..b3faebb5 --- /dev/null +++ b/lib/backupstore/StoreTestUtils.h @@ -0,0 +1,118 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StoreTestUtils.h +// Purpose: Utilities for housekeeping and checking a test store +// Created: 18/02/14 +// +// -------------------------------------------------------------------------- + +#ifndef STORETESTUTILS__H +#define STORETESTUTILS__H + +#include "Test.h" + +class BackupProtocolCallable; +class BackupProtocolClient; +class SocketStreamTLS; +class TLSContext; + +//! Holds the expected reference counts of each object. +extern std::vector<uint32_t> ExpectedRefCounts; + +//! Holds the PID of the currently running bbstored test server. +extern int bbstored_pid, bbackupd_pid; + +//! Sets the expected refcount of an object, resizing vector if necessary. +void set_refcount(int64_t ObjectID, uint32_t RefCount = 1); + +//! Initialises a TLSContext object using the standard certficate filenames. +void init_context(TLSContext& rContext); + +//! Opens a connection to the server (bbstored). +std::auto_ptr<SocketStream> open_conn(const char *hostname, + TLSContext& rContext); + +//! Opens a connection to the server (bbstored) without logging in. +std::auto_ptr<BackupProtocolCallable> connect_to_bbstored(TLSContext& rContext); + +//! Opens a connection to the server (bbstored) and logs in. +std::auto_ptr<BackupProtocolCallable> connect_and_login(TLSContext& rContext, + int flags = 0); + +//! Checks the number of files of each type in the store against expectations. +bool check_num_files(int files, int old, int deleted, int dirs); + +//! Checks the number of blocks in files of each type against expectations. +bool check_num_blocks(BackupProtocolCallable& Client, int Current, int Old, + int Deleted, int Dirs, int Total); + +//! Change the soft and hard limits on the test account. +bool change_account_limits(const char* soft, const char* hard); + +//! Checks an account for errors, returning the number of errors found and fixed. +int check_account_for_errors(Log::Level log_level = Log::WARNING); + +//! Checks an account for errors, returning true if it's OK, for use in assertions. +bool check_account(Log::Level log_level = Log::WARNING); + +//! Runs housekeeping on an account, to remove old and deleted files if necessary. +int64_t run_housekeeping(BackupStoreAccountDatabase::Entry& rAccount); + +//! Runs housekeeping and checks the account, returning true if it's OK. +bool run_housekeeping_and_check_account(); + +//! Tests that all object reference counts have the expected values. +bool check_reference_counts(); + +//! Starts the bbstored test server running, which must not already be running. +bool StartServer(); + +//! Stops the currently running bbstored test server. +bool StopServer(bool wait_for_process = false); + +//! Starts the bbackupd client running, which must not already be running. +bool StartClient(const std::string& bbackupd_conf_file = "testfiles/bbackupd.conf"); + +//! Stops the currently running bbackupd client. +bool StopClient(bool wait_for_process = false); + +//! Creates the standard test account, for example after delete_account(). +bool create_account(int soft, int hard); + +//! Deletes the standard test account, for testing behaviour with no account. +bool delete_account(); + +#define TEST_PROTOCOL_ERROR_OR(protocol, error, or_statements) \ + { \ + int type, subtype; \ + (protocol).GetLastError(type, subtype); \ + if (type == BackupProtocolError::ErrorType) \ + { \ + TEST_EQUAL_LINE(BackupProtocolError::error, subtype, \ + "command returned error: " << \ + BackupProtocolError::GetMessage(subtype)); \ + if (subtype != BackupProtocolError::error) \ + { \ + or_statements; \ + } \ + } \ + else \ + { \ + TEST_FAIL_WITH_MESSAGE("command did not return an error, but a " \ + "response of type " << type << ", subtype " << subtype << \ + " instead"); \ + or_statements; \ + } \ + } + +#define TEST_COMMAND_RETURNS_ERROR_OR(protocol, command, error, or_statements) \ + TEST_CHECK_THROWS_AND_OR((protocol) . command, ConnectionException, \ + Protocol_UnexpectedReply, /* and_command */, or_statements); \ + TEST_PROTOCOL_ERROR_OR(protocol, error, or_statements) + +#define TEST_COMMAND_RETURNS_ERROR(protocol, command, error) \ + TEST_COMMAND_RETURNS_ERROR_OR(protocol, command, error,) + +#endif // STORETESTUTILS__H + diff --git a/lib/backupstore/backupprotocol.txt b/lib/backupstore/backupprotocol.txt new file mode 100644 index 00000000..5921d009 --- /dev/null +++ b/lib/backupstore/backupprotocol.txt @@ -0,0 +1,266 @@ +# +# backup protocol definition +# + +Name Backup +IdentString Box-Backup:v=C +ServerContextClass BackupStoreContext BackupStoreContext.h + +AddType Filename BackupStoreFilenameClear BackupStoreFilenameClear.h +AddType String std::string + +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 + # reply has stream following (if successful) + + +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 + + +CreateDirectory2 46 Command(Success) StreamWithCommand + int64 ContainingDirectoryID + int64 AttributesModTime + int64 ModificationTime + 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 + +GetAccountUsage2 44 Command(AccountUsage2) + # no data members + +AccountUsage2 45 Reply + String AccountName + bool AccountEnabled + int64 ClientStoreMarker + int32 BlockSize + int64 LastObjectIDUsed + int64 BlocksUsed + int64 BlocksInCurrentFiles + int64 BlocksInOldFiles + int64 BlocksInDeletedFiles + int64 BlocksInDirectories + int64 BlocksSoftLimit + int64 BlocksHardLimit + int64 NumCurrentFiles + int64 NumOldFiles + int64 NumDeletedFiles + int64 NumDirectories + +# 46 is CreateDirectory2 diff --git a/lib/bbackupd/BackupClientContext.cpp b/lib/bbackupd/BackupClientContext.cpp new file mode 100644 index 00000000..4c0b01ce --- /dev/null +++ b/lib/bbackupd/BackupClientContext.cpp @@ -0,0 +1,578 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientContext.cpp +// Purpose: Keep track of context +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SIGNAL_H + #include <signal.h> +#endif + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#include "BoxPortsAndFiles.h" +#include "BoxTime.h" +#include "BackupClientContext.h" +#include "SocketStreamTLS.h" +#include "Socket.h" +#include "BackupStoreConstants.h" +#include "BackupStoreException.h" +#include "BackupDaemon.h" +#include "autogen_BackupProtocol.h" +#include "BackupStoreFile.h" +#include "Logging.h" +#include "TcpNice.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::BackupClientContext(BackupDaemon &, TLSContext &, const std::string &, int32_t, bool, bool, std::string) +// Purpose: Constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientContext::BackupClientContext +( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier& rProgressNotifier, + bool TcpNiceMode +) +: 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), + mpNice(NULL) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::~BackupClientContext() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientContext::~BackupClientContext() +{ + CloseAnyOpenConnection(); + + // Delete delete list + if(mpDeleteList != 0) + { + delete mpDeleteList; + mpDeleteList = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetConnection() +// Purpose: Returns the connection, making the connection and logging into +// the backup store if necessary. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupProtocolCallable &BackupClientContext::GetConnection() +{ + // Already got it? Just return it. + if(mapConnection.get()) + { + return *mapConnection; + } + + // Defensive. Must close connection before releasing any old socket. + mapConnection.reset(); + + std::auto_ptr<SocketStream> apSocket(new SocketStreamTLS); + + try + { + // Defensive. + mapConnection.reset(); + + // Log intention + BOX_INFO("Opening connection to server '" << mHostname << + "'..."); + + // Connect! + ((SocketStreamTLS *)(apSocket.get()))->Open(mrTLSContext, + Socket::TypeINET, mHostname, mPort); + + if(mTcpNiceMode) + { + // Pass control of apSocket to NiceSocketStream, + // which will take care of destroying it for us. + // But we need to hang onto a pointer to the nice + // socket, so we can enable and disable nice mode. + // This is scary, it could be deallocated under us. + mpNice = new NiceSocketStream(apSocket); + apSocket.reset(mpNice); + } + + // We need to call some methods that aren't defined in + // BackupProtocolCallable, so we need to hang onto a more + // strongly typed pointer (to avoid far too many casts). + BackupProtocolClient *pClient = new BackupProtocolClient(apSocket); + mapConnection.reset(pClient); + + // Set logging option + pClient->SetLogToSysLog(mExtendedLogging); + + if (mExtendedLogToFile) + { + ASSERT(mpExtendedLogFileHandle == NULL); + + mpExtendedLogFileHandle = fopen( + mExtendedLogFile.c_str(), "a+"); + + if (!mpExtendedLogFileHandle) + { + BOX_LOG_SYS_ERROR("Failed to open extended " + "log file: " << mExtendedLogFile); + } + else + { + pClient->SetLogToFile(mpExtendedLogFileHandle); + } + } + + // Handshake + pClient->Handshake(); + + // Check the version of the server + { + std::auto_ptr<BackupProtocolVersion> serverVersion( + mapConnection->QueryVersion(BACKUP_STORE_SERVER_VERSION)); + if(serverVersion->GetVersion() != BACKUP_STORE_SERVER_VERSION) + { + THROW_EXCEPTION(BackupStoreException, WrongServerVersion) + } + } + + // Login -- if this fails, the Protocol will exception + 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) + { + if(loginConf->GetClientStoreMarker() != mClientStoreMarker) + { + // Not good... finish the connection, abort, etc, ignoring errors + try + { + mapConnection->QueryFinished(); + } + catch(...) + { + // IGNORE + } + + // Then throw an exception about this + THROW_EXCEPTION_MESSAGE(BackupStoreException, + ClientMarkerNotAsExpected, + "Expected " << mClientStoreMarker << + " but found " << loginConf->GetClientStoreMarker() << + ": is someone else writing to the " + "same account?"); + } + } + else // mClientStoreMarker == ClientStoreMarker_NotKnown + { + // Yes, choose one, the current time will do + box_time_t marker = GetCurrentBoxTime(); + + // Set it on the store + mapConnection->QuerySetClientStoreMarker(marker); + + // Record it so that it can be picked up later. + mClientStoreMarker = marker; + } + + // Log success + BOX_INFO("Connection made, login successful"); + + // Check to see if there is any space available on the server + if(loginConf->GetBlocksUsed() >= loginConf->GetBlocksHardLimit()) + { + // no -- flag so only things like deletions happen + mStorageLimitExceeded = true; + // Log + BOX_WARNING("Exceeded storage hard-limit on server, " + "not uploading changes to files"); + } + } + catch(...) + { + // Clean up. + mapConnection.reset(); + throw; + } + + return *mapConnection; +} + +BackupProtocolCallable* BackupClientContext::GetOpenConnection() const +{ + return mapConnection.get(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::CloseAnyOpenConnection() +// Purpose: Closes a connection, if it's open +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupClientContext::CloseAnyOpenConnection() +{ + BackupProtocolCallable* pConnection(GetOpenConnection()); + if(pConnection) + { + try + { + // Quit nicely + pConnection->QueryFinished(); + } + catch(...) + { + // Ignore errors here + } + + // Delete it anyway. + mapConnection.reset(); + } + + // Delete any pending list + if(mpDeleteList != 0) + { + delete mpDeleteList; + mpDeleteList = 0; + } + + if (mpExtendedLogFileHandle != NULL) + { + fclose(mpExtendedLogFileHandle); + mpExtendedLogFileHandle = NULL; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetTimeout() +// Purpose: Gets the current timeout time. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +int BackupClientContext::GetTimeout() const +{ + BackupProtocolCallable* pConnection(GetOpenConnection()); + if(pConnection) + { + return pConnection->GetTimeout(); + } + + return (15*60*1000); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetDeleteList() +// Purpose: Returns the delete list, creating one if necessary +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList &BackupClientContext::GetDeleteList() +{ + // Already created? + if(mpDeleteList == 0) + { + mpDeleteList = new BackupClientDeleteList; + } + + // Return reference to object + return *mpDeleteList; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::PerformDeletions() +// Purpose: Perform any pending file deletions. +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientContext::PerformDeletions() +{ + // Got a list? + if(mpDeleteList == 0) + { + // Nothing to do + return; + } + + // Delegate to the delete list object + mpDeleteList->PerformDeletions(*this); + + // Delete the object + delete mpDeleteList; + mpDeleteList = 0; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetCurrentIDMap() const +// Purpose: Return a (const) reference to the current ID map +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +const BackupClientInodeToIDMap &BackupClientContext::GetCurrentIDMap() const +{ + ASSERT(mpCurrentIDMap != 0); + if(mpCurrentIDMap == 0) + { + THROW_EXCEPTION(CommonException, Internal) + } + return *mpCurrentIDMap; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::GetNewIDMap() const +// Purpose: Return a reference to the new ID map +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap &BackupClientContext::GetNewIDMap() const +{ + ASSERT(mpNewIDMap != 0); + if(mpNewIDMap == 0) + { + THROW_EXCEPTION(CommonException, Internal) + } + return *mpNewIDMap; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::FindFilename(int64_t, int64_t, std::string &, bool &) const +// Purpose: Attempts to find the pathname of an object with a given ID on the server. +// Returns true if it can be found, in which case rPathOut is the local filename, +// and rIsDirectoryOut == true if the local object is a directory. +// Created: 12/11/03 +// +// -------------------------------------------------------------------------- +bool BackupClientContext::FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, + bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer, box_time_t *pAttributesHashOnServer, BackupStoreFilenameClear *pLeafname) +{ + // Make a connection to the server + BackupProtocolCallable &connection(GetConnection()); + + // Request filenames from the server, in a "safe" manner to ignore errors properly + { + BackupProtocolGetObjectName send(ObjectID, ContainingDirectory); + connection.Send(send); + } + std::auto_ptr<BackupProtocolMessage> preply(connection.Receive()); + + // Is it of the right type? + if(preply->GetType() != BackupProtocolObjectName::TypeID) + { + // Was an error or something + return false; + } + + // Cast to expected type. + BackupProtocolObjectName *names = (BackupProtocolObjectName *)(preply.get()); + + // Anything found? + int32_t numElements = names->GetNumNameElements(); + if(numElements <= 0) + { + // No. + return false; + } + + // Get the stream containing all the names + std::auto_ptr<IOStream> nameStream(connection.ReceiveStream()); + + // Path + std::string path; + + // Remember this is in reverse order! + for(int l = 0; l < numElements; ++l) + { + BackupStoreFilenameClear elementName; + elementName.ReadFromStream(*nameStream, GetTimeout()); + + // Store leafname for caller? + if(l == 0 && pLeafname) + { + *pLeafname = elementName; + } + + // Is it part of the filename in the location? + if(l < (numElements - 1)) + { + // Part of filename within + path = (path.empty())?(elementName.GetClearFilename()):(elementName.GetClearFilename() + DIRECTORY_SEPARATOR_ASCHAR + path); + } + else + { + // Location name -- look up in daemon's records + std::string locPath; + if(!mrResolver.FindLocationPathName(elementName.GetClearFilename(), locPath)) + { + // Didn't find the location... so can't give the local filename + return false; + } + + // Add in location path + path = (path.empty())?(locPath):(locPath + DIRECTORY_SEPARATOR_ASCHAR + path); + } + } + + // Is it a directory? + rIsDirectoryOut = ((names->GetFlags() & BackupProtocolListDirectory::Flags_Dir) == BackupProtocolListDirectory::Flags_Dir); + + // Is it the current version? + rIsCurrentVersionOut = ((names->GetFlags() & (BackupProtocolListDirectory::Flags_OldVersion | BackupProtocolListDirectory::Flags_Deleted)) == 0); + + // And other information which may be required + if(pModTimeOnServer) *pModTimeOnServer = names->GetModificationTime(); + if(pAttributesHashOnServer) *pAttributesHashOnServer = names->GetAttributesHash(); + + // Tell caller about the pathname + rPathOut = path; + + // Found + return true; +} + +void BackupClientContext::SetMaximumDiffingTime(int iSeconds) +{ + mMaximumDiffingTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set maximum diffing time to " << mMaximumDiffingTime << + " seconds"); +} + +void BackupClientContext::SetKeepAliveTime(int iSeconds) +{ + mKeepAliveTime = iSeconds < 0 ? 0 : iSeconds; + BOX_TRACE("Set keep-alive time to " << mKeepAliveTime << " seconds"); + mKeepAliveTimer.Reset(mKeepAliveTime * MILLI_SEC_IN_SEC); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::ManageDiffProcess() +// Purpose: Initiates a file diff control timer +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::ManageDiffProcess() +{ + ASSERT(!mbIsManaged); + mbIsManaged = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::UnManageDiffProcess() +// Purpose: suspends file diff control timer +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::UnManageDiffProcess() +{ + // ASSERT(mbIsManaged); + mbIsManaged = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientContext::DoKeepAlive() +// Purpose: Check whether it's time to send a KeepAlive +// message over the SSL link, and if so, send it. +// Created: 04/19/2005 +// +// -------------------------------------------------------------------------- +void BackupClientContext::DoKeepAlive() +{ + BackupProtocolCallable* pConnection(GetOpenConnection()); + if (!pConnection) + { + return; + } + + if (mKeepAliveTime == 0) + { + return; + } + + if (!mKeepAliveTimer.HasExpired()) + { + return; + } + + BOX_TRACE("KeepAliveTime reached, sending keep-alive message"); + pConnection->QueryGetIsAlive(); + + mKeepAliveTimer.Reset(mKeepAliveTime * MILLI_SEC_IN_SEC); +} + +int BackupClientContext::GetMaximumDiffingTime() +{ + return mMaximumDiffingTime; +} diff --git a/lib/bbackupd/BackupClientContext.h b/lib/bbackupd/BackupClientContext.h new file mode 100644 index 00000000..df43a232 --- /dev/null +++ b/lib/bbackupd/BackupClientContext.h @@ -0,0 +1,252 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientContext.h +// Purpose: Keep track of context +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTCONTEXT__H +#define BACKUPCLIENTCONTEXT__H + +#include "BoxTime.h" +#include "BackupClientDeleteList.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupDaemonInterface.h" +#include "BackupStoreFile.h" +#include "ExcludeList.h" +#include "TcpNice.h" +#include "Timer.h" + +class TLSContext; +class BackupProtocolClient; +class SocketStreamTLS; +class BackupClientInodeToIDMap; +class BackupDaemon; +class BackupStoreFilenameClear; + +#include <string> + + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientContext +// Purpose: +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupClientContext : public DiffTimer +{ +public: + BackupClientContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode + ); + virtual ~BackupClientContext(); + +private: + BackupClientContext(const BackupClientContext &); + +public: + // GetConnection() will open a connection if none is currently open. + virtual BackupProtocolCallable& GetConnection(); + // GetOpenConnection() will not open a connection, just return NULL if there is + // no connection already open. + virtual BackupProtocolCallable* GetOpenConnection() const; + void CloseAnyOpenConnection(); + int GetTimeout() const; + BackupClientDeleteList &GetDeleteList(); + void PerformDeletions(); + + enum + { + ClientStoreMarker_NotKnown = 0 + }; + + void SetClientStoreMarker(int64_t ClientStoreMarker) {mClientStoreMarker = ClientStoreMarker;} + int64_t GetClientStoreMarker() const {return mClientStoreMarker;} + + bool StorageLimitExceeded() {return mStorageLimitExceeded;} + void SetStorageLimitExceeded() {mStorageLimitExceeded = true;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetIDMaps(const BackupClientInodeToIDMap *, BackupClientInodeToIDMap *) + // Purpose: Store pointers to the Current and New ID maps + // Created: 11/11/03 + // + // -------------------------------------------------------------------------- + void SetIDMaps(const BackupClientInodeToIDMap *pCurrent, BackupClientInodeToIDMap *pNew) + { + ASSERT(pCurrent != 0); + ASSERT(pNew != 0); + mpCurrentIDMap = pCurrent; + mpNewIDMap = pNew; + } + const BackupClientInodeToIDMap &GetCurrentIDMap() const; + BackupClientInodeToIDMap &GetNewIDMap() const; + + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetExcludeLists(ExcludeList *, ExcludeList *) + // Purpose: Sets the exclude lists for the operation. Can be 0. + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + void SetExcludeLists(ExcludeList *pExcludeFiles, ExcludeList *pExcludeDirs) + { + mpExcludeFiles = pExcludeFiles; + mpExcludeDirs = pExcludeDirs; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ExcludeFile(const std::string &) + // Purpose: Returns true is this file should be excluded from the backup + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + inline bool ExcludeFile(const std::string &rFullFilename) + { + if(mpExcludeFiles != 0) + { + return mpExcludeFiles->IsExcluded(rFullFilename); + } + // If no list, don't exclude anything + return false; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ExcludeDir(const std::string &) + // Purpose: Returns true is this directory should be excluded from the backup + // Created: 28/1/04 + // + // -------------------------------------------------------------------------- + inline bool ExcludeDir(const std::string &rFullDirName) + { + if(mpExcludeDirs != 0) + { + return mpExcludeDirs->IsExcluded(rFullDirName); + } + // If no list, don't exclude anything + return false; + } + + // Utility functions -- may do a lot of work + bool FindFilename(int64_t ObjectID, int64_t ContainingDirectory, std::string &rPathOut, bool &rIsDirectoryOut, + bool &rIsCurrentVersionOut, box_time_t *pModTimeOnServer = 0, box_time_t *pAttributesHashOnServer = 0, + BackupStoreFilenameClear *pLeafname = 0); // not const as may connect to server + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetMaximumDiffingTime() + // Purpose: Sets the maximum time that will be spent diffing a file + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void SetMaximumDiffingTime(int iSeconds); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::SetKeepAliveTime() + // Purpose: Sets the time interval for repetitive keep-alive operation + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + virtual void SetKeepAliveTime(int iSeconds); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::ManageDiffProcess() + // Purpose: Initiates an SSL connection/session keep-alive process + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void ManageDiffProcess(); + + // -------------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::UnManageDiffProcess() + // Purpose: Suspends an SSL connection/session keep-alive process + // Created: 04/19/2005 + // + // -------------------------------------------------------------------------- + void UnManageDiffProcess(); + + // ------------------------------------------------------------------- + // + // Function + // Name: BackupClientContext::DoKeepAlive() + // Purpose: Check whether it's time to send a KeepAlive + // message over the SSL link, and if so, send it. + // Created: 04/19/2005 + // + // ------------------------------------------------------------------- + virtual void DoKeepAlive(); + virtual int GetMaximumDiffingTime(); + virtual bool IsManaged() { return mbIsManaged; } + + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } + + void SetNiceMode(bool enabled) + { + if(mTcpNiceMode) + { + mpNice->SetEnabled(enabled); + } + } + + bool mExperimentalSnapshotMode; + +private: + LocationResolver &mrResolver; + TLSContext &mrTLSContext; + std::string mHostname; + int mPort; + uint32_t mAccountNumber; + std::auto_ptr<BackupProtocolCallable> mapConnection; + bool mExtendedLogging; + bool mExtendedLogToFile; + std::string mExtendedLogFile; + FILE* mpExtendedLogFileHandle; + int64_t mClientStoreMarker; + BackupClientDeleteList *mpDeleteList; + const BackupClientInodeToIDMap *mpCurrentIDMap; + BackupClientInodeToIDMap *mpNewIDMap; + bool mStorageLimitExceeded; + ExcludeList *mpExcludeFiles; + ExcludeList *mpExcludeDirs; + Timer mKeepAliveTimer; + bool mbIsManaged; + int mKeepAliveTime; + int mMaximumDiffingTime; + ProgressNotifier &mrProgressNotifier; + bool mTcpNiceMode; + NiceSocketStream *mpNice; +}; + +#endif // BACKUPCLIENTCONTEXT__H diff --git a/lib/bbackupd/BackupClientDeleteList.cpp b/lib/bbackupd/BackupClientDeleteList.cpp new file mode 100644 index 00000000..ce5e6264 --- /dev/null +++ b/lib/bbackupd/BackupClientDeleteList.cpp @@ -0,0 +1,229 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDeleteList.cpp +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <algorithm> + +#include "BackupClientDeleteList.h" +#include "BackupClientContext.h" +#include "autogen_BackupProtocol.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::BackupClientDeleteList() +// Purpose: Constructor +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList::BackupClientDeleteList() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::~BackupClientDeleteList() +// Purpose: Destructor +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +BackupClientDeleteList::~BackupClientDeleteList() +{ +} + +BackupClientDeleteList::FileToDelete::FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath) +: mDirectoryID(DirectoryID), + mFilename(rFilename), + mLocalPath(rLocalPath) +{ } + +BackupClientDeleteList::DirToDelete::DirToDelete(int64_t ObjectID, + const std::string& rLocalPath) +: mObjectID(ObjectID), + mLocalPath(rLocalPath) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::AddDirectoryDelete(int64_t, +// const BackupStoreFilename&) +// Purpose: Add a directory to the list of directories to be deleted. +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath) +{ + // Only add the delete to the list if it's not in the "no delete" set + if(mDirectoryNoDeleteList.find(ObjectID) == + mDirectoryNoDeleteList.end()) + { + // Not in the list, so should delete it + mDirectoryList.push_back(DirToDelete(ObjectID, rLocalPath)); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::AddFileDelete(int64_t, +// const BackupStoreFilename &) +// Purpose: +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, const std::string& rLocalPath) +{ + // Try to find it in the no delete list + std::vector<std::pair<int64_t, BackupStoreFilename> >::iterator + delEntry(mFileNoDeleteList.begin()); + while(delEntry != mFileNoDeleteList.end()) + { + if((delEntry)->first == DirectoryID + && (delEntry)->second == rFilename) + { + // Found! + break; + } + ++delEntry; + } + + // Only add it to the delete list if it wasn't in the no delete list + if(delEntry == mFileNoDeleteList.end()) + { + mFileList.push_back(FileToDelete(DirectoryID, rFilename, + rLocalPath)); + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) +// Purpose: Perform all the pending deletes +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::PerformDeletions(BackupClientContext &rContext) +{ + // Anything to do? + if(mDirectoryList.empty() && mFileList.empty()) + { + // Nothing! + return; + } + + // Get a connection + BackupProtocolCallable &connection(rContext.GetConnection()); + + // Do the deletes + for(std::vector<DirToDelete>::iterator i(mDirectoryList.begin()); + i != mDirectoryList.end(); ++i) + { + connection.QueryDeleteDirectory(i->mObjectID); + rContext.GetProgressNotifier().NotifyDirectoryDeleted( + i->mObjectID, i->mLocalPath); + } + + // Clear the directory list + mDirectoryList.clear(); + + // Delete the files + for(std::vector<FileToDelete>::iterator i(mFileList.begin()); + i != mFileList.end(); ++i) + { + connection.QueryDeleteFile(i->mDirectoryID, i->mFilename); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->mDirectoryID, i->mLocalPath); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::StopDirectoryDeletion(int64_t) +// Purpose: Stop a directory being deleted +// Created: 19/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::StopDirectoryDeletion(int64_t ObjectID) +{ + // First of all, is it in the delete vector? + std::vector<DirToDelete>::iterator delEntry(mDirectoryList.begin()); + for(; delEntry != mDirectoryList.end(); delEntry++) + { + if(delEntry->mObjectID == ObjectID) + { + // Found! + break; + } + } + if(delEntry != mDirectoryList.end()) + { + // erase this entry + mDirectoryList.erase(delEntry); + } + else + { + // Haven't been asked to delete it yet, put it in the + // no delete list + mDirectoryNoDeleteList.insert(ObjectID); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDeleteList::StopFileDeletion(int64_t, const BackupStoreFilename &) +// Purpose: Stop a file from being deleted +// Created: 19/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientDeleteList::StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename) +{ + // Find this in the delete list + std::vector<FileToDelete>::iterator delEntry(mFileList.begin()); + while(delEntry != mFileList.end()) + { + if(delEntry->mDirectoryID == DirectoryID + && delEntry->mFilename == rFilename) + { + // Found! + break; + } + ++delEntry; + } + + if(delEntry != mFileList.end()) + { + // erase this entry + mFileList.erase(delEntry); + } + else + { + // Haven't been asked to delete it yet, put it in the no delete list + mFileNoDeleteList.push_back(std::pair<int64_t, BackupStoreFilename>(DirectoryID, rFilename)); + } +} + diff --git a/lib/bbackupd/BackupClientDeleteList.h b/lib/bbackupd/BackupClientDeleteList.h new file mode 100644 index 00000000..b0fbf51a --- /dev/null +++ b/lib/bbackupd/BackupClientDeleteList.h @@ -0,0 +1,75 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDeleteList.h +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTDELETELIST__H +#define BACKUPCLIENTDELETELIST__H + +#include "BackupStoreFilename.h" + +class BackupClientContext; + +#include <vector> +#include <utility> +#include <set> + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientDeleteList +// Purpose: List of pending deletes for backup +// Created: 10/11/03 +// +// -------------------------------------------------------------------------- +class BackupClientDeleteList +{ +private: + class FileToDelete + { + public: + int64_t mDirectoryID; + BackupStoreFilename mFilename; + std::string mLocalPath; + FileToDelete(int64_t DirectoryID, + const BackupStoreFilename& rFilename, + const std::string& rLocalPath); + }; + + class DirToDelete + { + public: + int64_t mObjectID; + std::string mLocalPath; + DirToDelete(int64_t ObjectID, const std::string& rLocalPath); + }; + +public: + BackupClientDeleteList(); + ~BackupClientDeleteList(); + + void AddDirectoryDelete(int64_t ObjectID, + const std::string& rLocalPath); + void AddFileDelete(int64_t DirectoryID, + const BackupStoreFilename &rFilename, + const std::string& rLocalPath); + + void StopDirectoryDeletion(int64_t ObjectID); + void StopFileDeletion(int64_t DirectoryID, + const BackupStoreFilename &rFilename); + + void PerformDeletions(BackupClientContext &rContext); + +private: + std::vector<DirToDelete> mDirectoryList; + std::set<int64_t> mDirectoryNoDeleteList; // note: things only get in this list if they're not present in mDirectoryList when they are 'added' + std::vector<FileToDelete> mFileList; + std::vector<std::pair<int64_t, BackupStoreFilename> > mFileNoDeleteList; +}; + +#endif // BACKUPCLIENTDELETELIST__H + diff --git a/lib/bbackupd/BackupClientDirectoryRecord.cpp b/lib/bbackupd/BackupClientDirectoryRecord.cpp new file mode 100644 index 00000000..94cb7965 --- /dev/null +++ b/lib/bbackupd/BackupClientDirectoryRecord.cpp @@ -0,0 +1,2302 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDirectoryRecord.cpp +// Purpose: Implementation of record about directory for +// backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_DIRENT_H + #include <dirent.h> +#endif + +#include <errno.h> +#include <string.h> + +#include "autogen_BackupProtocol.h" +#include "autogen_CipherException.h" +#include "autogen_ClientException.h" +#include "Archive.h" +#include "BackupClientContext.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupClientInodeToIDMap.h" +#include "BackupDaemon.h" +#include "BackupStoreException.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" + +typedef std::map<std::string, BackupStoreDirectory::Entry *> DecryptedEntriesMap_t; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::BackupClientDirectoryRecord() +// Purpose: Constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName) + : mObjectID(ObjectID), + mSubDirName(rSubDirName), + mInitialSyncDone(false), + mSyncDone(false), + mpPendingEntries(0) +{ + ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::~BackupClientDirectoryRecord() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::~BackupClientDirectoryRecord() +{ + // Make deletion recursive + DeleteSubDirectories(); + + // Delete maps + if(mpPendingEntries != 0) + { + delete mpPendingEntries; + mpPendingEntries = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::DeleteSubDirectories(); +// Purpose: Delete all sub directory entries +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::DeleteSubDirectories() +{ + // Delete all pointers + for(std::map<std::string, BackupClientDirectoryRecord *>::iterator i = mSubDirectories.begin(); + i != mSubDirectories.end(); ++i) + { + delete i->second; + } + + // Empty list + 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 +// Name: BackupClientDirectoryRecord::SyncDirectory(i +// BackupClientDirectoryRecord::SyncParams &, +// int64_t, const std::string &, +// const std::string &, bool) +// Purpose: Recursively synchronise a local directory +// with the server. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::SyncDirectory( + BackupClientDirectoryRecord::SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, + const Location& rBackupLocation, + bool ThisDirHasJustBeenCreated) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + // Signal received by daemon? + if(rParams.mrRunStatusProvider.StopRun()) + { + // Yes. Stop now. + THROW_EXCEPTION(BackupStoreException, SignalReceived) + } + + // Start by making some flag changes, marking this sync as not done, + // and on the immediate sub directories. + mSyncDone = false; + for(std::map<std::string, BackupClientDirectoryRecord *>::iterator + i = mSubDirectories.begin(); + i != mSubDirectories.end(); ++i) + { + i->second->mSyncDone = false; + } + + // Work out the time in the future after which the file should + // be uploaded regardless. This is a simple way to avoid having + // too many problems with file servers when they have clients + // with badly out of sync clocks. + rParams.mUploadAfterThisTimeInTheFuture = GetCurrentBoxTime() + + rParams.mMaxFileTimeInFuture; + + // Build the current state checksum to compare against while + // getting info from dirs. Note checksum is used locally only, + // so byte order isn't considered. + MD5Digest currentStateChecksum; + + EMU_STRUCT_STAT dest_st; + // Stat the directory, to get attribute info + // If it's a symbolic link, we want the link target here + // (as we're about to back up the contents of the directory) + { + if(EMU_STAT(rLocalPath.c_str(), &dest_st) != 0) + { + // The directory has probably been deleted, so + // just ignore this error. In a future scan, this + // deletion will be noticed, deleted from server, + // and this object deleted. + 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 + { + BackupClientInodeToIDMap &idMap( + rParams.mrContext.GetNewIDMap()); + idMap.AddToMap(dest_st.st_ino, mObjectID, ContainingDirectoryID, + ConvertVssPathToRealPath(rLocalPath, rBackupLocation)); + } + // Add attributes to checksum + currentStateChecksum.Add(&dest_st.st_mode, + sizeof(dest_st.st_mode)); + currentStateChecksum.Add(&dest_st.st_uid, + sizeof(dest_st.st_uid)); + currentStateChecksum.Add(&dest_st.st_gid, + sizeof(dest_st.st_gid)); + // Inode to be paranoid about things moving around + currentStateChecksum.Add(&dest_st.st_ino, + sizeof(dest_st.st_ino)); +#ifdef HAVE_STRUCT_STAT_ST_FLAGS + currentStateChecksum.Add(&dest_st.st_flags, + sizeof(dest_st.st_flags)); +#endif + + StreamableMemBlock xattr; + BackupClientFileAttributes::FillExtendedAttr(xattr, + rLocalPath.c_str()); + currentStateChecksum.Add(xattr.GetBuffer(), xattr.GetSize()); + } + + // Read directory entries, building arrays of names + // First, need to read the contents of the directory. + std::vector<std::string> dirs; + std::vector<std::string> files; + bool downloadDirectoryRecordBecauseOfFutureFiles = false; + + // BLOCK + { + // read the contents... + DIR *dirHandle = 0; + try + { + std::string nonVssDirPath = ConvertVssPathToRealPath(rLocalPath, + rBackupLocation); + rNotifier.NotifyScanDirectory(this, nonVssDirPath); + + dirHandle = ::opendir(rLocalPath.c_str()); + if(dirHandle == 0) + { + // Report the error (logs and eventual email to administrator) + if (errno == EACCES) + { + rNotifier.NotifyDirListFailed(this, + nonVssDirPath, + "Access denied"); + } + else + { + rNotifier.NotifyDirListFailed(this, + nonVssDirPath, + strerror(errno)); + } + + // Report the error (logs and eventual email + // to administrator) + SetErrorWhenReadingFilesystemObject(rParams, + nonVssDirPath); + // Ignore this directory for now. + return; + } + + struct dirent *en = 0; + int num_entries_found = 0; + + while((en = ::readdir(dirHandle)) != 0) + { + num_entries_found++; + rParams.mrContext.DoKeepAlive(); + if(rParams.mpBackgroundTask) + { + rParams.mpBackgroundTask->RunBackgroundTask( + BackgroundTask::Scanning_Dirs, + num_entries_found, 0); + } + + if (!SyncDirectoryEntry(rParams, rNotifier, + rBackupLocation, rLocalPath, + currentStateChecksum, en, dest_st, dirs, + files, downloadDirectoryRecordBecauseOfFutureFiles)) + { + // This entry is not to be backed up. + continue; + } + } + + if(::closedir(dirHandle) != 0) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + dirHandle = 0; + } + catch(...) + { + if(dirHandle != 0) + { + ::closedir(dirHandle); + } + throw; + } + } + + // Finish off the checksum, and compare with the one currently stored + bool checksumDifferent = true; + currentStateChecksum.Finish(); + if(mInitialSyncDone && currentStateChecksum.DigestMatches(mStateChecksum)) + { + // The checksum is the same, and there was one to compare with + checksumDifferent = false; + } + + // Pointer to potentially downloaded store directory info + std::auto_ptr<BackupStoreDirectory> apDirOnStore; + + try + { + // Want to get the directory listing? + if(ThisDirHasJustBeenCreated) + { + // Avoid sending another command to the server when we know it's empty + apDirOnStore.reset(new BackupStoreDirectory(mObjectID, + ContainingDirectoryID)); + } + // Consider asking the store for it + else if(!mInitialSyncDone || checksumDifferent || + downloadDirectoryRecordBecauseOfFutureFiles) + { + apDirOnStore = FetchDirectoryListing(rParams); + } + + // Make sure the attributes are up to date -- if there's space + // on the server and this directory has not just been created + // (because it's attributes will be correct in this case) and + // the checksum is different, implying they *MIGHT* be + // different. + if((!ThisDirHasJustBeenCreated) && checksumDifferent && + !rParams.mrContext.StorageLimitExceeded()) + { + UpdateAttributes(rParams, apDirOnStore.get(), rLocalPath); + } + + // Create the list of pointers to directory entries + std::vector<BackupStoreDirectory::Entry *> entriesLeftOver; + if(apDirOnStore.get()) + { + entriesLeftOver.resize(apDirOnStore->GetNumberOfEntries(), 0); + BackupStoreDirectory::Iterator i(*apDirOnStore); + // Copy in pointers to all the entries + for(unsigned int l = 0; l < apDirOnStore->GetNumberOfEntries(); ++l) + { + entriesLeftOver[l] = i.Next(); + } + } + + // Do the directory reading + bool updateCompleteSuccess = UpdateItems(rParams, rLocalPath, + rRemotePath, rBackupLocation, apDirOnStore.get(), + entriesLeftOver, files, dirs); + + // LAST THING! (think exception safety) + // Store the new checksum -- don't fetch things unnecessarily + // in the future But... only if 1) the storage limit isn't + // exceeded -- make sure things are done again if the directory + // is modified later and 2) All the objects within the + // directory were stored successfully. + if(!rParams.mrContext.StorageLimitExceeded() && + updateCompleteSuccess) + { + currentStateChecksum.CopyDigestTo(mStateChecksum); + } + } + catch(...) + { + // Bad things have happened -- clean up + // Set things so that we get a full go at stuff later + ::memset(mStateChecksum, 0, sizeof(mStateChecksum)); + + throw; + } + + // Flag things as having happened. + mInitialSyncDone = true; + mSyncDone = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncDirectorEntry( +// BackupClientDirectoryRecord::SyncParams &, +// int64_t, const std::string &, +// const std::string &, bool) +// Purpose: Recursively synchronise a local directory +// with the server. +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +bool BackupClientDirectoryRecord::SyncDirectoryEntry( + BackupClientDirectoryRecord::SyncParams &rParams, + ProgressNotifier& rNotifier, + const Location& rBackupLocation, + const std::string &rDirLocalPath, + MD5Digest& currentStateChecksum, + struct dirent *en, + EMU_STRUCT_STAT dir_st, + std::vector<std::string>& rDirs, + std::vector<std::string>& rFiles, + bool& rDownloadDirectoryRecordBecauseOfFutureFiles) +{ + std::string entry_name = en->d_name; + if(entry_name == "." || entry_name == "..") + { + // ignore parent directory entries + return false; + } + + // Stat file to get info + std::string filename = MakeFullPath(rDirLocalPath, entry_name); + std::string realFileName = ConvertVssPathToRealPath(filename, + rBackupLocation); + EMU_STRUCT_STAT file_st; + +#ifdef WIN32 + // Don't stat the file just yet, to ensure that users can exclude + // unreadable files to suppress warnings that they are not accessible. + // + // Our emulated readdir() abuses en->d_type, which would normally + // contain DT_REG, DT_DIR, etc, but we only use it here and 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) + { + // We don't know whether it's a file or a directory, so check + // both. This only affects whether a warning message is + // displayed; the file is not backed up in either case. + if(!(rParams.mrContext.ExcludeFile(filename)) && + !(rParams.mrContext.ExcludeDir(filename))) + { + // Report the error (logs and eventual email to + // administrator) + rNotifier.NotifyFileStatFailed(this, filename, + strerror(errno)); + + // FIXME move to NotifyFileStatFailed() + SetErrorWhenReadingFilesystemObject(rParams, filename); + } + + // Ignore this entry for now. + return false; + } + + 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. + + int type = file_st.st_mode & S_IFMT; + if(type == S_IFDIR && file_st.st_dev != dir_st.st_dev) + { + if(!(rParams.mrContext.ExcludeDir(filename))) + { + rNotifier.NotifyMountPointSkipped(this, filename); + } + return false; + } +#endif + + if(type == S_IFREG || type == S_IFLNK) + { + // File or symbolic link + + // Exclude it? + if(rParams.mrContext.ExcludeFile(realFileName)) + { + rNotifier.NotifyFileExcluded(this, realFileName); + // Next item! + return false; + } + } + else if(type == S_IFDIR) + { + // Directory + + // Exclude it? + if(rParams.mrContext.ExcludeDir(realFileName)) + { + rNotifier.NotifyDirExcluded(this, realFileName); + + // Next item! + return false; + } + + #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); + return false; + } + #endif + } + else // not a file or directory, what is it? + { + 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(realFileName)) + { + rNotifier.NotifyFileExcluded(this, realFileName); + } + else + { + rNotifier.NotifyUnsupportedFileType(this, realFileName); + SetErrorWhenReadingFilesystemObject(rParams, + realFileName); + } + + return false; + } + + // The object should be backed up (file, symlink or dir, not excluded). + // So make the information for adding to the checksum. + + #ifdef WIN32 + // We didn't stat the file before, but now we need the information. + if(emu_stat(filename.c_str(), &file_st) != 0) + { + rNotifier.NotifyFileStatFailed(this, + ConvertVssPathToRealPath(filename, rBackupLocation), + strerror(errno)); + + // Report the error (logs and eventual email to administrator) + SetErrorWhenReadingFilesystemObject(rParams, filename); + + // Ignore this entry for now. + return false; + } + + if(file_st.st_dev != dir_st.st_dev) + { + rNotifier.NotifyMountPointSkipped(this, + ConvertVssPathToRealPath(filename, rBackupLocation)); + return false; + } + #endif + + // Basic structure for checksum info + struct { + box_time_t mModificationTime; + box_time_t mAttributeModificationTime; + int64_t mSize; + // And then the name follows + } checksum_info; + + // Be paranoid about structure packing + ::memset(&checksum_info, 0, sizeof(checksum_info)); + + checksum_info.mModificationTime = FileModificationTime(file_st); + checksum_info.mAttributeModificationTime = FileAttrModificationTime(file_st); + checksum_info.mSize = file_st.st_size; + currentStateChecksum.Add(&checksum_info, sizeof(checksum_info)); + currentStateChecksum.Add(en->d_name, strlen(en->d_name)); + + // If the file has been modified madly into the future, download the + // directory record anyway to ensure that it doesn't get uploaded + // every single time the disc is scanned. + if(checksum_info.mModificationTime > rParams.mUploadAfterThisTimeInTheFuture) + { + rDownloadDirectoryRecordBecauseOfFutureFiles = true; + // Log that this has happened + if(!rParams.mHaveLoggedWarningAboutFutureFileTimes) + { + rNotifier.NotifyFileModifiedInFuture(this, + ConvertVssPathToRealPath(filename, rBackupLocation)); + rParams.mHaveLoggedWarningAboutFutureFileTimes = true; + } + } + + // We've decided to back it up, so add to file or directory list. + if(type == S_IFREG || type == S_IFLNK) + { + rFiles.push_back(entry_name); + } + else if(type == S_IFDIR) + { + rDirs.push_back(entry_name); + } + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::FetchDirectoryListing( +// BackupClientDirectoryRecord::SyncParams &) +// Purpose: Fetch the directory listing of this directory from +// the store. +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +std::auto_ptr<BackupStoreDirectory> +BackupClientDirectoryRecord::FetchDirectoryListing( + BackupClientDirectoryRecord::SyncParams &rParams) +{ + std::auto_ptr<BackupStoreDirectory> apDir; + + // Get connection to store + BackupProtocolCallable &connection(rParams.mrContext.GetConnection()); + + // Query the directory + std::auto_ptr<BackupProtocolSuccess> dirreply(connection.QueryListDirectory( + mObjectID, + // both files and directories + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + // exclude old/deleted stuff + BackupProtocolListDirectory::Flags_Deleted | + BackupProtocolListDirectory::Flags_OldVersion, + true /* want attributes */)); + + // Retrieve the directory from the stream following + apDir.reset(new BackupStoreDirectory(connection.ReceiveStream(), + connection.GetTimeout())); + return apDir; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UpdateAttributes( +// BackupClientDirectoryRecord::SyncParams &, +// const std::string &) +// Purpose: Sets the attributes of the directory on the store, +// if necessary. +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::UpdateAttributes( + BackupClientDirectoryRecord::SyncParams &rParams, + BackupStoreDirectory *pDirOnStore, + const std::string &rLocalPath) +{ + // Get attributes for the directory + BackupClientFileAttributes attr; + box_time_t attrModTime = 0; + attr.ReadAttributes(rLocalPath.c_str(), true /* directories have zero mod times */, + 0 /* no modification time */, &attrModTime); + + // Assume attributes need updating, unless proved otherwise + bool updateAttr = true; + + // Got a listing to compare with? + ASSERT(pDirOnStore == 0 || (pDirOnStore != 0 && pDirOnStore->HasAttributes())); + if(pDirOnStore != 0 && pDirOnStore->HasAttributes()) + { + const StreamableMemBlock &storeAttrEnc(pDirOnStore->GetAttributes()); + // Explict decryption + BackupClientFileAttributes storeAttr(storeAttrEnc); + + // Compare the attributes + if(attr.Compare(storeAttr, true, + true /* ignore both modification times */)) + { + // No update necessary + updateAttr = false; + } + } + + // Update them? + if(updateAttr) + { + // Get connection to store + BackupProtocolCallable &connection(rParams.mrContext.GetConnection()); + + // Exception thrown if this doesn't work + 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; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UpdateItems(BackupClientDirectoryRecord::SyncParams &, const std::string &, BackupStoreDirectory *, std::vector<BackupStoreDirectory::Entry *> &) +// Purpose: Update the items stored on the server. The rFiles vector will be erased after it's used to save space. +// Returns true if all items were updated successfully. (If not, the failures will have been logged). +// Created: 2003/10/09 +// +// -------------------------------------------------------------------------- +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, + const std::vector<std::string> &rDirs) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + bool allUpdatedSuccessfully = true; + + // Decrypt all the directory entries. + // It would be nice to be able to just compare the encrypted versions, however this doesn't work + // in practise because there can be multiple encodings of the same filename using different + // methods (although each method will result in the same string for the same filename.) This + // happens when the server fixes a broken store, and gives plain text generated filenames. + // So if we didn't do things like this, then you wouldn't be able to recover from bad things + // happening with the server. + DecryptedEntriesMap_t decryptedEntries; + if(pDirOnStore != 0) + { + BackupStoreDirectory::Iterator i(*pDirOnStore); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + 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"); + } + } + } + + // Do files + for(std::vector<std::string>::const_iterator f = rFiles.begin(); + f != rFiles.end(); ++f) + { + // Send keep-alive message if needed + rContext.DoKeepAlive(); + + // 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; + // BLOCK + { + // Stat the file + EMU_STRUCT_STAT st; + if(EMU_LSTAT(filename.c_str(), &st) != 0) + { + rNotifier.NotifyFileStatFailed(this, nonVssFilePath, + strerror(errno)); + + // Report the error (logs and + // eventual email to administrator) + SetErrorWhenReadingFilesystemObject(rParams, nonVssFilePath); + + // Ignore this entry for now. + continue; + } + + // Extract required data + modTime = FileModificationTime(st); + fileSize = st.st_size; + inodeNum = st.st_ino; + attributesHash = BackupClientFileAttributes::GenerateAttributeHash(st, filename, *f); + } + + // See if it's in the listing (if we have one) + BackupStoreFilenameClear storeFilename(*f); + BackupStoreDirectory::Entry *en = 0; + int64_t latestObjectID = 0; + if(pDirOnStore != 0) + { + DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*f)); + if(i != decryptedEntries.end()) + { + en = i->second; + latestObjectID = en->GetObjectID(); + } + } + + // Check that the entry which might have been found is in fact a file + if((en != 0) && !(en->IsFile())) + { + // Directory exists in the place of this file -- sort it out + RemoveDirectoryInPlaceOfFile(rParams, pDirOnStore, + en, *f); + en = 0; + } + + // Check for renaming? + if(pDirOnStore != 0 && en == 0) + { + // We now know... + // 1) File has just been added + // 2) It's not in the store + + // Do we know about the inode number? + const BackupClientInodeToIDMap &idMap(rContext.GetCurrentIDMap()); + int64_t renameObjectID = 0, renameInDirectory = 0; + if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) + { + // Look up on the server to get the name, to build the local filename + std::string localPotentialOldName; + bool isDir = false; + bool isCurrentVersion = false; + box_time_t srvModTime = 0, srvAttributesHash = 0; + BackupStoreFilenameClear 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) + { + // Check that the object we found in the ID map doesn't exist on disc + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && errno == ENOENT) + { + // Doesn't exist locally, but does exist on the server. + // Therefore we can safely rename it to this new file. + + // Get the connection to the server + BackupProtocolCallable &connection(rContext.GetConnection()); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is space available + if(!rContext.StorageLimitExceeded()) + { + // Rename the existing files (ie include old versions) on the server + 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 + BackupClientDeleteList &rdelList(rContext.GetDeleteList()); + rdelList.StopFileDeletion(renameInDirectory, oldLeafname); + + // 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); + + // Store the object ID for the inode lookup map later + latestObjectID = renameObjectID; + } + } + } + } + } + } + + // Is it in the mPendingEntries list? + box_time_t pendingFirstSeenTime = 0; // ie not seen + if(mpPendingEntries != 0) + { + std::map<std::string, box_time_t>::const_iterator i(mpPendingEntries->find(*f)); + if(i != mpPendingEntries->end()) + { + // found it -- set flag + pendingFirstSeenTime = i->second; + } + } + + // If pDirOnStore == 0, then this must have been after an initial sync: + ASSERT(pDirOnStore != 0 || mInitialSyncDone); + // So, if pDirOnStore == 0, then we know that everything before syncPeriodStart + // is either on the server, or in the toupload list. If the directory had changed, + // we'd have got a directory listing. + // + // At this point, if (pDirOnStore == 0 && en == 0), we can assume it's on the server with a + // mod time < syncPeriodStart, or didn't exist before that time. + // + // But if en != 0, then we need to compare modification times to avoid uploading it again. + + // Need to update? + // + // Condition for upload: + // modification time within sync period + // if it's been seen before but not uploaded, is the time from this first sight longer than the MaxUploadWait + // 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. + + if (en == 0 || en->GetModificationTime() != modTime) + { + // Check the file modified within the acceptable time period we're checking + // If the file isn't on the server, the acceptable time starts at zero. + // Check pDirOnStore and en, because if we didn't download a directory listing, + // pDirOnStore will be zero, but we know it's on the server. + if (modTime < rParams.mSyncPeriodEnd) + { + if (pDirOnStore != 0 && en == 0) + { + doUpload = true; + decisionReason = "not on server"; + } + else if (modTime >= rParams.mSyncPeriodStart) + { + doUpload = true; + decisionReason = "modified since last sync"; + } + } + + // However, just in case things are continually + // modified, we check the first seen time. + // The two compares of syncPeriodEnd and + // pendingFirstSeenTime are because the values + // are unsigned. + + if (!doUpload && + pendingFirstSeenTime != 0 && + rParams.mSyncPeriodEnd > pendingFirstSeenTime && + (rParams.mSyncPeriodEnd - pendingFirstSeenTime) + > rParams.mMaxUploadWait) + { + doUpload = true; + decisionReason = "continually modified"; + } + + // Then make sure that if files are added with a + // time less than the sync period start + // (which can easily happen on file server), it + // gets uploaded. The directory contents checksum + // will pick up the fact it has been added, so the + // store listing will be available when this happens. + + if (!doUpload && + modTime <= rParams.mSyncPeriodStart && + en != 0 && + en->GetModificationTime() != modTime) + { + doUpload = true; + decisionReason = "mod time changed"; + } + + // And just to catch really badly off clocks in + // the future for file server clients, + // just upload the file if it's madly in the future. + + if (!doUpload && modTime > + rParams.mUploadAfterThisTimeInTheFuture) + { + doUpload = true; + decisionReason = "mod time in the future"; + } + } + + if (en != 0 && en->GetModificationTime() == modTime) + { + doUpload = false; + decisionReason = "not modified since last upload"; + } + else if (!doUpload) + { + if (modTime > rParams.mSyncPeriodEnd) + { + box_time_t now = GetCurrentBoxTime(); + int age = BoxTimeToSeconds(now - + modTime); + std::ostringstream s; + s << "modified too recently: only " << + age << " seconds ago"; + decisionReason = s.str(); + } + else + { + std::ostringstream s; + s << "mod time is " << modTime << + " which is outside sync window, " + << rParams.mSyncPeriodStart << " to " + << rParams.mSyncPeriodEnd; + decisionReason = s.str(); + } + } + + BOX_TRACE("Upload decision: " << nonVssFilePath << ": " << + (doUpload ? "will upload" : "will not upload") << + " (" << decisionReason << ")"); + + bool fileSynced = true; + + if (doUpload) + { + // Upload needed, don't mark sync success until + // we've actually done it + fileSynced = false; + + // Make sure we're connected -- must connect here so we know whether + // the storage limit has been exceeded, and hence whether or not + // to actually upload the file. + rContext.GetConnection(); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is space available + if(!rContext.StorageLimitExceeded()) + { + // Upload the file to the server, recording the + // object ID it returns + bool noPreviousVersionOnServer = + ((pDirOnStore != 0) && (en == 0)); + + // Surround this in a try/catch block, to + // catch errors, but still continue + bool uploadSuccess = false; + try + { + latestObjectID = UploadFile(rParams, + filename, + nonVssFilePath, + rRemotePath + "/" + *f, + storeFilename, + fileSize, modTime, + attributesHash, + noPreviousVersionOnServer); + + if (latestObjectID == 0) + { + // storage limit exceeded + rParams.mrContext.SetStorageLimitExceeded(); + uploadSuccess = false; + allUpdatedSuccessfully = false; + } + else + { + uploadSuccess = true; + } + } + catch(ConnectionException &e) + { + // Connection errors should just be + // passed on to the main handler, + // retries would probably just cause + // more problems. + rNotifier.NotifyFileUploadException( + this, nonVssFilePath, e); + throw; + } + catch(BoxException &e) + { + if (e.GetType() == BackupStoreException::ExceptionType && + e.GetSubType() == BackupStoreException::SignalReceived) + { + // abort requested, pass the + // exception on up. + throw; + } + + // an error occured -- make return + // code false, to show error in directory + allUpdatedSuccessfully = false; + // Log it. + SetErrorWhenReadingFilesystemObject(rParams, + nonVssFilePath); + rNotifier.NotifyFileUploadException(this, + nonVssFilePath, e); + } + + // Update structures if the file was uploaded + // successfully. + if(uploadSuccess) + { + fileSynced = true; + + // delete from pending entries + if(pendingFirstSeenTime != 0 && mpPendingEntries != 0) + { + mpPendingEntries->erase(*f); + } + } + } + else + { + rNotifier.NotifyFileSkippedServerFull(this, nonVssFilePath); + } + } + else if(en != 0 && en->GetAttributesHash() != attributesHash) + { + // Attributes have probably changed, upload them again. + // If the attributes have changed enough, the directory + // hash will have changed too, and so the dir will have + // been downloaded, and the entry will be available. + + // Get connection + BackupProtocolCallable &connection(rContext.GetConnection()); + + // Only do this step if there is room on the server. + // This step will be repeated later when there is + // space available + if(!rContext.StorageLimitExceeded()) + { + try + { + rNotifier.NotifyFileUploadingAttributes(this, + nonVssFilePath); + + // Update store + BackupClientFileAttributes 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 '" << nonVssFilePath << "', will try again " + "later"); + } + } + } + + if(modTime >= rParams.mSyncPeriodEnd) + { + // Allocate? + if(mpPendingEntries == 0) + { + mpPendingEntries = new std::map<std::string, box_time_t>; + } + // Adding to mPendingEntries list + if(pendingFirstSeenTime == 0) + { + // Haven't seen this before -- add to list! + (*mpPendingEntries)[*f] = modTime; + } + } + + // Zero pointer in rEntriesLeftOver, if we have a pointer to zero + if(en != 0) + { + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] == en) + { + rEntriesLeftOver[l] = 0; + break; + } + } + } + + // Does this file need an entry in the ID map? + if(fileSize >= rParams.mFileTrackingSizeThreshold) + { + // Get the map + BackupClientInodeToIDMap &idMap(rContext.GetNewIDMap()); + + // Need to get an ID from somewhere... + if(latestObjectID == 0) + { + // Don't know it -- haven't sent anything to the store, and didn't get a listing. + // Look it up in the current map, and if it's there, use that. + const BackupClientInodeToIDMap ¤tIDMap(rContext.GetCurrentIDMap()); + int64_t objid = 0, dirid = 0; + if(currentIDMap.Lookup(inodeNum, objid, dirid)) + { + // Found + if (dirid != mObjectID) + { + 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); + + // NOTE: If the above assert fails, an inode number has been reused by the OS, + // or there is a problem somewhere. If this happened on a short test run, look + // 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. + + latestObjectID = objid; + } + } + + if(latestObjectID != 0) + { + BOX_TRACE("Storing uploaded file ID " << + inodeNum << " (" << nonVssFilePath << ") " + "in ID map as object " << + latestObjectID << " with parent " << + mObjectID); + idMap.AddToMap(inodeNum, latestObjectID, + mObjectID /* containing directory */, + nonVssFilePath); + } + + } + + if (fileSynced) + { + rNotifier.NotifyFileSynchronised(this, nonVssFilePath, + fileSize); + } + } + + // Erase contents of files to save space when recursing + rFiles.clear(); + + // Delete the pending entries, if the map is empty + if(mpPendingEntries != 0 && mpPendingEntries->size() == 0) + { + BOX_TRACE("Deleting mpPendingEntries from dir ID " << + BOX_FORMAT_OBJECTID(mObjectID)); + delete mpPendingEntries; + mpPendingEntries = 0; + } + + // Do directories + for(std::vector<std::string>::const_iterator d = rDirs.begin(); + d != rDirs.end(); ++d) + { + // Send keep-alive message if needed + rContext.DoKeepAlive(); + + // 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); + BackupStoreDirectory::Entry *en = 0; + if(pDirOnStore != 0) + { + DecryptedEntriesMap_t::iterator i(decryptedEntries.find(*d)); + if(i != decryptedEntries.end()) + { + en = i->second; + } + } + + // Check that the entry which might have been found is in fact a directory + if((en != 0) && !(en->IsDir())) + { + // Entry exists, but is not a directory. Bad. + // Get rid of it. + BackupProtocolCallable &connection(rContext.GetConnection()); + connection.QueryDeleteFile(mObjectID /* in directory */, storeFilename); + + std::string filenameClear = DecryptFilename(en, + rRemotePath); + rNotifier.NotifyFileDeleted(en->GetObjectID(), + filenameClear); + + // Nothing found + en = 0; + } + + // Zero pointer in rEntriesLeftOver, if we have a pointer to zero + if(en != 0) + { + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] == en) + { + rEntriesLeftOver[l] = 0; + break; + } + } + } + + // Flag for having created directory, so can optimise the + // recursive call not to read it again, because we know + // it's empty. + bool haveJustCreatedDirOnServer = false; + + // Next, see if it's in the list of sub directories + BackupClientDirectoryRecord *psubDirRecord = 0; + std::map<std::string, BackupClientDirectoryRecord *>::iterator + e(mSubDirectories.find(*d)); + + if(e != mSubDirectories.end()) + { + // In the list, just use this pointer + psubDirRecord = e->second; + } + else + { + // Note: if we have exceeded our storage limit, then + // we should not upload any more data, nor create any + // DirectoryRecord representing data that would have + // been uploaded. This step will be repeated when + // there is some space available. + bool doCreateDirectoryRecord = true; + + // Need to create the record. But do we need to create the directory on the server? + int64_t subDirObjectID = 0; + if(en != 0) + { + // No. Exists on the server, and we know about it from the listing. + subDirObjectID = en->GetObjectID(); + } + else if(rContext.StorageLimitExceeded()) + // know we've got a connection if we get this far, + // as dir will have been modified. + { + doCreateDirectoryRecord = false; + } + else + { + // Yes, creation required! + // It is known that it doesn't exist: + // + // if en == 0 and pDirOnStore == 0, then the + // directory has had an initial sync, and + // hasn't been modified (Really? then why + // are we here? TODO FIXME) + // so it has definitely been created already + // (so why create it again?) + // + // if en == 0 but pDirOnStore != 0, well... obviously it doesn't exist. + // + subDirObjectID = CreateRemoteDir(dirname, + nonVssDirPath, rRemotePath + "/" + *d, + storeFilename, &haveJustCreatedDirOnServer, + rParams); + doCreateDirectoryRecord = (subDirObjectID != 0); + } + + if (doCreateDirectoryRecord) + { + // New an object for this + psubDirRecord = new BackupClientDirectoryRecord(subDirObjectID, *d); + + // Store in list + try + { + mSubDirectories[*d] = psubDirRecord; + } + catch(...) + { + delete psubDirRecord; + psubDirRecord = 0; + throw; + } + } + } + + // ASSERT(psubDirRecord != 0 || rContext.StorageLimitExceeded()); + // There's another possible reason now: the directory no longer + // existed when we finally got around to checking its + // attributes. See for example Brendon Baumgartner's reported + // error with Wordpress cache directories. + + if(psubDirRecord) + { + // Sync this sub directory too + psubDirRecord->SyncDirectory(rParams, mObjectID, dirname, + rRemotePath + "/" + *d, rBackupLocation, + haveJustCreatedDirOnServer); + } + } + + // Delete everything which is on the store, but not on disc + for(unsigned int l = 0; l < rEntriesLeftOver.size(); ++l) + { + if(rEntriesLeftOver[l] != 0) + { + BackupStoreDirectory::Entry *en = rEntriesLeftOver[l]; + + // These entries can't be deleted immediately, as it would prevent + // renaming and moving of objects working properly. So we add them + // to a list, which is actually deleted at the very end of the session. + // 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; + } + + std::string localName = MakeFullPath(rLocalPath, + filenameClear); + std::string nonVssLocalName = ConvertVssPathToRealPath(localName, + rBackupLocation); + + // Delete this entry -- file or directory? + if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_File) != 0) + { + // Set a pending deletion for the file + rdel.AddFileDelete(mObjectID, en->GetName(), + localName); + } + else if((en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) != 0) + { + // Set as a pending deletion for the directory + rdel.AddDirectoryDelete(en->GetObjectID(), + localName); + + // 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(filenameClear)); + if(e != mSubDirectories.end() && !isCorruptFilename) + { + // Carefully delete the entry from the map + BackupClientDirectoryRecord *rec = e->second; + mSubDirectories.erase(e); + delete rec; + + BOX_TRACE("Deleted directory record for " << + nonVssLocalName); + } + } + } + } + + // Return success flag (will be false if some files failed) + return allUpdatedSuccessfully; +} + +int64_t BackupClientDirectoryRecord::CreateRemoteDir(const std::string& localDirPath, + const std::string& nonVssDirPath, const std::string& remoteDirPath, + BackupStoreFilenameClear& storeFilename, bool* pHaveJustCreatedDirOnServer, + BackupClientDirectoryRecord::SyncParams &rParams) +{ + // Get attributes + box_time_t attrModTime = 0; + InodeRefType inodeNum = 0; + BackupClientFileAttributes attr; + *pHaveJustCreatedDirOnServer = false; + ProgressNotifier& rNotifier(rParams.mrContext.GetProgressNotifier()); + + try + { + attr.ReadAttributes(localDirPath, + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime, 0 /* not file size */, + &inodeNum); + } + catch (BoxException &e) + { + // We used to try to recover from this, but we'd need an + // attributes block to upload to the server, so we have to + // skip creating the directory instead. + BOX_WARNING("Failed to read attributes of directory, " + "ignoring it for now: " << nonVssDirPath); + return 0; // no object ID + } + + // Check to see if the directory been renamed + // First, do we have a record in the ID map? + int64_t renameObjectID = 0, renameInDirectory = 0; + bool renameDir = false; + const BackupClientInodeToIDMap &idMap(rParams.mrContext.GetCurrentIDMap()); + + if(idMap.Lookup(inodeNum, renameObjectID, renameInDirectory)) + { + // Look up on the server to get the name, to build the local filename + std::string localPotentialOldName; + bool isDir = false; + bool isCurrentVersion = false; + if(rParams.mrContext.FindFilename(renameObjectID, renameInDirectory, + localPotentialOldName, isDir, isCurrentVersion)) + { + // Only interested if it's a directory + if(isDir && isCurrentVersion) + { + // Check that the object doesn't exist already + EMU_STRUCT_STAT st; + if(EMU_STAT(localPotentialOldName.c_str(), &st) != 0 && + errno == ENOENT) + { + // Doesn't exist locally, but does exist + // on the server. Therefore we can + // safely rename it. + renameDir = true; + } + } + } + } + + // Get connection + BackupProtocolCallable &connection(rParams.mrContext.GetConnection()); + + // Don't do a check for storage limit exceeded here, because if we get to this + // stage, a connection will have been opened, and the status known, so the check + // in the else if(...) above will be correct. + + // Build attribute stream for sending + 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 */, + BackupProtocolMoveObject::Flags_MoveAllWithSameName | + BackupProtocolMoveObject::Flags_AllowMoveOverDeletedObject, + storeFilename); + + // Put the latest attributes on it + connection.QueryChangeDirAttributes(renameObjectID, attrModTime, attrStream); + + // Stop it being deleted later + BackupClientDeleteList &rdelList( + rParams.mrContext.GetDeleteList()); + rdelList.StopDirectoryDeletion(renameObjectID); + + // This is the ID for the renamed directory + return renameObjectID; + } + else + { + int64_t subDirObjectID = 0; // no object ID + + // Create a new directory + try + { + subDirObjectID = connection.QueryCreateDirectory( + mObjectID, attrModTime, storeFilename, + attrStream)->GetObjectID(); + // Flag as having done this for optimisation later + *pHaveJustCreatedDirOnServer = true; + } + catch(BoxException &e) + { + int type, subtype; + connection.GetLastError(type, subtype); + rNotifier.NotifyFileUploadServerError(this, nonVssDirPath, + type, subtype); + if(e.GetType() == ConnectionException::ExceptionType && + e.GetSubType() == ConnectionException::Protocol_UnexpectedReply && + type == BackupProtocolError::ErrorType && + subtype == BackupProtocolError::Err_StorageLimitExceeded) + { + // The hard limit was exceeded on the server, notify! + rParams.mrContext.SetStorageLimitExceeded(); + rParams.mrSysadminNotifier.NotifySysadmin( + SysadminNotifier::StoreFull); + } + else + { + throw; + } + } + + if(*pHaveJustCreatedDirOnServer) + { + rNotifier.NotifyDirectoryCreated(subDirObjectID, + nonVssDirPath, remoteDirPath); + } + + return subDirObjectID; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile(SyncParams &, BackupStoreDirectory *, int64_t, const std::string &) +// Purpose: Called to resolve difficulties when a directory is found on the +// store where a file is to be uploaded. +// Created: 9/7/04 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::RemoveDirectoryInPlaceOfFile( + SyncParams &rParams, + BackupStoreDirectory* pDirOnStore, + BackupStoreDirectory::Entry* pEntry, + const std::string &rFilename) +{ + // First, delete the directory + BackupProtocolCallable &connection(rParams.mrContext.GetConnection()); + connection.QueryDeleteDirectory(pEntry->GetObjectID()); + + BackupStoreFilenameClear clear(pEntry->GetName()); + rParams.mrContext.GetProgressNotifier().NotifyDirectoryDeleted( + pEntry->GetObjectID(), clear.GetClearFilename()); + + // Then, delete any directory record + std::map<std::string, BackupClientDirectoryRecord *>::iterator + e(mSubDirectories.find(rFilename)); + + if(e != mSubDirectories.end()) + { + // A record exists for this, remove it + BackupClientDirectoryRecord *psubDirRecord = e->second; + mSubDirectories.erase(e); + + // And delete the object + delete psubDirRecord; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::UploadFile( +// BackupClientDirectoryRecord::SyncParams &, +// const std::string &, +// const BackupStoreFilename &, +// int64_t, box_time_t, box_time_t, bool) +// Purpose: Private. Upload a file to the server. May send +// a patch instead of the whole thing +// Created: 20/1/04 +// +// -------------------------------------------------------------------------- +int64_t BackupClientDirectoryRecord::UploadFile( + BackupClientDirectoryRecord::SyncParams &rParams, + const std::string &rLocalPath, + const std::string &rNonVssFilePath, + const std::string &rRemotePath, + const BackupStoreFilenameClear &rStoreFilename, + int64_t FileSize, + box_time_t ModificationTime, + box_time_t AttributesHash, + bool NoPreviousVersionOnServer) +{ + BackupClientContext& rContext(rParams.mrContext); + ProgressNotifier& rNotifier(rContext.GetProgressNotifier()); + + // Get the connection + BackupProtocolCallable &connection(rContext.GetConnection()); + + // Info + int64_t objID = 0; + 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 && + FileSize >= rParams.mDiffingUploadSizeThreshold) + { + // YES -- try to do diff, if possible + // First, query the server to see if there's an old version available + std::auto_ptr<BackupProtocolSuccess> getBlockIndex(connection.QueryGetBlockIndexByName(mObjectID, rStoreFilename)); + diffFromID = getBlockIndex->GetObjectID(); + + if(diffFromID != 0) + { + // Found an old version + + // Get the index + std::auto_ptr<IOStream> blockIndexStream(connection.ReceiveStream()); + + // + // Diff the file + // + + rContext.ManageDiffProcess(); + + bool isCompletelyDifferent = false; + + apStreamToUpload = BackupStoreFile::EncodeFileDiff( + rLocalPath, + mObjectID, /* containing directory */ + rStoreFilename, diffFromID, *blockIndexStream, + connection.GetTimeout(), + &rContext, // DiffTimer implementation + 0 /* not interested in the modification time */, + &isCompletelyDifferent, + rParams.mpBackgroundTask); + + if(isCompletelyDifferent) + { + diffFromID = 0; + } + + rContext.UnManageDiffProcess(); + } + } + + if(apStreamToUpload.get()) + { + rNotifier.NotifyFileUploadingPatch(this, rNonVssFilePath, + apStreamToUpload->GetBytesToUpload()); + } + else // No patch upload, so do a normal upload + { + // below threshold or nothing to diff from, so upload whole + rNotifier.NotifyFileUploading(this, rNonVssFilePath); + + // Prepare to upload, getting a stream which will encode the file as we go along + apStreamToUpload = BackupStoreFile::EncodeFile( + rLocalPath, mObjectID, /* containing directory */ + rStoreFilename, NULL, &rParams, + &(rParams.mrRunStatusProvider), + rParams.mpBackgroundTask); + } + + 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) + { + rContext.UnManageDiffProcess(); + + if(e.GetType() == ConnectionException::ExceptionType && + e.GetSubType() == ConnectionException::Protocol_UnexpectedReply) + { + // Check and see what error the protocol has, + // this is more useful to users than the exception. + int type, subtype; + if(connection.GetLastError(type, subtype)) + { + if(type == BackupProtocolError::ErrorType + && subtype == BackupProtocolError::Err_StorageLimitExceeded) + { + // The hard limit was exceeded on the server, notify! + rParams.mrSysadminNotifier.NotifySysadmin( + SysadminNotifier::StoreFull); + // return an error code instead of + // throwing an exception that we + // can't debug. + return 0; + } + rNotifier.NotifyFileUploadServerError(this, + rNonVssFilePath, type, subtype); + } + } + + // Send the error on it's way + throw; + } + + rNotifier.NotifyFileUploaded(this, rNonVssFilePath, FileSize, + uploadedSize, objID); + + // Return the new object ID of this file + return objID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SetErrorWhenReadingFilesystemObject( +// SyncParams &, const char *) +// Purpose: Sets the error state when there were problems +// reading an object from the filesystem. +// Created: 29/3/04 +// +// -------------------------------------------------------------------------- +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)); + + // More detailed logging was already done by the caller, but if we + // have a read error reported, we need to be able to search the logs + // to find out which file it was, so we need to log a consistent and + // clear error message. + BOX_WARNING("Failed to backup file, see above for details: " << + rFilename); + + // Mark that an error occured in the parameters object + rParams.mReadErrorsOnFilesystemObjects = true; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncParams::SyncParams(BackupClientContext &) +// Purpose: Constructor +// Created: 8/3/04 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::SyncParams::SyncParams( + RunStatusProvider &rRunStatusProvider, + SysadminNotifier &rSysadminNotifier, + ProgressNotifier &rProgressNotifier, + BackupClientContext &rContext, + BackgroundTask *pBackgroundTask) +: mSyncPeriodStart(0), + mSyncPeriodEnd(0), + mMaxUploadWait(0), + mMaxFileTimeInFuture(99999999999999999LL), + mFileTrackingSizeThreshold(16*1024), + mDiffingUploadSizeThreshold(16*1024), + mpBackgroundTask(pBackgroundTask), + mrRunStatusProvider(rRunStatusProvider), + mrSysadminNotifier(rSysadminNotifier), + mrProgressNotifier(rProgressNotifier), + mrContext(rContext), + mReadErrorsOnFilesystemObjects(false), + mMaxUploadRate(0), + mUploadAfterThisTimeInTheFuture(99999999999999999LL), + mHaveLoggedWarningAboutFutureFileTimes(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::SyncParams::~SyncParams() +// Purpose: Destructor +// Created: 8/3/04 +// +// -------------------------------------------------------------------------- +BackupClientDirectoryRecord::SyncParams::~SyncParams() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::Deserialize(Archive & rArchive) +// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::Deserialize(Archive & rArchive) +{ + // Make deletion recursive + DeleteSubDirectories(); + + // Delete maps + if(mpPendingEntries != 0) + { + delete mpPendingEntries; + mpPendingEntries = 0; + } + + // + // + // + rArchive.Read(mObjectID); + rArchive.Read(mSubDirName); + rArchive.Read(mInitialSyncDone); + rArchive.Read(mSyncDone); + + // + // + // + int64_t iCount = 0; + rArchive.Read(iCount); + + if (iCount != sizeof(mStateChecksum)/sizeof(mStateChecksum[0])) + { + // we have some kind of internal system representation change: throw for now + THROW_EXCEPTION(CommonException, Internal) + } + + for (int v = 0; v < iCount; v++) + { + // Load each checksum entry + rArchive.Read(mStateChecksum[v]); + } + + // + // + // + iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + // load each pending entry + mpPendingEntries = new std::map<std::string, box_time_t>; + if (!mpPendingEntries) + { + throw std::bad_alloc(); + } + + for (int v = 0; v < iCount; v++) + { + std::string strItem; + box_time_t btItem; + + rArchive.Read(strItem); + rArchive.Read(btItem); + (*mpPendingEntries)[strItem] = btItem; + } + } + + // + // + // + iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + std::string strItem; + rArchive.Read(strItem); + + BackupClientDirectoryRecord* pSubDirRecord = + new BackupClientDirectoryRecord(0, ""); + // will be deserialized anyway, give it id 0 for now + + if (!pSubDirRecord) + { + throw std::bad_alloc(); + } + + /***** RECURSE *****/ + pSubDirRecord->Deserialize(rArchive); + mSubDirectories[strItem] = pSubDirRecord; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientDirectoryRecord::Serialize(Archive & rArchive) +// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void BackupClientDirectoryRecord::Serialize(Archive & rArchive) const +{ + // + // + // + rArchive.Write(mObjectID); + rArchive.Write(mSubDirName); + rArchive.Write(mInitialSyncDone); + rArchive.Write(mSyncDone); + + // + // + // + int64_t iCount = 0; + + // when reading back the archive, we will + // need to know how many items there are. + iCount = sizeof(mStateChecksum) / sizeof(mStateChecksum[0]); + rArchive.Write(iCount); + + for (int v = 0; v < iCount; v++) + { + rArchive.Write(mStateChecksum[v]); + } + + // + // + // + if (!mpPendingEntries) + { + iCount = 0; + rArchive.Write(iCount); + } + else + { + iCount = mpPendingEntries->size(); + rArchive.Write(iCount); + + for (std::map<std::string, box_time_t>::const_iterator + i = mpPendingEntries->begin(); + i != mpPendingEntries->end(); i++) + { + rArchive.Write(i->first); + rArchive.Write(i->second); + } + } + // + // + // + iCount = mSubDirectories.size(); + rArchive.Write(iCount); + + for (std::map<std::string, BackupClientDirectoryRecord*>::const_iterator + i = mSubDirectories.begin(); + i != mSubDirectories.end(); i++) + { + const BackupClientDirectoryRecord* pSubItem = i->second; + ASSERT(pSubItem); + + rArchive.Write(i->first); + pSubItem->Serialize(rArchive); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Location::Location() +// Purpose: Constructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +Location::Location() +: mIDMapIndex(0) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Location::~Location() +// Purpose: Destructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +Location::~Location() +{ } + +// -------------------------------------------------------------------------- +// +// 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(!mapDirectoryRecord.get()) + { + 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); + + mapDirectoryRecord->Serialize(rArchive); + } + + // + // + // + if(!mapExcludeFiles.get()) + { + 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); + + mapExcludeFiles->Serialize(rArchive); + } + + // + // + // + if(!mapExcludeDirs.get()) + { + 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); + + mapExcludeDirs->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) +{ + // + // + // + mapDirectoryRecord.reset(); + mapExcludeFiles.reset(); + mapExcludeDirs.reset(); + + // + // + // + 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(); + } + + mapDirectoryRecord.reset(pSubRecord); + mapDirectoryRecord->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) + { + mapExcludeFiles.reset(new ExcludeList); + if(!mapExcludeFiles.get()) + { + throw std::bad_alloc(); + } + + mapExcludeFiles->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) + { + mapExcludeDirs.reset(new ExcludeList); + if(!mapExcludeDirs.get()) + { + throw std::bad_alloc(); + } + + mapExcludeDirs->Deserialize(rArchive); + } + else + { + // there is something going on here + THROW_EXCEPTION(ClientException, CorruptStoreObjectInfoFile); + } +} diff --git a/lib/bbackupd/BackupClientDirectoryRecord.h b/lib/bbackupd/BackupClientDirectoryRecord.h new file mode 100644 index 00000000..865fc747 --- /dev/null +++ b/lib/bbackupd/BackupClientDirectoryRecord.h @@ -0,0 +1,244 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientDirectoryRecord.h +// Purpose: Implementation of record about directory for backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTDIRECTORYRECORD__H +#define BACKUPCLIENTDIRECTORYRECORD__H + +#include <string> +#include <map> +#include <memory> + +#include "BackgroundTask.h" +#include "BackupClientFileAttributes.h" +#include "BackupDaemonInterface.h" +#include "BackupStoreDirectory.h" +#include "BoxTime.h" +#include "MD5Digest.h" +#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; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientDirectoryRecord +// Purpose: Implementation of record about directory for backup client +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupClientDirectoryRecord +{ +public: + BackupClientDirectoryRecord(int64_t ObjectID, const std::string &rSubDirName); + virtual ~BackupClientDirectoryRecord(); + + void Deserialize(Archive & rArchive); + void Serialize(Archive & rArchive) const; +private: + BackupClientDirectoryRecord(const BackupClientDirectoryRecord &); +public: + + enum + { + UnknownDirectoryID = 0 + }; + + // -------------------------------------------------------------------------- + // + // Class + // Name: BackupClientDirectoryRecord::SyncParams + // Purpose: Holds parameters etc for directory syncing. Not passed as + // const, some parameters may be modified during sync. + // Created: 8/3/04 + // + // -------------------------------------------------------------------------- + class SyncParams : public ReadLoggingStream::Logger + { + public: + SyncParams( + RunStatusProvider &rRunStatusProvider, + SysadminNotifier &rSysadminNotifier, + ProgressNotifier &rProgressNotifier, + BackupClientContext &rContext, + BackgroundTask *pBackgroundTask); + ~SyncParams(); + private: + // No copying + SyncParams(const SyncParams&); + SyncParams &operator=(const SyncParams&); + + public: + // Data members are public, as accessors are not justified here + box_time_t mSyncPeriodStart; + box_time_t mSyncPeriodEnd; + box_time_t mMaxUploadWait; + box_time_t mMaxFileTimeInFuture; + int32_t mFileTrackingSizeThreshold; + int32_t mDiffingUploadSizeThreshold; + BackgroundTask *mpBackgroundTask; + RunStatusProvider &mrRunStatusProvider; + SysadminNotifier &mrSysadminNotifier; + ProgressNotifier &mrProgressNotifier; + BackupClientContext &mrContext; + bool mReadErrorsOnFilesystemObjects; + int64_t mMaxUploadRate; + + // Member variables modified by syncing process + box_time_t mUploadAfterThisTimeInTheFuture; + bool mHaveLoggedWarningAboutFutureFileTimes; + + bool StopRun() { return mrRunStatusProvider.StopRun(); } + void NotifySysadmin(SysadminNotifier::EventCode Event) + { + mrSysadminNotifier.NotifySysadmin(Event); + } + ProgressNotifier& GetProgressNotifier() const + { + return mrProgressNotifier; + } + + /* ReadLoggingStream::Logger implementation */ + virtual void Log(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length, elapsed, finish); + } + virtual void Log(int64_t readSize, int64_t offset, + int64_t length) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset, + length); + } + virtual void Log(int64_t readSize, int64_t offset) + { + mrProgressNotifier.NotifyReadProgress(readSize, offset); + } + }; + + void SyncDirectory(SyncParams &rParams, + int64_t ContainingDirectoryID, + const std::string &rLocalPath, + const std::string &rRemotePath, + const Location& rBackupLocation, + bool ThisDirHasJustBeenCreated = false); + + bool SyncDirectoryEntry(SyncParams &rParams, + ProgressNotifier& rNotifier, + const Location& rBackupLocation, + const std::string &rDirLocalPath, + MD5Digest& currentStateChecksum, + struct dirent *en, + EMU_STRUCT_STAT dir_st, + std::vector<std::string>& rDirs, + std::vector<std::string>& rFiles, + bool& rDownloadDirectoryRecordBecauseOfFutureFiles); + + std::string ConvertVssPathToRealPath(const std::string &rVssPath, + const Location& rBackupLocation); + + int64_t GetObjectID() const { return mObjectID; } + +private: + void DeleteSubDirectories(); + std::auto_ptr<BackupStoreDirectory> FetchDirectoryListing(SyncParams &rParams); + void UpdateAttributes(SyncParams &rParams, + BackupStoreDirectory *pDirOnStore, + const std::string &rLocalPath); +protected: // to allow tests to hook in before UpdateItems() runs + virtual 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); +private: + int64_t CreateRemoteDir(const std::string& localDirPath, + const std::string& nonVssDirPath, + const std::string& remoteDirPath, + BackupStoreFilenameClear& storeFilename, + bool* pHaveJustCreatedDirOnServer, + BackupClientDirectoryRecord::SyncParams &rParams); + int64_t UploadFile(SyncParams &rParams, + const std::string &rFilename, + const std::string &rNonVssFilePath, + const std::string &rRemotePath, + const BackupStoreFilenameClear &rStoreFilename, + int64_t FileSize, box_time_t ModificationTime, + box_time_t AttributesHash, bool NoPreviousVersionOnServer); + void SetErrorWhenReadingFilesystemObject(SyncParams &rParams, + 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); + + int64_t mObjectID; + std::string mSubDirName; + bool mInitialSyncDone; + bool mSyncDone; + + // Checksum of directory contents and attributes, used to detect changes + uint8_t mStateChecksum[MD5Digest::DigestLength]; + + std::map<std::string, box_time_t> *mpPendingEntries; + std::map<std::string, BackupClientDirectoryRecord *> mSubDirectories; + // mpPendingEntries is a pointer rather than simple a member + // variable, because most of the time it'll be empty. This would + // 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> mapDirectoryRecord; + std::auto_ptr<ExcludeList> mapExcludeFiles; + std::auto_ptr<ExcludeList> mapExcludeDirs; + int mIDMapIndex; + +#ifdef ENABLE_VSS + bool mIsSnapshotCreated; + VSS_ID mSnapshotVolumeId; + std::string mSnapshotPath; +#endif +}; + +#endif // BACKUPCLIENTDIRECTORYRECORD__H + + diff --git a/lib/bbackupd/BackupClientInodeToIDMap.cpp b/lib/bbackupd/BackupClientInodeToIDMap.cpp new file mode 100644 index 00000000..6eaf7394 --- /dev/null +++ b/lib/bbackupd/BackupClientInodeToIDMap.cpp @@ -0,0 +1,320 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientInodeToIDMap.cpp +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <depot.h> + +#define BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION +#include "BackupClientInodeToIDMap.h" +#undef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION + +#include "Archive.h" +#include "BackupStoreException.h" +#include "CollectInBufferStream.h" +#include "MemBlockStream.h" +#include "autogen_CommonException.h" + +#include "MemLeakFindOn.h" + +#define BOX_DBM_INODE_DB_VERSION_KEY "BackupClientInodeToIDMap.Version" +#define BOX_DBM_INODE_DB_VERSION_CURRENT 2 + +#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 +// Name: BackupClientInodeToIDMap::BackupClientInodeToIDMap() +// Purpose: Constructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap::BackupClientInodeToIDMap() + : mReadOnly(true), + mEmpty(false), + mpDepot(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::~BackupClientInodeToIDMap() +// Purpose: Destructor +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +BackupClientInodeToIDMap::~BackupClientInodeToIDMap() +{ + if(mpDepot != 0) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Open(const char *, bool, bool) +// Purpose: Open the database map, creating a file on disc to store everything +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::Open(const char *Filename, bool ReadOnly, + bool CreateNew) +{ + mFilename = Filename; + + // Correct arguments? + ASSERT(!(CreateNew && ReadOnly)); + + // Correct usage? + ASSERT_DBM_CLOSED(); + ASSERT(!mEmpty); + + // Open the database file + int mode = ReadOnly ? DP_OREADER : DP_OWRITER; + if(CreateNew) + { + mode |= DP_OCREAT; + } + + mpDepot = dpopen(Filename, mode, 0); + + if(!mpDepot) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure, + BOX_DBM_MESSAGE("Failed to open inode database: " << + mFilename)); + } + + const char* version_key = BOX_DBM_INODE_DB_VERSION_KEY; + int32_t version = 0; + + if(CreateNew) + { + version = BOX_DBM_INODE_DB_VERSION_CURRENT; + + int ret = dpput(mpDepot, version_key, strlen(version_key), + (char *)(&version), sizeof(version), DP_DKEEP); + + if(!ret) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure, + BOX_DBM_MESSAGE("Failed to write version number to inode " + "database: " << mFilename)); + } + } + else + { + int ret = dpgetwb(mpDepot, version_key, strlen(version_key), 0, + sizeof(version), (char *)(&version)); + + if(ret == -1) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure, + "Missing version number in inode database. Perhaps it " + "needs to be recreated: " << mFilename); + } + + if(ret != sizeof(version)) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure, + "Wrong size version number in inode database: expected " + << sizeof(version) << " bytes but found " << ret); + } + + if(version != BOX_DBM_INODE_DB_VERSION_CURRENT) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, BerkelyDBFailure, + "Wrong version number in inode database: expected " << + BOX_DBM_INODE_DB_VERSION_CURRENT << " but found " << + version << ". Perhaps it needs to be recreated: " << + mFilename); + } + + // By this point the version number has been checked and is OK. + } + + // Read only flag + mReadOnly = ReadOnly; +} + +// -------------------------------------------------------------------------- +// +// 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. +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::OpenEmpty() +{ + ASSERT_DBM_CLOSED(); + ASSERT(mpDepot == 0); + mEmpty = true; + mReadOnly = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Close() +// Purpose: Close the database file +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::Close() +{ + 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. +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupClientInodeToIDMap::AddToMap(InodeRefType InodeRef, int64_t ObjectID, + int64_t InDirectory, const std::string& LocalPath) +{ + if(mReadOnly) + { + THROW_EXCEPTION(BackupStoreException, InodeMapIsReadOnly); + } + + if(mpDepot == 0) + { + THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen); + } + + ASSERT_DBM_OPEN(); + + // Setup structures + CollectInBufferStream buf; + Archive arc(buf, IOStream::TimeOutInfinite); + arc.WriteExact((uint64_t)ObjectID); + arc.WriteExact((uint64_t)InDirectory); + arc.Write(LocalPath); + buf.SetForReading(); + + ASSERT_DBM_OK(dpput(mpDepot, (const char *)&InodeRef, sizeof(InodeRef), + (const char *)buf.GetBuffer(), buf.GetSize(), DP_DOVER), + "Failed to add record to inode database", mFilename, + BackupStoreException, BerkelyDBFailure); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupClientInodeToIDMap::Lookup(InodeRefType, +// int64_t &, int64_t &) const +// Purpose: Looks up an inode in the map, returning true if it +// exists, and the object ids of it and the directory +// it's in the reference arguments. +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +bool BackupClientInodeToIDMap::Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, + int64_t &rInDirectoryOut, std::string* pLocalPathOut) const +{ + if(mEmpty) + { + // Map is empty + return false; + } + + if(mpDepot == 0) + { + THROW_EXCEPTION(BackupStoreException, InodeMapNotOpen); + } + + ASSERT_DBM_OPEN(); + int size; + char* data = dpget(mpDepot, (const char *)&InodeRef, sizeof(InodeRef), + 0, -1, &size); + if(data == NULL) + { + // key not in file + return false; + } + + // Free data automatically when the guard goes out of scope. + MemoryBlockGuard<char *> guard(data); + MemBlockStream stream(data, size); + Archive arc(stream, IOStream::TimeOutInfinite); + + // Return data + try + { + arc.Read(rObjectIDOut); + arc.Read(rInDirectoryOut); + if(pLocalPathOut) + { + arc.Read(*pLocalPathOut); + } + } + catch(CommonException &e) + { + if(e.GetSubType() == CommonException::ArchiveBlockIncompleteRead) + { + THROW_FILE_ERROR("Failed to lookup record in inode database: " + << InodeRef << ": not enough data in record", mFilename, + BackupStoreException, BerkelyDBFailure); + // Need to throw precisely that exception to ensure that the + // invalid database is deleted, so that we don't hit the same + // error next time. + } + + throw; + } + + // Found + return true; +} diff --git a/lib/bbackupd/BackupClientInodeToIDMap.h b/lib/bbackupd/BackupClientInodeToIDMap.h new file mode 100644 index 00000000..4bb1e085 --- /dev/null +++ b/lib/bbackupd/BackupClientInodeToIDMap.h @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupClientInodeToIDMap.h +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPCLIENTINODETOIDMAP_H +#define BACKUPCLIENTINODETOIDMAP_H + +#include <sys/types.h> + +#include <map> +#include <utility> + +// avoid having to include the DB files when not necessary +#ifndef BACKIPCLIENTINODETOIDMAP_IMPLEMENTATION + class DEPOT; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupClientInodeToIDMap +// Purpose: Map of inode numbers to file IDs on the store +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +class BackupClientInodeToIDMap +{ +public: + BackupClientInodeToIDMap(); + ~BackupClientInodeToIDMap(); +private: + BackupClientInodeToIDMap(const BackupClientInodeToIDMap &rToCopy); // not allowed +public: + + void Open(const char *Filename, bool ReadOnly, bool CreateNew); + void OpenEmpty(); + + void AddToMap(InodeRefType InodeRef, int64_t ObjectID, + int64_t InDirectory, const std::string& LocalPath); + bool Lookup(InodeRefType InodeRef, int64_t &rObjectIDOut, + int64_t &rInDirectoryOut, std::string* pLocalPathOut = NULL) const; + + void Close(); + +private: + bool mReadOnly; + bool mEmpty; + std::string mFilename; + DEPOT *mpDepot; +}; + +#endif // BACKUPCLIENTINODETOIDMAP_H + + diff --git a/lib/bbackupd/BackupDaemon.cpp b/lib/bbackupd/BackupDaemon.cpp new file mode 100644 index 00000000..3427a722 --- /dev/null +++ b/lib/bbackupd/BackupDaemon.cpp @@ -0,0 +1,3648 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.cpp +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif +#ifdef HAVE_SIGNAL_H + #include <signal.h> +#endif +#ifdef HAVE_SYS_PARAM_H + #include <sys/param.h> +#endif +#ifdef HAVE_SYS_WAIT_H + #include <sys/wait.h> +#endif +#ifdef HAVE_SYS_MOUNT_H + #include <sys/mount.h> +#endif +#ifdef HAVE_MNTENT_H + #include <mntent.h> +#endif +#ifdef HAVE_SYS_MNTTAB_H + #include <cstdio> + #include <sys/mnttab.h> +#endif +#ifdef HAVE_PROCESS_H + #include <process.h> +#endif + +#include <iostream> +#include <set> +#include <sstream> + +#include "Configuration.h" +#include "IOStream.h" +#include "MemBlockStream.h" +#include "CommonException.h" +#include "BoxPortsAndFiles.h" + +#include "SSLLib.h" + +#include "autogen_BackupProtocol.h" +#include "autogen_ClientException.h" +#include "autogen_CommonException.h" +#include "autogen_ConversionException.h" +#include "Archive.h" +#include "BackupClientContext.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientDirectoryRecord.h" +#include "BackupClientFileAttributes.h" +#include "BackupClientInodeToIDMap.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupConstants.h" +#include "BackupDaemon.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BannerText.h" +#include "Conversion.h" +#include "ExcludeList.h" +#include "FileStream.h" +#include "IOStreamGetLine.h" +#include "LocalProcessStream.h" +#include "Logging.h" +#include "Random.h" +#include "Timer.h" +#include "Utils.h" + +#ifdef WIN32 + #include "Win32ServiceFunctions.h" + #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 + + // Mutex support by Achim: see https://www.boxbackup.org/ticket/67 + + // Creates the two mutexes checked for by the installer/uninstaller to + // see if the program is still running. One of the mutexes is created + // in the global name space (which makes it possible to access the + // mutex across user sessions in Windows XP); the other is created in + // the session name space (because versions of Windows NT prior to + // 4.0 TSE don't have a global name space and don't support the + // 'Global\' prefix). + + void CreateMutexes(const std::string& rName) + { + SECURITY_DESCRIPTOR SecurityDesc; + SECURITY_ATTRIBUTES SecurityAttr; + + /* By default on Windows NT, created mutexes are accessible only by the user + running the process. We need our mutexes to be accessible to all users, so + that the mutex detection can work across user sessions in Windows XP. To + do this we use a security descriptor with a null DACL. + */ + + InitializeSecurityDescriptor(&SecurityDesc, SECURITY_DESCRIPTOR_REVISION); + SetSecurityDescriptorDacl(&SecurityDesc, TRUE, NULL, FALSE); + SecurityAttr.nLength = sizeof(SecurityAttr); + SecurityAttr.lpSecurityDescriptor = &SecurityDesc; + SecurityAttr.bInheritHandle = FALSE; + // We don't care if this succeeds or fails. It's only used to + // ensure that an installer can detect if Box Backup is running. + CreateMutexA(&SecurityAttr, FALSE, rName.c_str()); + std::string global_name = "Global\\" + rName; + CreateMutexA(&SecurityAttr, FALSE, global_name.c_str()); + } +#endif + +#include "MemLeakFindOn.h" + +static const time_t MAX_SLEEP_TIME = 1024; + +// Make the actual sync period have a little bit of extra time, up to a 64th of the main sync period. +// This prevents repetative cycles of load on the server +#define SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY 6 + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::BackupDaemon() +// Purpose: constructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupDaemon::BackupDaemon() + : mState(BackupDaemon::State_Initialising), + mDeleteRedundantLocationsAfter(0), + mLastNotifiedEvent(SysadminNotifier::MAX), + mDeleteUnusedRootDirEntriesAfter(0), + mClientStoreMarker(BackupClientContext::ClientStoreMarker_NotKnown), + mStorageLimitExceeded(false), + mReadErrorsOnFilesystemObjects(false), + mLastSyncTime(0), + mNextSyncTime(0), + mCurrentSyncStartTime(0), + mUpdateStoreInterval(0), + mDeleteStoreObjectInfoFile(false), + mDoSyncForcedByPreviousSyncError(false), + mNumFilesUploaded(-1), + mNumDirsCreated(-1), + mMaxBandwidthFromSyncAllowScript(0), + mLogAllFileAccess(false), + mpProgressNotifier(this), + mpLocationResolver(this), + mpRunStatusProvider(this), + mpSysadminNotifier(this), + mapCommandSocketPollTimer(NULL) + #ifdef WIN32 + , mInstallService(false), + mRemoveService(false), + mRunAsService(false), + mServiceName("bbackupd") + #endif +#ifdef ENABLE_VSS + , mpVssBackupComponents(NULL) +#endif +{ + // Only ever one instance of a daemon + SSLLib::Initialise(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::~BackupDaemon() +// Purpose: Destructor +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +BackupDaemon::~BackupDaemon() +{ + DeleteAllLocations(); + DeleteAllIDMaps(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DaemonName() +// Purpose: Get name of daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +const char *BackupDaemon::DaemonName() const +{ + return "bbackupd"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DaemonBanner() +// Purpose: Daemon banner +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string BackupDaemon::DaemonBanner() const +{ + return BANNER_TEXT("Backup Client"); +} + +void BackupDaemon::Usage() +{ + this->Daemon::Usage(); + +#ifdef WIN32 + std::cout << + " -s Run as a Windows Service, for internal use only\n" + " -i Install Windows Service (you may want to specify a config file)\n" + " -r Remove Windows Service\n" + " -S <name> Service name for -i and -r options\n"; +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::GetConfigVerify() +// Purpose: Get configuration specification +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *BackupDaemon::GetConfigVerify() const +{ + // Defined elsewhere + return &BackupDaemonConfigVerify; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupInInitialProcess() +// Purpose: Platforms with non-checkable credentials on +// local sockets only. +// Prints a warning if the command socket is used. +// Created: 25/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetupInInitialProcess() +{ + const Configuration& config(GetConfiguration()); + + // These keys may or may not be required, depending on the configured + // store type (e.g. not when using Amazon S3 stores), so they can't be + // verified by BackupDaemonConfigVerify. + std::vector<std::string> requiredKeys; + requiredKeys.push_back("StoreHostname"); + requiredKeys.push_back("AccountNumber"); + requiredKeys.push_back("CertificateFile"); + requiredKeys.push_back("PrivateKeyFile"); + requiredKeys.push_back("TrustedCAsFile"); + bool missingRequiredKeys = false; + + for(std::vector<std::string>::const_iterator i = requiredKeys.begin(); + i != requiredKeys.end(); i++) + { + if(!config.KeyExists(*i)) + { + BOX_ERROR("Missing required configuration key: " << *i); + missingRequiredKeys = true; + } + } + + if(missingRequiredKeys) + { + THROW_EXCEPTION_MESSAGE(CommonException, InvalidConfiguration, + "Some required configuration keys are missing in " << + GetConfigFileName()); + } + +#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + // Print a warning on this platform if the CommandSocket is used. + if(GetConfiguration().KeyExists("CommandSocket")) + { + BOX_WARNING( + "==============================================================================\n" + "SECURITY WARNING: This platform cannot check the credentials of connections to\n" + "the command socket. This is a potential DoS security problem.\n" + "Remove the CommandSocket directive from the bbackupd.conf file if bbackupctl\n" + "is not used.\n" + "==============================================================================\n" + ); + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteAllLocations() +// Purpose: Deletes all records stored +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteAllLocations() +{ + // Run through, and delete everything + for(Locations::iterator i = mLocations.begin(); + i != mLocations.end(); ++i) + { + delete *i; + } + + // Clear the contents of the map, so it is empty + mLocations.clear(); + + // And delete everything from the associated mount vector + mIDMapMounts.clear(); +} + +#ifdef WIN32 +std::string BackupDaemon::GetOptionString() +{ + std::string oldOpts = this->Daemon::GetOptionString(); + ASSERT(oldOpts.find("s") == std::string::npos); + ASSERT(oldOpts.find("S") == std::string::npos); + ASSERT(oldOpts.find("i") == std::string::npos); + ASSERT(oldOpts.find("r") == std::string::npos); + return oldOpts + "sS:ir"; +} + +int BackupDaemon::ProcessOption(signed int option) +{ + switch(option) + { + case 's': + { + mRunAsService = true; + return 0; + } + + case 'S': + { + mServiceName = optarg; + Logging::SetProgramName(mServiceName); + return 0; + } + + case 'i': + { + mInstallService = true; + return 0; + } + + case 'r': + { + mRemoveService = true; + return 0; + } + + default: + { + return this->Daemon::ProcessOption(option); + } + } +} + +int BackupDaemon::Main(const std::string &rConfigFileName) +{ + if (mInstallService) + { + return InstallService(rConfigFileName.c_str(), mServiceName); + } + + if (mRemoveService) + { + 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 + + CreateMutexes("__boxbackup_mutex__"); + + int returnCode; + + if (mRunAsService) + { + // We will be called reentrantly by the Service Control + // Manager, and we had better not call OurService again! + mRunAsService = false; + + BOX_INFO("Box Backup service starting"); + returnCode = OurService(rConfigFileName.c_str()); + BOX_INFO("Box Backup service shut down"); + } + else + { + returnCode = this->Daemon::Main(rConfigFileName); + } + + return returnCode; +} +#endif // WIN32 + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::Run() +// Purpose: Run function for daemon +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::Run() +{ + // initialise global timer mechanism + Timers::Init(); + + mapCommandSocketPollTimer.reset(new Timer(COMMAND_SOCKET_POLL_INTERVAL, + "CommandSocketPollTimer")); + + #ifndef WIN32 + // Ignore SIGPIPE so that if a command connection is broken, + // the daemon doesn't terminate. + ::signal(SIGPIPE, SIG_IGN); + #endif + + // Create a command socket? + const Configuration &conf(GetConfiguration()); + if(conf.KeyExists("CommandSocket")) + { + // Yes, create a local UNIX socket + mapCommandSocketInfo.reset(new CommandSocketInfo); + const char *socketName = + conf.GetKeyValue("CommandSocket").c_str(); + #ifdef WIN32 + mapCommandSocketInfo->mListeningSocket.Listen( + socketName); + #else + ::unlink(socketName); + mapCommandSocketInfo->mListeningSocket.Listen( + Socket::TypeUNIX, socketName); + #endif + } + + // Handle things nicely on exceptions + try + { + Run2(); + } + catch(...) + { + try + { + mapCommandSocketInfo.reset(); + } + catch(std::exception &e) + { + BOX_WARNING("Internal error while closing command " + "socket after another exception, ignored: " << + e.what()); + } + catch(...) + { + BOX_WARNING("Error closing command socket after " + "exception, ignored."); + } + + mapCommandSocketPollTimer.reset(); + Timers::Cleanup(); + + throw; + } + + // Clean up + mapCommandSocketInfo.reset(); + mapCommandSocketPollTimer.reset(); + Timers::Cleanup(); +} + +void BackupDaemon::InitCrypto() +{ + // Read in the certificates creating a TLS context + const Configuration &conf(GetConfiguration()); + std::string certFile(conf.GetKeyValue("CertificateFile")); + std::string keyFile(conf.GetKeyValue("PrivateKeyFile")); + std::string caFile(conf.GetKeyValue("TrustedCAsFile")); + mTlsContext.Initialise(false /* as client */, certFile.c_str(), + keyFile.c_str(), caFile.c_str()); + + // Set up the keys for various things + BackupClientCryptoKeys_Setup(conf.GetKeyValue("KeysFile")); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::Run2() +// Purpose: Run function for daemon (second stage) +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::Run2() +{ + InitCrypto(); + + const Configuration &conf(GetConfiguration()); + + // How often to connect to the store (approximate) + mUpdateStoreInterval = SecondsToBoxTime( + conf.GetKeyValueInt("UpdateStoreInterval")); + mBackupErrorDelay = conf.GetKeyValueInt("BackupErrorDelay"); + + // But are we connecting automatically? + bool automaticBackup = conf.GetKeyValueBool("AutomaticBackup"); + + // When the next sync should take place -- which is ASAP + mNextSyncTime = 0; + + // When the last sync started (only updated if the store was not full when the sync ended) + mLastSyncTime = 0; + + // -------------------------------------------------------------------------------------------- + + mDeleteStoreObjectInfoFile = DeserializeStoreObjectInfo(mLastSyncTime, + mNextSyncTime); + + // -------------------------------------------------------------------------------------------- + + + // Set state + SetState(State_Idle); + + mDoSyncForcedByPreviousSyncError = false; + + // Loop around doing backups + do + { + // Flags used below + bool storageLimitExceeded = false; + bool doSync = false; + bool mDoSyncForcedByCommand = false; + + // Check whether we should be stopping, and if so, + // don't hang around waiting on the command socket. + if(StopRun()) + { + BOX_INFO("Skipping command socket polling " + "due to shutdown request"); + break; + } + + // Is a delay necessary? + box_time_t currentTime = GetCurrentBoxTime(); + box_time_t requiredDelay = (mNextSyncTime < currentTime) + ? (0) : (mNextSyncTime - currentTime); + mNextSyncTime = currentTime + requiredDelay; + + if (mDoSyncForcedByPreviousSyncError) + { + BOX_INFO("Last backup was not successful, " + "next one starting at " << + FormatTime(mNextSyncTime, false, true)); + } + else if (automaticBackup) + { + BOX_INFO("Automatic backups are enabled, " + "next one starting at " << + FormatTime(mNextSyncTime, false, true)); + } + else + { + BOX_INFO("No automatic backups, waiting for " + "bbackupctl snapshot command"); + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + + if(requiredDelay > SecondsToBoxTime(MAX_SLEEP_TIME)) + { + requiredDelay = SecondsToBoxTime(MAX_SLEEP_TIME); + } + + // Only delay if necessary + if(requiredDelay == 0) + { + // No sleep necessary, so don't listen on the command + // socket at all right now. + } + else if(mapCommandSocketInfo.get() != 0) + { + // A command socket exists, so sleep by waiting for a + // connection or command on it. + WaitOnCommandSocket(requiredDelay, doSync, + mDoSyncForcedByCommand); + } + else + { + // No command socket or connection, just do a normal + // sleep. + time_t sleepSeconds = + BoxTimeToSeconds(requiredDelay); + ::sleep((sleepSeconds <= 0) + ? 1 : sleepSeconds); + } + + // We have now slept, so if automaticBackup is enabled then + // it's time for a backup now. + + if(StopRun()) + { + BOX_INFO("Stopping idle loop due to shutdown request"); + break; + } + else if(doSync) + { + BOX_INFO("Starting a backup immediately due to " + "bbackupctl sync command"); + } + else if(GetCurrentBoxTime() < mNextSyncTime) + { + BOX_TRACE("Deadline not reached, sleeping again"); + continue; + } + else if(mDoSyncForcedByPreviousSyncError) + { + BOX_INFO("Last backup was not successful, next one " + "starting now"); + } + else if(!automaticBackup) + { + BOX_TRACE("Sleeping again because automatic backups " + "are not enabled"); + continue; + } + else + { + BOX_INFO("Automatic backups are enabled, next one " + "starting now"); + } + + // If we pass this point, or exit the loop, we should have + // logged something at INFO level or higher to explain why. + + // Use a script to see if sync is allowed now? + if(mDoSyncForcedByCommand) + { + BOX_INFO("Skipping SyncAllowScript due to bbackupctl " + "force-sync command"); + } + else + { + int d = UseScriptToSeeIfSyncAllowed(); + if(d > 0) + { + // Script has asked for a delay + mNextSyncTime = GetCurrentBoxTime() + + SecondsToBoxTime(d); + BOX_INFO("Impending backup stopped by " + "SyncAllowScript, next attempt " + "scheduled for " << + FormatTime(mNextSyncTime, false)); + continue; + } + } + + mCurrentSyncStartTime = GetCurrentBoxTime(); + RunSyncNowWithExceptionHandling(); + + // Set state + SetState(storageLimitExceeded?State_StorageLimitExceeded:State_Idle); + } + while(!StopRun()); + + // Make sure we have a clean start next time round (if restart) + DeleteAllLocations(); + DeleteAllIDMaps(); +} + +std::auto_ptr<BackupClientContext> BackupDaemon::RunSyncNowWithExceptionHandling() +{ + bool errorOccurred = false; + int errorCode = 0, errorSubCode = 0; + std::string errorString = "unknown"; + + try + { + OnBackupStart(); + // Do sync + RunSyncNow(); + } + catch(BoxException &e) + { + errorOccurred = true; + errorString = e.what(); + errorCode = e.GetType(); + errorSubCode = e.GetSubType(); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error during backup run: " << e.what()); + errorOccurred = true; + errorString = e.what(); + } + catch(...) + { + // TODO: better handling of exceptions here... + // need to be very careful + errorOccurred = true; + } + + // do not retry immediately without a good reason + mDoSyncForcedByPreviousSyncError = false; + + // Is it a berkely db failure? + bool isBerkelyDbFailure = false; + + // Notify system administrator about the final state of the backup + if(errorOccurred) + { + if (errorCode == BackupStoreException::ExceptionType + && errorSubCode == BackupStoreException::BerkelyDBFailure) + { + isBerkelyDbFailure = true; + } + + if(isBerkelyDbFailure) + { + // Delete corrupt files + DeleteCorruptBerkelyDbFiles(); + } + + ResetCachedState(); + + // Handle restart? + if(StopRun()) + { + BOX_NOTICE("Exception (" << errorCode << "/" << + errorSubCode << ") due to signal"); + OnBackupFinish(); + return mapClientContext; // releases mapClientContext + } + + NotifySysadmin(SysadminNotifier::BackupError); + + // If the Berkely db files get corrupted, + // delete them and try again immediately. + if(isBerkelyDbFailure) + { + BOX_ERROR("Berkely db inode map files corrupted, " + "deleting and restarting scan. Renamed files " + "and directories will not be tracked until " + "after this scan."); + ::sleep(1); + } + else + { + // Not restart/terminate, pause and retry + // Notify administrator + SetState(State_Error); + BOX_ERROR("Exception caught (" << errorString << + " " << errorCode << "/" << errorSubCode << + "), reset state and waiting to retry..."); + ::sleep(10); + mNextSyncTime = GetCurrentBoxTime() + + SecondsToBoxTime(mBackupErrorDelay) + + Random::RandomInt(mUpdateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + } + } + + if(mReadErrorsOnFilesystemObjects) + { + NotifySysadmin(SysadminNotifier::ReadError); + } + + if(mStorageLimitExceeded) + { + NotifySysadmin(SysadminNotifier::StoreFull); + } + + if (!errorOccurred && !mReadErrorsOnFilesystemObjects && + !mStorageLimitExceeded) + { + NotifySysadmin(SysadminNotifier::BackupOK); + } + + // If we were retrying after an error, and this backup succeeded, + // then now would be a good time to stop :-) + mDoSyncForcedByPreviousSyncError = errorOccurred && !isBerkelyDbFailure; + + OnBackupFinish(); + return mapClientContext; // releases mapClientContext +} + +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(); +} + +std::auto_ptr<BackupClientContext> BackupDaemon::GetNewContext +( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode +) +{ + std::auto_ptr<BackupClientContext> context(new BackupClientContext( + rResolver, rTLSContext, rHostname, Port, AccountNumber, + ExtendedLogging, ExtendedLogToFile, ExtendedLogFile, + rProgressNotifier, TcpNiceMode)); + return context; +} + +// Returns the BackupClientContext so that tests can use it to hold the +// connection open and prevent housekeeping from running. Otherwise don't use +// it, let it be destroyed and close the connection. +std::auto_ptr<BackupClientContext> BackupDaemon::RunSyncNow() +{ + Timers::AssertInitialised(); + + // Delete the serialised store object file, + // so that we don't try to reload it after a + // partially completed backup + if(mDeleteStoreObjectInfoFile && !DeleteStoreObjectInfo()) + { + BOX_ERROR("Failed to delete the StoreObjectInfoFile, " + "backup cannot continue safely."); + THROW_EXCEPTION(ClientException, + FailedToDeleteStoreObjectInfoFile); + } + + // In case the backup throws an exception, + // we should not try to delete the store info + // object file again. + mDeleteStoreObjectInfoFile = false; + + const Configuration &conf(GetConfiguration()); + + std::auto_ptr<FileLogger> fileLogger; + + if (conf.KeyExists("LogFile")) + { + bool overwrite = false; + if (conf.KeyExists("LogFileOverwrite")) + { + overwrite = conf.GetKeyValueBool("LogFileOverwrite"); + } + + Log::Level level = Log::INFO; + if (conf.KeyExists("LogFileLevel")) + { + level = Logging::GetNamedLevel( + conf.GetKeyValue("LogFileLevel")); + } + + fileLogger.reset(new FileLogger(conf.GetKeyValue("LogFile"), + level, !overwrite)); + } + + std::string extendedLogFile; + if (conf.KeyExists("ExtendedLogFile")) + { + extendedLogFile = conf.GetKeyValue("ExtendedLogFile"); + } + + if (conf.KeyExists("LogAllFileAccess")) + { + mLogAllFileAccess = conf.GetKeyValueBool("LogAllFileAccess"); + } + + // Then create a client context object (don't + // just connect, as this may be unnecessary) + mapClientContext = GetNewContext( + *mpLocationResolver, + mTlsContext, + conf.GetKeyValue("StoreHostname"), + conf.GetKeyValueInt("StorePort"), + conf.GetKeyValueUint32("AccountNumber"), + conf.GetKeyValueBool("ExtendedLogging"), + conf.KeyExists("ExtendedLogFile"), + extendedLogFile, + *mpProgressNotifier, + conf.GetKeyValueBool("TcpNice") + ); + + // The minimum age a file needs to be before it will be + // considered for uploading + box_time_t minimumFileAge = SecondsToBoxTime( + conf.GetKeyValueInt("MinimumFileAge")); + + // The maximum time we'll wait to upload a file, regardless + // of how often it's modified + box_time_t maxUploadWait = SecondsToBoxTime( + conf.GetKeyValueInt("MaxUploadWait")); + // Adjust by subtracting the minimum file age, so is relative + // to sync period end in comparisons + if (maxUploadWait > minimumFileAge) + { + maxUploadWait -= minimumFileAge; + } + else + { + maxUploadWait = 0; + } + + // Calculate the sync period of files to examine + box_time_t syncPeriodStart = mLastSyncTime; + box_time_t syncPeriodEnd = GetCurrentBoxTime() - minimumFileAge; + + if(syncPeriodStart >= syncPeriodEnd && + syncPeriodStart - syncPeriodEnd < minimumFileAge) + { + // This can happen if we receive a force-sync command less + // than minimumFileAge after the last sync. Deal with it by + // moving back syncPeriodStart, which should not do any + // damage. + syncPeriodStart = syncPeriodEnd - SecondsToBoxTime(1); + } + + if(syncPeriodStart >= syncPeriodEnd) + { + BOX_ERROR("Invalid (negative) sync period: perhaps your clock " + "is going backwards? (" << syncPeriodStart << " to " << + syncPeriodEnd << ")"); + THROW_EXCEPTION(ClientException, ClockWentBackwards); + } + + // Check logic + ASSERT(syncPeriodEnd > syncPeriodStart); + // Paranoid check on sync times + if(syncPeriodStart >= syncPeriodEnd) + { + return mapClientContext; // releases mapClientContext + } + + // Adjust syncPeriodEnd to emulate snapshot behaviour properly + box_time_t syncPeriodEndExtended = syncPeriodEnd; + + // Using zero min file age? + if(minimumFileAge == 0) + { + // Add a year on to the end of the end time, + // to make sure we sync files which are + // modified after the scan run started. + // Of course, they may be eligible to be + // synced again the next time round, + // but this should be OK, because the changes + // only upload should upload no data. + syncPeriodEndExtended += SecondsToBoxTime( + (time_t)(356*24*3600)); + } + + // Set up the sync parameters + BackupClientDirectoryRecord::SyncParams params(*mpRunStatusProvider, + *mpSysadminNotifier, *mpProgressNotifier, *mapClientContext, this); + params.mSyncPeriodStart = syncPeriodStart; + params.mSyncPeriodEnd = syncPeriodEndExtended; + // use potentially extended end time + params.mMaxUploadWait = maxUploadWait; + params.mFileTrackingSizeThreshold = + conf.GetKeyValueInt("FileTrackingSizeThreshold"); + params.mDiffingUploadSizeThreshold = + 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; + mReadErrorsOnFilesystemObjects = false; + + // Setup various timings + int maximumDiffingTime = 600; + int keepAliveTime = 60; + + // max diffing time, keep-alive time + if(conf.KeyExists("MaximumDiffingTime")) + { + maximumDiffingTime = conf.GetKeyValueInt("MaximumDiffingTime"); + } + if(conf.KeyExists("KeepAliveTime")) + { + keepAliveTime = conf.GetKeyValueInt("KeepAliveTime"); + } + + mapClientContext->SetMaximumDiffingTime(maximumDiffingTime); + mapClientContext->SetKeepAliveTime(keepAliveTime); + + // Set store marker + mapClientContext->SetClientStoreMarker(mClientStoreMarker); + + // Set up the locations, if necessary -- need to do it here so we have + // a (potential) connection to use. + { + const Configuration &locations( + conf.GetSubConfiguration( + "BackupLocations")); + + // Make sure all the directory records + // are set up + SetupLocations(*mapClientContext, locations); + } + + mpProgressNotifier->NotifyIDMapsSetup(*mapClientContext); + + // Get some ID maps going + SetupIDMapsForSync(); + + // Delete any unused directories? + DeleteUnusedRootDirEntries(*mapClientContext); + +#ifdef ENABLE_VSS + CreateVssBackupComponents(); +#endif + + // Go through the records, syncing them + for(Locations::const_iterator + i(mLocations.begin()); + i != mLocations.end(); ++i) + { + // Set current and new ID map pointers + // in the context + mapClientContext->SetIDMaps(mCurrentIDMaps[(*i)->mIDMapIndex], + mNewIDMaps[(*i)->mIDMapIndex]); + + // Set exclude lists (context doesn't + // take ownership) + mapClientContext->SetExcludeLists( + (*i)->mapExcludeFiles.get(), + (*i)->mapExcludeDirs.get()); + + // Sync the directory + std::string locationPath = (*i)->mPath; +#ifdef ENABLE_VSS + if((*i)->mIsSnapshotCreated) + { + locationPath = (*i)->mSnapshotPath; + } +#endif + + (*i)->mapDirectoryRecord->SyncDirectory(params, + BackupProtocolListDirectory::RootDirectory, + locationPath, std::string("/") + (*i)->mName, **i); + + // Unset exclude lists (just in case) + mapClientContext->SetExcludeLists(0, 0); + } + + // Perform any deletions required -- these are + // delayed until the end to allow renaming to + // happen neatly. + mapClientContext->PerformDeletions(); + +#ifdef ENABLE_VSS + CleanupVssBackupComponents(); +#endif + + // Get the new store marker + mClientStoreMarker = mapClientContext->GetClientStoreMarker(); + mStorageLimitExceeded = mapClientContext->StorageLimitExceeded(); + mReadErrorsOnFilesystemObjects |= + params.mReadErrorsOnFilesystemObjects; + + if(!mStorageLimitExceeded) + { + // The start time of the next run is the end time of this + // run. This is only done if the storage limit wasn't + // exceeded (as things won't have been done properly if + // it was) + mLastSyncTime = syncPeriodEnd; + } + + // Commit the ID Maps + CommitIDMapsAfterSync(); + + // Calculate when the next sync run should be + mNextSyncTime = mCurrentSyncStartTime + + mUpdateStoreInterval + + Random::RandomInt(mUpdateStoreInterval >> + SYNC_PERIOD_RANDOM_EXTRA_TIME_SHIFT_BY); + + // -------------------------------------------------------------------------------------------- + + // We had a successful backup, save the store + // info. If we save successfully, we must + // delete the file next time we start a backup + + mDeleteStoreObjectInfoFile = + SerializeStoreObjectInfo(mLastSyncTime, mNextSyncTime); + + // -------------------------------------------------------------------------------------------- + + return mapClientContext; // releases mapClientContext +} + +#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 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; + } + 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; + + // 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_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; + VssFreeSnapshotProperties(&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); + VssFreeSnapshotProperties(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() +{ + ResetLogFile(); + + // Touch a file to record times in filesystem + TouchFileInWorkingDir("last_sync_start"); + + // Reset statistics on uploads + BackupStoreFile::ResetStats(); + + // Tell anything connected to the command socket + SendSyncStartOrFinish(true /* start */); + + // Notify administrator + NotifySysadmin(SysadminNotifier::BackupStart); + + // Setup timer for polling the command socket + mapCommandSocketPollTimer.reset(new Timer(COMMAND_SOCKET_POLL_INTERVAL, + "CommandSocketPollTimer")); + + // Set state and log start + SetState(State_Connected); + BOX_NOTICE("Beginning scan of local files"); +} + +void BackupDaemon::OnBackupFinish() +{ + try + { + // 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 + << ", " << mNumFilesUploaded << " files uploaded, " + << mNumDirsCreated << " dirs created"); + + // Reset statistics again + BackupStoreFile::ResetStats(); + + // Notify administrator + NotifySysadmin(SysadminNotifier::BackupFinish); + + // Stop the timer for polling the command socket, + // to prevent needless alarms while sleeping. + mapCommandSocketPollTimer.reset(); + + // 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()); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::UseScriptToSeeIfSyncAllowed() +// Purpose: Private. Use a script to see if the sync should be +// allowed now (if configured). Returns -1 if it's +// allowed, time in seconds to wait otherwise. +// Created: 21/6/04 +// +// -------------------------------------------------------------------------- +int BackupDaemon::UseScriptToSeeIfSyncAllowed() +{ + const Configuration &conf(GetConfiguration()); + + // Got a script to run? + if(!conf.KeyExists("SyncAllowScript")) + { + // No. Do sync. + return -1; + } + + // If there's no result, try again in five minutes + int waitInSeconds = (60*5); + + std::string script(conf.GetKeyValue("SyncAllowScript") + + " \"" + GetConfigFileName() + "\""); + + // Run it? + pid_t pid = 0; + try + { + std::auto_ptr<IOStream> pscript(LocalProcessStream(script, + pid)); + + // Read in the result + IOStreamGetLine getLine(*pscript); + std::string line; + if(getLine.GetLine(line, true, 30000)) // 30 seconds should be enough + { + 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) + { + BOX_ERROR("Internal error running SyncAllowScript: " + << e.what() << " (" << script << ")"); + } + catch(...) + { + // Ignore any exceptions + // Log that something bad happened + BOX_ERROR("Unknown error running SyncAllowScript (" << + script << ")"); + } + + // Wait and then cleanup child process, if any + if(pid != 0) + { + int status = 0; + ::waitpid(pid, &status, 0); + } + + 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, sleeping for " + << waitInSeconds << " seconds (" << script << ")"); + 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 (" << 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; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::RunBackgroundTask() +// Purpose: Checks for connections or commands on the command +// socket and handles them with minimal delay. Polled +// during lengthy operations such as file uploads. +// Created: 07/04/14 +// +// -------------------------------------------------------------------------- +bool BackupDaemon::RunBackgroundTask(State state, uint64_t progress, + uint64_t maximum) +{ + BOX_TRACE("BackupDaemon::RunBackgroundTask: state = " << state << + ", progress = " << progress << "/" << maximum); + + if(!mapCommandSocketPollTimer.get()) + { + return true; // no background task + } + + if(mapCommandSocketPollTimer->HasExpired()) + { + mapCommandSocketPollTimer->Reset(COMMAND_SOCKET_POLL_INTERVAL); + } + else + { + // Do no more work right now + return true; + } + + if(mapCommandSocketInfo.get()) + { + BOX_TRACE("BackupDaemon::RunBackgroundTask: polling command socket"); + + bool sync_flag_out, sync_is_forced_out; + + WaitOnCommandSocket(0, // RequiredDelay + sync_flag_out, sync_is_forced_out); + + if(sync_flag_out) + { + BOX_WARNING("Ignoring request to sync while " + "already syncing."); + } + } + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::WaitOnCommandSocket(box_time_t, bool &, bool &) +// Purpose: Waits on a the command socket for a time of UP TO +// the required time but may be much less, and handles +// a command if necessary. +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut) +{ + DoSyncFlagOut = false; + SyncIsForcedOut = false; + + ASSERT(mapCommandSocketInfo.get()); + if(!mapCommandSocketInfo.get()) + { + // failure case isn't too bad + ::sleep(1); + return; + } + + BOX_TRACE("Wait on command socket, delay = " << + BOX_FORMAT_MICROSECONDS(RequiredDelay)); + + try + { + // Timeout value for connections and things + int timeout = ((int)BoxTimeToMilliSeconds(RequiredDelay)) + 1; + // Handle bad boundary cases + if(timeout <= 0) timeout = 1; + if(timeout == INFTIM) timeout = 100000; + + // Wait for socket connection, or handle a command? + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + // There should be no GetLine, as it would be holding onto a + // pointer to a dead mpConnectedSocket. + ASSERT(!mapCommandSocketInfo->mapGetLine.get()); + + // No connection, listen for a new one + mapCommandSocketInfo->mpConnectedSocket.reset( + mapCommandSocketInfo->mListeningSocket.Accept(timeout).release()); + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + // If a connection didn't arrive, there was a timeout, which means we've + // waited long enough and it's time to go. + return; + } + else + { +#ifdef PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + bool uidOK = true; + BOX_WARNING("On this platform, no security check can be made on the credentials of peers connecting to the command socket. (bbackupctl)"); +#else + // Security check -- does the process connecting to this socket have + // the same UID as this process? + bool uidOK = false; + // BLOCK + { + uid_t remoteEUID = 0xffff; + gid_t remoteEGID = 0xffff; + if(mapCommandSocketInfo->mpConnectedSocket->GetPeerCredentials(remoteEUID, remoteEGID)) + { + // Credentials are available -- check UID + if(remoteEUID == ::getuid()) + { + // Acceptable + uidOK = true; + } + } + } +#endif // PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET + + // Is this an acceptable connection? + if(!uidOK) + { + // Dump the connection + BOX_ERROR("Incoming command connection from peer had different user ID than this process, or security check could not be completed."); + mapCommandSocketInfo->mpConnectedSocket.reset(); + return; + } + else + { + // Log + BOX_INFO("Connection from command socket"); + + // Send a header line summarising the configuration and current state + const Configuration &conf(GetConfiguration()); + std::ostringstream hello; + hello << "bbackupd: " << + (conf.GetKeyValueBool("AutomaticBackup") ? 1 : 0) + << " " << + conf.GetKeyValueInt("UpdateStoreInterval") + << " " << + conf.GetKeyValueInt("MinimumFileAge") + << " " << + conf.GetKeyValueInt("MaxUploadWait") + << "\nstate " << mState << "\n"; + mapCommandSocketInfo->mpConnectedSocket->Write( + hello.str(), timeout); + + // Set the timeout to something very small, so we don't wait too long on waiting + // for any incoming data + timeout = 10; // milliseconds + } + } + + mapCommandSocketInfo->mapGetLine.reset( + new IOStreamGetLine( + *(mapCommandSocketInfo->mpConnectedSocket.get()))); + } + + // So there must be a connection now. + ASSERT(mapCommandSocketInfo->mpConnectedSocket.get() != 0); + ASSERT(mapCommandSocketInfo->mapGetLine.get() != 0); + + // Ping the remote side, to provide errors which will mean the socket gets closed + mapCommandSocketInfo->mpConnectedSocket->Write("ping\n", 5, + timeout); + + // Wait for a command or something on the socket + std::string command; + while(mapCommandSocketInfo->mapGetLine.get() != 0 + && !mapCommandSocketInfo->mapGetLine->IsEOF() + && mapCommandSocketInfo->mapGetLine->GetLine(command, false /* no preprocessing */, timeout)) + { + BOX_TRACE("Receiving command '" << command + << "' over command socket"); + + bool sendOK = false; + bool sendResponse = true; + + // Command to process! + if(command == "quit" || command == "") + { + // Close the socket. + CloseCommandConnection(); + sendResponse = false; + } + else if(command == "sync") + { + // Sync now! + DoSyncFlagOut = true; + SyncIsForcedOut = false; + sendOK = true; + } + else if(command == "force-sync") + { + // Sync now (forced -- overrides any SyncAllowScript) + DoSyncFlagOut = true; + SyncIsForcedOut = true; + sendOK = true; + } + else if(command == "reload") + { + // Reload the configuration + SetReloadConfigWanted(); + sendOK = true; + } + else if(command == "terminate") + { + // Terminate the daemon cleanly + SetTerminateWanted(); + sendOK = true; + } + + // Send a response back? + if(sendResponse) + { + std::string response = sendOK ? "ok\n" : "error\n"; + mapCommandSocketInfo->mpConnectedSocket->Write( + response, timeout); + } + + // Set timeout to something very small, so this just checks for data which is waiting + timeout = 1; + } + + // Close on EOF? + if(mapCommandSocketInfo->mapGetLine.get() != 0 && + mapCommandSocketInfo->mapGetLine->IsEOF()) + { + CloseCommandConnection(); + } + } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write to command socket: " << ce.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write to command socket: " << + e.what()); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } + catch(...) + { + BOX_ERROR("Failed to write to command socket: unknown error"); + + // If an error occurs, and there is a connection active, + // just close that connection and continue. Otherwise, + // let the error propagate. + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + throw; // thread will die + } + else + { + // Close socket and ignore error + CloseCommandConnection(); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CloseCommandConnection() +// Purpose: Close the command connection, ignoring any errors +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::CloseCommandConnection() +{ + try + { + BOX_TRACE("Closing command connection"); + mapCommandSocketInfo->mapGetLine.reset(); + mapCommandSocketInfo->mpConnectedSocket.reset(); + } + catch(std::exception &e) + { + BOX_ERROR("Internal error while closing command " + "socket: " << e.what()); + } + catch(...) + { + // Ignore any errors + } +} + + +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.cpp +// Purpose: Send a start or finish sync message to the command socket, if it's connected. +// +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SendSyncStartOrFinish(bool SendStart) +{ + // The bbackupctl program can't rely on a state change, because it + // may never change if the server doesn't need to be contacted. + + if(mapCommandSocketInfo.get() && + mapCommandSocketInfo->mpConnectedSocket.get() != 0) + { + std::string message = SendStart ? "start-sync" : "finish-sync"; + try + { + message += "\n"; + mapCommandSocketInfo->mpConnectedSocket->Write(message, + 1); // short timeout, it's overlapped + } + catch(std::exception &e) + { + BOX_ERROR("Internal error while sending to " + "command socket client: " << e.what()); + CloseCommandConnection(); + } + catch(...) + { + CloseCommandConnection(); + } + } +} + + + + +#if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_NMTONNAME) + // string comparison ordering for when mount points are handled + // by code, rather than the OS. + typedef struct + { + bool operator()(const std::string &s1, const std::string &s2) + { + if(s1.size() == s2.size()) + { + // Equal size, sort according to natural sort order + return s1 < s2; + } + else + { + // Make sure longer strings go first + return s1.size() > s2.size(); + } + } + } mntLenCompare; +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupLocations(BackupClientContext &, const Configuration &) +// Purpose: Makes sure that the list of directories records is correctly set up +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf) +{ + // Going to need a copy of the root directory. Get a connection, + // and fetch it. + BackupProtocolCallable& connection(rClientContext.GetConnection()); + + // Ask server for a list of everything in the root directory, + // which is a directory itself + std::auto_ptr<BackupProtocolSuccess> dirreply( + connection.QueryListDirectory( + BackupProtocolListDirectory::RootDirectory, + // only directories + BackupProtocolListDirectory::Flags_Dir, + // exclude old/deleted stuff + BackupProtocolListDirectory::Flags_Deleted | + BackupProtocolListDirectory::Flags_OldVersion, + false /* no attributes */)); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(connection.ReceiveStream()); + dir.ReadFromStream(*dirstream, connection.GetTimeout()); + + // Map of mount names to ID map index + std::map<std::string, int> mounts; + int numIDMaps = 0; + +#ifdef HAVE_MOUNTS +#if !defined(HAVE_STRUCT_STATFS_F_MNTONNAME) && !defined(HAVE_STRUCT_STATVFS_F_MNTONNAME) + // Linux and others can't tell you where a directory is mounted. So we + // have to read the mount entries from /etc/mtab! Bizarre that the OS + // itself can't tell you, but there you go. + std::set<std::string, mntLenCompare> mountPoints; + // BLOCK + FILE *mountPointsFile = 0; + +#ifdef HAVE_STRUCT_MNTENT_MNT_DIR + // Open mounts file + mountPointsFile = ::setmntent("/proc/mounts", "r"); + if(mountPointsFile == 0) + { + mountPointsFile = ::setmntent("/etc/mtab", "r"); + } + if(mountPointsFile == 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + + try + { + // Read all the entries, and put them in the set + struct mntent *entry = 0; + while((entry = ::getmntent(mountPointsFile)) != 0) + { + BOX_TRACE("Found mount point at " << entry->mnt_dir); + mountPoints.insert(std::string(entry->mnt_dir)); + } + + // Close mounts file + ::endmntent(mountPointsFile); + } + catch(...) + { + ::endmntent(mountPointsFile); + throw; + } +#else // ! HAVE_STRUCT_MNTENT_MNT_DIR + // Open mounts file + mountPointsFile = ::fopen("/etc/mnttab", "r"); + if(mountPointsFile == 0) + { + THROW_EXCEPTION(CommonException, OSFileError); + } + + try + { + // Read all the entries, and put them in the set + struct mnttab entry; + while(getmntent(mountPointsFile, &entry) == 0) + { + BOX_TRACE("Found mount point at " << entry.mnt_mountp); + mountPoints.insert(std::string(entry.mnt_mountp)); + } + + // Close mounts file + ::fclose(mountPointsFile); + } + catch(...) + { + ::fclose(mountPointsFile); + throw; + } +#endif // HAVE_STRUCT_MNTENT_MNT_DIR + // Check sorting and that things are as we expect + ASSERT(mountPoints.size() > 0); +#ifndef BOX_RELEASE_BUILD + { + std::set<std::string, mntLenCompare>::reverse_iterator i(mountPoints.rbegin()); + ASSERT(*i == "/"); + } +#endif // n BOX_RELEASE_BUILD +#endif // n HAVE_STRUCT_STATFS_F_MNTONNAME || n HAVE_STRUCT_STATVFS_F_MNTONNAME +#endif // HAVE_MOUNTS + + // Then... go through each of the entries in the configuration, + // 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)); + std::auto_ptr<Location> apLoc; + + try + { + 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->mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rConfig)); + pLoc->mapExcludeDirs.reset(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(pLoc->mName); // generate the filename + BackupStoreDirectory::Entry *en = iter.FindMatchingClearName(dirname); + int64_t oid = 0; + if(en != 0) + { + oid = en->GetObjectID(); + + // Delete the entry from the directory, so we get a list of + // unused root directories at the end of this. + dir.DeleteEntry(oid); + } + + // Do a fsstat on the pathname to find out which mount it's on + { + +#if defined HAVE_STRUCT_STATFS_F_MNTONNAME || defined HAVE_STRUCT_STATVFS_F_MNTONNAME || defined WIN32 + + // BSD style statfs -- includes mount point, which is nice. +#ifdef HAVE_STRUCT_STATVFS_F_MNTONNAME + struct statvfs s; + if(::statvfs(pLoc->mPath.c_str(), &s) != 0) +#else // HAVE_STRUCT_STATVFS_F_MNTONNAME + struct statfs s; + if(::statfs(pLoc->mPath.c_str(), &s) != 0) +#endif // HAVE_STRUCT_STATVFS_F_MNTONNAME + { + THROW_SYS_ERROR("Failed to stat path " + "'" << pLoc->mPath << "' " + "for location " + "'" << pLoc->mName << "'", + CommonException, OSFileError); + } + + // Where the filesystem is mounted + std::string mountName(s.f_mntonname); + +#else // !HAVE_STRUCT_STATFS_F_MNTONNAME && !WIN32 + + // Warn in logs if the directory isn't absolute + if(pLoc->mPath[0] != '/') + { + BOX_WARNING("Location path '" + << pLoc->mPath + << "' is not absolute"); + } + // Go through the mount points found, and find a suitable one + std::string mountName("/"); + { + std::set<std::string, mntLenCompare>::const_iterator i(mountPoints.begin()); + BOX_TRACE(mountPoints.size() + << " potential mount points"); + for(; i != mountPoints.end(); ++i) + { + // Compare first n characters with the filename + // 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(), pLoc->mPath.c_str(), i->size()) == 0) + { + // Match + mountName = *i; + break; + } + } + BOX_TRACE("mount point chosen for " + << pLoc->mPath << " is " + << mountName); + } + +#endif + + // Got it? + std::map<std::string, int>::iterator f(mounts.find(mountName)); + if(f != mounts.end()) + { + // Yes -- store the index + pLoc->mIDMapIndex = f->second; + } + else + { + // No -- new index + pLoc->mIDMapIndex = numIDMaps; + mounts[mountName] = numIDMaps; + + // Store the mount name + mIDMapMounts.push_back(mountName); + + // Increment number of maps + ++numIDMaps; + } + } + + // Does this exist on the server? + if(en == 0) + { + // Doesn't exist, so it has to be created on the server. Let's go! + // First, get the directory's attributes and modification time + box_time_t attrModTime = 0; + BackupClientFileAttributes attr; + try + { + attr.ReadAttributes(pLoc->mPath.c_str(), + true /* directories have zero mod times */, + 0 /* not interested in mod time */, + &attrModTime /* get the attribute modification time */); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to get attributes " + "for path '" << pLoc->mPath + << "', skipping location '" << + pLoc->mName << "'"); + throw; + } + + // Execute create directory command + try + { + std::auto_ptr<IOStream> attrStream( + new MemBlockStream(attr)); + std::auto_ptr<BackupProtocolSuccess> + dirCreate(connection.QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + attrModTime, dirname, attrStream)); + + // Object ID for later creation + oid = dirCreate->GetObjectID(); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to create remote " + "directory '/" << pLoc->mName << + "', skipping location '" << + pLoc->mName << "'"); + throw; + } + + } + + // Create and store the directory object for the root of this location + ASSERT(oid != 0); + if(pLoc->mapDirectoryRecord.get() == NULL) + { + pLoc->mapDirectoryRecord.reset( + new BackupClientDirectoryRecord(oid, *pLocName)); + } + + // Remove it from the temporary list to avoid deletion + tmpLocations.remove(pLoc); + + // Push it back on the vector of locations + 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 '" + << pLoc->mName << "' path '" + << pLoc->mPath << "': " << e.what() << + ": please check for previous errors"); + mReadErrorsOnFilesystemObjects = true; + } + catch(...) + { + BOX_ERROR("Failed to configure location '" + << pLoc->mName << "' path '" + << pLoc->mPath << "': please check for " + "previous errors"); + 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 && + mDeleteRedundantLocationsAfter == 0) + { + BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " + "in root directory found, but will not delete because " + "DeleteRedundantLocationsAfter = 0"); + } + else if(dir.GetNumberOfEntries() > 0) + { + box_time_t now = GetCurrentBoxTime(); + + // This should reset the timer if the list of unused + // locations changes, but it will not if the number of + // unused locations does not change, but the locations + // do change, e.g. one mysteriously appears and another + // mysteriously appears. (FIXME) + if (dir.GetNumberOfEntries() != mUnusedRootDirEntries.size() || + mDeleteUnusedRootDirEntriesAfter == 0) + { + mDeleteUnusedRootDirEntriesAfter = now + + SecondsToBoxTime(mDeleteRedundantLocationsAfter); + } + + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + + BOX_NOTICE(dir.GetNumberOfEntries() << " redundant locations " + "in root directory found, will delete from store " + "after " << secs << " seconds."); + + // Store directories in list of things to delete + mUnusedRootDirEntries.clear(); + BackupStoreDirectory::Iterator iter(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = iter.Next()) != 0) + { + // Add name to list + BackupStoreFilenameClear clear(en->GetName()); + const std::string &name(clear.GetClearFilename()); + mUnusedRootDirEntries.push_back( + std::pair<int64_t,std::string> + (en->GetObjectID(), name)); + // Log this + BOX_INFO("Unused location in root: " << name); + } + ASSERT(mUnusedRootDirEntries.size() > 0); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetupIDMapsForSync() +// Purpose: Sets up ID maps for the sync process -- make sure they're all there +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetupIDMapsForSync() +{ + // Make sure we have some blank, empty ID maps + DeleteIDMapVector(mNewIDMaps); + FillIDMapVector(mNewIDMaps, true /* new maps */); + DeleteIDMapVector(mCurrentIDMaps); + FillIDMapVector(mCurrentIDMaps, false /* new maps */); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &) +// Purpose: Fills the vector with the right number of empty ID maps +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector, bool NewMaps) +{ + ASSERT(rVector.size() == 0); + rVector.reserve(mIDMapMounts.size()); + + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + // Create the object + BackupClientInodeToIDMap *pmap = new BackupClientInodeToIDMap(); + try + { + // Get the base filename of this map + std::string filename; + MakeMapBaseName(l, filename); + + // If it's a new one, add a suffix + if(NewMaps) + { + 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())) + { + pmap->OpenEmpty(); + } + else + { + // Open the map + pmap->Open(filename.c_str(), !NewMaps /* read only */, NewMaps /* create new */); + } + + // Store on vector + rVector.push_back(pmap); + } + catch(...) + { + delete pmap; + throw; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteCorruptBerkelyDbFiles() +// Purpose: Delete the Berkely db files from disc after they have been corrupted. +// Created: 14/9/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteCorruptBerkelyDbFiles() +{ + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + // Get the base filename of this map + std::string filename; + MakeMapBaseName(l, filename); + + // Delete the file + BOX_TRACE("Deleting " << filename); + ::unlink(filename.c_str()); + + // Add a suffix for the new map + filename += ".n"; + + // Delete that too + BOX_TRACE("Deleting " << filename); + ::unlink(filename.c_str()); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeMapBaseName(unsigned int, std::string &) +// Purpose: Makes the base name for a inode map +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const +{ + // Get the directory for the maps + const Configuration &config(GetConfiguration()); + std::string dir(config.GetKeyValue("DataDirectory")); + + // Make a leafname + std::string leaf(mIDMapMounts[MountNumber]); + for(unsigned int z = 0; z < leaf.size(); ++z) + { + if(leaf[z] == DIRECTORY_SEPARATOR_ASCHAR) + { + leaf[z] = '_'; + } + } + + // Build the final filename + rNameOut = dir + DIRECTORY_SEPARATOR "mnt" + leaf; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CommitIDMapsAfterSync() +// Purpose: Commits the new ID maps, so the 'new' maps are now the 'current' maps. +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::CommitIDMapsAfterSync() +{ + // Get rid of the maps in memory (leaving them on disc of course) + DeleteIDMapVector(mCurrentIDMaps); + DeleteIDMapVector(mNewIDMaps); + + // Then move the old maps into the new places + for(unsigned int l = 0; l < mIDMapMounts.size(); ++l) + { + std::string target; + MakeMapBaseName(l, target); + std::string newmap(target + ".n"); + + // Try to rename +#ifdef WIN32 + // win32 rename doesn't overwrite existing files + ::remove(target.c_str()); +#endif + if(::rename(newmap.c_str(), target.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to rename ID map: " << + newmap << " to " << target); + THROW_EXCEPTION(CommonException, OSFileError) + } + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &) +// Purpose: Deletes the contents of a vector of ID maps +// Created: 11/11/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector) +{ + while(!rVector.empty()) + { + // Pop off list + BackupClientInodeToIDMap *toDel = rVector.back(); + rVector.pop_back(); + + // Close and delete + delete toDel; + } + ASSERT(rVector.size() == 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::FindLocationPathName(const std::string &, std::string &) const +// Purpose: Tries to find the path of the root of a backup location. Returns true (and path in rPathOut) +// if it can be found, false otherwise. +// Created: 12/11/03 +// +// -------------------------------------------------------------------------- +bool BackupDaemon::FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const +{ + // Search for the location + for(Locations::const_iterator i(mLocations.begin()); i != mLocations.end(); ++i) + { + if((*i)->mName == rLocationName) + { + rPathOut = (*i)->mPath; + return true; + } + } + + // Didn't find it + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SetState(int) +// Purpose: Record current action of daemon, and update process title to reflect this +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupDaemon::SetState(int State) +{ + // Two little checks + if(State == mState) return; + if(State < 0) return; + + // Update + mState = State; + + // Set process title + const static char *stateText[] = {"idle", "connected", "error -- waiting for retry", "over limit on server -- not backing up"}; + SetProcessTitle(stateText[State]); + + // If there's a command socket connected, then inform it -- disconnecting from the + // command socket if there's an error + + std::ostringstream msg; + msg << "state " << State << "\n"; + + if(!mapCommandSocketInfo.get()) + { + return; + } + + if(mapCommandSocketInfo->mpConnectedSocket.get() == 0) + { + return; + } + + // Something connected to the command socket, tell it about the new state + try + { + mapCommandSocketInfo->mpConnectedSocket->Write(msg.str(), + 1); // very short timeout, it's overlapped anyway + } + catch(ConnectionException &ce) + { + BOX_NOTICE("Failed to write state to command socket: " << + ce.what()); + CloseCommandConnection(); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write state to command socket: " << + e.what()); + CloseCommandConnection(); + } + catch(...) + { + BOX_ERROR("Failed to write state to command socket: " + "unknown error"); + CloseCommandConnection(); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::TouchFileInWorkingDir(const char *) +// Purpose: Make sure a zero length file of the name exists in the working directory. +// Use for marking times of events in the filesystem. +// Created: 21/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::TouchFileInWorkingDir(const char *Filename) +{ + // Filename + const Configuration &config(GetConfiguration()); + std::string fn(config.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); + fn += Filename; + + // Open and close it to update the timestamp + 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()); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::NotifySysadmin(int) +// Purpose: Run the script to tell the sysadmin about events +// which need attention. +// Created: 25/2/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::NotifySysadmin(SysadminNotifier::EventCode Event) +{ + static const char *sEventNames[] = + { + "store-full", + "read-error", + "backup-error", + "backup-start", + "backup-finish", + "backup-ok", + 0 + }; + + // BOX_TRACE("sizeof(sEventNames) == " << sizeof(sEventNames)); + // BOX_TRACE("sizeof(*sEventNames) == " << sizeof(*sEventNames)); + // BOX_TRACE("NotifyEvent__MAX == " << NotifyEvent__MAX); + ASSERT((sizeof(sEventNames)/sizeof(*sEventNames)) == SysadminNotifier::MAX + 1); + + if(Event < 0 || Event >= SysadminNotifier::MAX) + { + THROW_EXCEPTION_MESSAGE(BackupStoreException, + BadNotifySysadminEventCode, "NotifySysadmin() called " + "for unknown event code " << Event); + } + + BOX_TRACE("BackupDaemon::NotifySysadmin() called, event = " << + sEventNames[Event]); + + if(!GetConfiguration().KeyExists("NotifyAlways") || + !GetConfiguration().GetKeyValueBool("NotifyAlways")) + { + // Don't send lots of repeated messages + // Note: backup-start and backup-finish will always be + // logged, because mLastNotifiedEvent is never set to + // these values and therefore they are never "duplicates". + if(mLastNotifiedEvent == Event) + { + if(Event == SysadminNotifier::BackupOK) + { + BOX_INFO("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + else + { + BOX_WARNING("Suppressing duplicate notification " + "about " << sEventNames[Event]); + } + return; + } + } + + // Is there a notification script? + const Configuration &conf(GetConfiguration()); + if(!conf.KeyExists("NotifyScript")) + { + // Log, and then return + if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) + { + BOX_INFO("Not notifying administrator about event " + << sEventNames[Event] << ", set NotifyScript " + "to do this in future"); + } + return; + } + + // Script to run + std::string script(conf.GetKeyValue("NotifyScript") + " " + + sEventNames[Event] + " \"" + GetConfigFileName() + "\""); + + // Log what we're about to do + BOX_INFO("About to notify administrator about event " + << sEventNames[Event] << ", running script '" << script << "'"); + + // Then do it + int returnCode = ::system(script.c_str()); + if(returnCode != 0) + { + BOX_WARNING("Notify script returned error code: " << + returnCode << " (" << script << ")"); + } + else if(Event != SysadminNotifier::BackupStart && + Event != SysadminNotifier::BackupFinish) + { + mLastNotifiedEvent = Event; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &) +// Purpose: Deletes any unused entries in the root directory, if they're scheduled to be deleted. +// Created: 13/5/04 +// +// -------------------------------------------------------------------------- +void BackupDaemon::DeleteUnusedRootDirEntries(BackupClientContext &rContext) +{ + if(mUnusedRootDirEntries.empty()) + { + BOX_INFO("Not deleting unused entries - none in list"); + return; + } + + if(mDeleteUnusedRootDirEntriesAfter == 0) + { + BOX_INFO("Not deleting unused entries - " + "zero delete time (bad)"); + return; + } + + // Check time + box_time_t now = GetCurrentBoxTime(); + if(now < mDeleteUnusedRootDirEntriesAfter) + { + int secs = BoxTimeToSeconds(mDeleteUnusedRootDirEntriesAfter + - now); + BOX_INFO("Not deleting unused entries - too early (" + << secs << " seconds remaining)"); + return; + } + + // Entries to delete, and it's the right time to do so... + BOX_NOTICE("Deleting unused locations from store root..."); + BackupProtocolCallable &connection(rContext.GetConnection()); + for(std::vector<std::pair<int64_t,std::string> >::iterator + i(mUnusedRootDirEntries.begin()); + i != mUnusedRootDirEntries.end(); ++i) + { + connection.QueryDeleteDirectory(i->first); + rContext.GetProgressNotifier().NotifyFileDeleted( + i->first, i->second); + } + + // Reset state + mDeleteUnusedRootDirEntriesAfter = 0; + mUnusedRootDirEntries.clear(); +} + +// -------------------------------------------------------------------------- + +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 + +} loc_StreamFormat; + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CommandSocketInfo::CommandSocketInfo() +// Purpose: Constructor +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +BackupDaemon::CommandSocketInfo::CommandSocketInfo() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::CommandSocketInfo::~CommandSocketInfo() +// Purpose: Destructor +// Created: 18/2/04 +// +// -------------------------------------------------------------------------- +BackupDaemon::CommandSocketInfo::~CommandSocketInfo() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::SerializeStoreObjectInfo( +// box_time_t theLastSyncTime, +// box_time_t theNextSyncTime) +// Purpose: Serializes remote directory and file information +// into a stream of bytes, using an Archive +// abstraction. +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- + +static const int STOREOBJECTINFO_MAGIC_ID_VALUE = 0x7777525F; +static const std::string STOREOBJECTINFO_MAGIC_ID_STRING = "BBACKUPD-STATE"; +static const int STOREOBJECTINFO_VERSION = 2; + +bool BackupDaemon::SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const +{ + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + return false; + } + + std::string StoreObjectInfoFile = + GetConfiguration().GetKeyValue("StoreObjectInfoFile"); + + if(StoreObjectInfoFile.size() <= 0) + { + return false; + } + + bool created = false; + + try + { + FileStream aFile(StoreObjectInfoFile.c_str(), + O_WRONLY | O_CREAT | O_TRUNC); + created = true; + + Archive anArchive(aFile, 0); + + anArchive.Write(STOREOBJECTINFO_MAGIC_ID_VALUE); + anArchive.Write(STOREOBJECTINFO_MAGIC_ID_STRING); + anArchive.Write(STOREOBJECTINFO_VERSION); + anArchive.Write(GetLoadedConfigModifiedTime()); + anArchive.Write(mClientStoreMarker); + anArchive.Write(theLastSyncTime); + anArchive.Write(theNextSyncTime); + + // + // + // + int64_t iCount = mLocations.size(); + anArchive.Write(iCount); + + for(Locations::const_iterator i = mLocations.begin(); + i != mLocations.end(); i++) + { + ASSERT(*i); + (*i)->Serialize(anArchive); + } + + // + // + // + iCount = mIDMapMounts.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + anArchive.Write(mIDMapMounts[v]); + + // + // + // + iCount = mUnusedRootDirEntries.size(); + anArchive.Write(iCount); + + for(int v = 0; v < iCount; v++) + { + anArchive.Write(mUnusedRootDirEntries[v].first); + anArchive.Write(mUnusedRootDirEntries[v].second); + } + + if (iCount > 0) + { + anArchive.Write(mDeleteUnusedRootDirEntriesAfter); + } + + // + // + // + aFile.Close(); + BOX_INFO("Saved store object info file version " << + STOREOBJECTINFO_VERSION << " (" << + StoreObjectInfoFile << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": " << e.what()); + } + catch(...) + { + BOX_ERROR("Failed to write StoreObjectInfoFile: " << + StoreObjectInfoFile << ": unknown error"); + } + + return created; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeserializeStoreObjectInfo( +// box_time_t & theLastSyncTime, +// box_time_t & theNextSyncTime) +// Purpose: Deserializes remote directory and file information +// from a stream of bytes, using an Archive +// abstraction. +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +bool BackupDaemon::DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime) +{ + // + // + // + DeleteAllLocations(); + + // + // + // + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + BOX_NOTICE("Store object info file is not enabled. Will " + "download directory listings from store."); + return false; + } + + std::string StoreObjectInfoFile = + GetConfiguration().GetKeyValue("StoreObjectInfoFile"); + + if(StoreObjectInfoFile.size() <= 0) + { + return false; + } + + int64_t fileSize; + if (!FileExists(StoreObjectInfoFile, &fileSize) || fileSize == 0) + { + BOX_NOTICE(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Store object info file does not exist or is empty")); + } + else + { + try + { + FileStream aFile(StoreObjectInfoFile, O_RDONLY); + Archive anArchive(aFile, 0); + + // + // see if the content looks like a valid serialised archive + // + int iMagicValue = 0; + anArchive.Read(iMagicValue); + + if(iMagicValue != STOREOBJECTINFO_MAGIC_ID_VALUE) + { + BOX_WARNING(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Store object info file is not a valid " + "or compatible serialised archive")); + return false; + } + + // + // get a bit optimistic and read in a string identifier + // + std::string strMagicValue; + anArchive.Read(strMagicValue); + + if(strMagicValue != STOREOBJECTINFO_MAGIC_ID_STRING) + { + BOX_WARNING(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Store object info file is not a valid " + "or compatible serialised archive")); + return false; + } + + // + // check if we are loading some future format + // version by mistake + // + int iVersion = 0; + anArchive.Read(iVersion); + + if(iVersion != STOREOBJECTINFO_VERSION) + { + BOX_WARNING(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Store object info file version " << + iVersion << " is not supported")); + return false; + } + + // + // check if this state file is even valid + // for the loaded bbackupd.conf file + // + box_time_t lastKnownConfigModTime; + anArchive.Read(lastKnownConfigModTime); + + if(lastKnownConfigModTime != GetLoadedConfigModifiedTime()) + { + BOX_WARNING(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Store object info file is older than " + "configuration file")); + return false; + } + + // + // this is it, go at it + // + anArchive.Read(mClientStoreMarker); + anArchive.Read(theLastSyncTime); + anArchive.Read(theNextSyncTime); + + // + // + // + int64_t iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + Location* pLocation = new Location; + if(!pLocation) + { + throw std::bad_alloc(); + } + + pLocation->Deserialize(anArchive); + mLocations.push_back(pLocation); + } + + // + // + // + iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + std::string strItem; + anArchive.Read(strItem); + + mIDMapMounts.push_back(strItem); + } + + // + // + // + iCount = 0; + anArchive.Read(iCount); + + for(int v = 0; v < iCount; v++) + { + int64_t anId; + anArchive.Read(anId); + + std::string aName; + anArchive.Read(aName); + + mUnusedRootDirEntries.push_back(std::pair<int64_t, std::string>(anId, aName)); + } + + if (iCount > 0) + anArchive.Read(mDeleteUnusedRootDirEntriesAfter); + + // + // + // + aFile.Close(); + + BOX_INFO(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Loaded store object info file version " << iVersion)); + return true; + } + catch(std::exception &e) + { + BOX_ERROR(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Internal error reading store object info " + "file: " << e.what())); + } + catch(...) + { + BOX_ERROR(BOX_FILE_MESSAGE(StoreObjectInfoFile, + "Internal error reading store object info " + "file: unknown error")); + } + } + + BOX_NOTICE("No usable cache, will download directory listings from " + "server."); + + DeleteAllLocations(); + + mClientStoreMarker = BackupClientContext::ClientStoreMarker_NotKnown; + theLastSyncTime = 0; + theNextSyncTime = 0; + + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupDaemon::DeleteStoreObjectInfo() +// Purpose: Deletes the serialised state file, to prevent us +// from using it again if a backup is interrupted. +// +// Created: 2006/02/12 +// +// -------------------------------------------------------------------------- + +bool BackupDaemon::DeleteStoreObjectInfo() const +{ + if(!GetConfiguration().KeyExists("StoreObjectInfoFile")) + { + return false; + } + + std::string storeObjectInfoFile(GetConfiguration().GetKeyValue("StoreObjectInfoFile")); + + // Check to see if the file exists + if(!FileExists(storeObjectInfoFile.c_str())) + { + // File doesn't exist -- so can't be deleted. But something + // isn't quite right, so log a message + BOX_WARNING("StoreObjectInfoFile did not exist when it " + "was supposed to: " << storeObjectInfoFile); + + // Return true to stop things going around in a loop + return true; + } + + // Actually delete it + if(::unlink(storeObjectInfoFile.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete the old " + "StoreObjectInfoFile: " << storeObjectInfoFile); + return false; + } + + return true; +} diff --git a/lib/bbackupd/BackupDaemon.h b/lib/bbackupd/BackupDaemon.h new file mode 100644 index 00000000..f9b8ba31 --- /dev/null +++ b/lib/bbackupd/BackupDaemon.h @@ -0,0 +1,539 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemon.h +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMON__H +#define BACKUPDAEMON__H + +#include <vector> +#include <string> +#include <memory> + +#include "BackupClientContext.h" +#include "BackupClientDirectoryRecord.h" +#include "BoxTime.h" +#include "Daemon.h" +#include "Logging.h" +#include "Socket.h" +#include "SocketListen.h" +#include "SocketStream.h" +#include "TLSContext.h" + +#include "autogen_BackupProtocol.h" +#include "autogen_BackupStoreException.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 + +#define COMMAND_SOCKET_POLL_INTERVAL 1000 + +class BackupClientDirectoryRecord; +class BackupClientContext; +class Configuration; +class BackupClientInodeToIDMap; +class ExcludeList; +class IOStreamGetLine; +class Archive; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupDaemon +// Purpose: Backup daemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class BackupDaemon : public Daemon, public ProgressNotifier, public LocationResolver, +public RunStatusProvider, public SysadminNotifier, public BackgroundTask +{ +public: + BackupDaemon(); + ~BackupDaemon(); + +private: + // methods below do partial (specialized) serialization of + // client state only + bool SerializeStoreObjectInfo(box_time_t theLastSyncTime, + box_time_t theNextSyncTime) const; + bool DeserializeStoreObjectInfo(box_time_t & theLastSyncTime, + box_time_t & theNextSyncTime); + bool DeleteStoreObjectInfo() const; + BackupDaemon(const BackupDaemon &); + +public: + #ifdef WIN32 + // add command-line options to handle Windows services + std::string GetOptionString(); + int ProcessOption(signed int option); + int Main(const std::string &rConfigFileName); + + // This shouldn't be here, but apparently gcc on + // Windows has no idea about inherited methods... + virtual int Main(const char *DefaultConfigFile, int argc, + const char *argv[]) + { + return Daemon::Main(DefaultConfigFile, argc, argv); + } + #endif + + void Run(); + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + virtual void Usage(); + const ConfigurationVerify *GetConfigVerify() const; + + bool FindLocationPathName(const std::string &rLocationName, std::string &rPathOut) const; + + enum + { + // Add stuff to this, make sure the textual equivalents in SetState() are changed too. + State_Initialising = -1, + State_Idle = 0, + State_Connected = 1, + State_Error = 2, + State_StorageLimitExceeded = 3 + }; + + int GetState() {return mState;} + static std::string GetStateName(int state) + { + std::string stateName; + + #define STATE(x) case BackupDaemon::State_ ## x: stateName = #x; break; + switch (state) + { + STATE(Initialising); + STATE(Idle); + STATE(Connected); + STATE(Error); + STATE(StorageLimitExceeded); + default: + stateName = "unknown"; + } + #undef STATE + + return stateName; + } + + // Allow other classes to call this too + void NotifySysadmin(SysadminNotifier::EventCode Event); + +private: + void Run2(); + +public: + void InitCrypto(); + std::auto_ptr<BackupClientContext> RunSyncNowWithExceptionHandling(); + std::auto_ptr<BackupClientContext> RunSyncNow(); + void ResetCachedState(); + void OnBackupStart(); + void OnBackupFinish(); + // TouchFileInWorkingDir is only here for use by Boxi. + // This does NOT constitute an API! + void TouchFileInWorkingDir(const char *Filename); + +protected: + virtual std::auto_ptr<BackupClientContext> GetNewContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode + ); + +private: + void DeleteAllLocations(); + void SetupLocations(BackupClientContext &rClientContext, const Configuration &rLocationsConf); + + void DeleteIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector); + void DeleteAllIDMaps() + { + DeleteIDMapVector(mCurrentIDMaps); + DeleteIDMapVector(mNewIDMaps); + } + void FillIDMapVector(std::vector<BackupClientInodeToIDMap *> &rVector, bool NewMaps); + + void SetupIDMapsForSync(); + void CommitIDMapsAfterSync(); + void DeleteCorruptBerkelyDbFiles(); + + void MakeMapBaseName(unsigned int MountNumber, std::string &rNameOut) const; + + void SetState(int State); + + void WaitOnCommandSocket(box_time_t RequiredDelay, bool &DoSyncFlagOut, bool &SyncIsForcedOut); + void CloseCommandConnection(); + void SendSyncStartOrFinish(bool SendStart); + + void DeleteUnusedRootDirEntries(BackupClientContext &rContext); + + // For warning user about potential security hole + virtual void SetupInInitialProcess(); + + int UseScriptToSeeIfSyncAllowed(); + +public: + 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 + + Locations mLocations; + + std::vector<std::string> mIDMapMounts; + std::vector<BackupClientInodeToIDMap *> mCurrentIDMaps; + std::vector<BackupClientInodeToIDMap *> mNewIDMaps; + + int mDeleteRedundantLocationsAfter; + + // For the command socket + class CommandSocketInfo + { + public: + CommandSocketInfo(); + ~CommandSocketInfo(); + private: + CommandSocketInfo(const CommandSocketInfo &); // no copying + CommandSocketInfo &operator=(const CommandSocketInfo &); + public: +#ifdef WIN32 + WinNamedPipeListener<1 /* listen backlog */> mListeningSocket; + std::auto_ptr<WinNamedPipeStream> mpConnectedSocket; +#else + SocketListen<SocketStream, 1 /* listen backlog */> mListeningSocket; + std::auto_ptr<SocketStream> mpConnectedSocket; +#endif + std::auto_ptr<IOStreamGetLine> mapGetLine; + }; + + // Using a socket? + std::auto_ptr<CommandSocketInfo> mapCommandSocketInfo; + + // Stop notifications being repeated. + SysadminNotifier::EventCode mLastNotifiedEvent; + + // Unused entries in the root directory wait a while before being deleted + box_time_t mDeleteUnusedRootDirEntriesAfter; // time to delete them + std::vector<std::pair<int64_t,std::string> > mUnusedRootDirEntries; + + int64_t mClientStoreMarker; + bool mStorageLimitExceeded; + bool mReadErrorsOnFilesystemObjects; + box_time_t mLastSyncTime, mNextSyncTime; + box_time_t mCurrentSyncStartTime, mUpdateStoreInterval, + mBackupErrorDelay; + 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; } + +private: + bool mLogAllFileAccess; + +public: + ProgressNotifier* GetProgressNotifier() { return mpProgressNotifier; } + LocationResolver* GetLocationResolver() { return mpLocationResolver; } + RunStatusProvider* GetRunStatusProvider() { return mpRunStatusProvider; } + SysadminNotifier* GetSysadminNotifier() { return mpSysadminNotifier; } + void SetProgressNotifier (ProgressNotifier* p) { mpProgressNotifier = p; } + void SetLocationResolver (LocationResolver* p) { mpLocationResolver = p; } + void SetRunStatusProvider(RunStatusProvider* p) { mpRunStatusProvider = p; } + void SetSysadminNotifier (SysadminNotifier* p) { mpSysadminNotifier = p; } + virtual bool RunBackgroundTask(State state, uint64_t progress, + uint64_t maximum); + +private: + ProgressNotifier* mpProgressNotifier; + LocationResolver* mpLocationResolver; + RunStatusProvider* mpRunStatusProvider; + SysadminNotifier* mpSysadminNotifier; + std::auto_ptr<Timer> mapCommandSocketPollTimer; + std::auto_ptr<BackupClientContext> mapClientContext; + + /* ProgressNotifier implementation */ +public: + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) { } + + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Scanning directory: " << rLocalPath); + } + + if (!RunBackgroundTask(BackgroundTask::Scanning_Dirs, 0, 0)) + { + THROW_EXCEPTION(BackupStoreException, + CancelledByBackgroundTask); + } + } + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to access file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Failed to list directory: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + #ifdef WIN32 + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is an NTFS junction/reparse point; create " + "a new location if you want to back it up"); + #else + BOX_WARNING("Ignored directory: " << rLocalPath << + ": is a mount point; create a new location " + "if you want to back it up"); + #endif + } + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded file: " << rLocalPath); + } + } + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_INFO("Skipping excluded directory: " << rLocalPath); + } + } + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Ignoring file of unknown type: " << rLocalPath); + } + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) + { + BOX_WARNING("Error reading file: " << rLocalPath + << ": " << rErrorMsg); + } + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Some files have modification times excessively " + "in the future. Check clock synchronisation. " + "Example file (only one shown): " << rLocalPath); + } + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + BOX_WARNING("Skipped file: server is full: " << rLocalPath); + } + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) + { + if (rException.GetType() == CommonException::ExceptionType && + rException.GetSubType() == CommonException::AccessDenied) + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": Access denied"); + } + else + { + BOX_ERROR("Failed to upload file: " << rLocalPath + << ": caught exception: " << rException.what() + << " (" << rException.GetType() + << "/" << rException.GetSubType() << ")"); + } + } + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) + { + BOX_ERROR("Failed to upload file: " << rLocalPath << + ": server error: " << + BackupProtocolError::GetMessage(subtype)); + } + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading complete file: " << rLocalPath); + } + } + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t EstimatedBytesToUpload) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading patch to file: " << rLocalPath << + ", estimated upload size = " << + EstimatedBytesToUpload); + } + } + virtual void NotifyFileUploadingAttributes( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploading new file attributes: " << + rLocalPath); + } + } + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize, int64_t UploadedSize, int64_t ObjectID) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Uploaded file: " << rLocalPath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + "): total size = " << FileSize << ", " + "uploaded size = " << UploadedSize); + } + mNumFilesUploaded++; + } + virtual void NotifyFileSynchronised( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize) + { + if (mLogAllFileAccess) + { + 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) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted directory: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) + { + if (mLogAllFileAccess) + { + BOX_NOTICE("Deleted file: " << rRemotePath << + " (ID " << BOX_FORMAT_OBJECTID(ObjectID) << + ")"); + } + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain, eta " << + BoxTimeToSeconds(finish - elapsed) << "s"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", " << (length - offset) << " remain"); + } + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) + { + BOX_TRACE("Read " << readSize << " bytes at " << offset << + ", unknown bytes remaining"); + } + +#ifdef WIN32 + private: + 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/lib/bbackupd/BackupDaemonInterface.h b/lib/bbackupd/BackupDaemonInterface.h new file mode 100644 index 00000000..d5c47c85 --- /dev/null +++ b/lib/bbackupd/BackupDaemonInterface.h @@ -0,0 +1,166 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupDaemonInterface.h +// Purpose: Interfaces for managing a BackupDaemon +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPDAEMONINTERFACE__H +#define BACKUPDAEMONINTERFACE__H + +#include <string> + +#include "BoxTime.h" + +class Archive; +class BackupClientContext; +class BackupDaemon; + +// -------------------------------------------------------------------------- +// +// Class +// Name: SysadminNotifier +// Purpose: Provides a NotifySysadmin() method to send mail to the sysadmin +// Created: 2005/11/15 +// +// -------------------------------------------------------------------------- +class SysadminNotifier +{ + public: + virtual ~SysadminNotifier() { } + + typedef enum + { + StoreFull = 0, + ReadError, + BackupError, + BackupStart, + BackupFinish, + BackupOK, + MAX + // When adding notifications, remember to add + // strings to NotifySysadmin() + } + EventCode; + + virtual void NotifySysadmin(EventCode Event) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProgressNotifier +// Purpose: Provides methods for the backup library to inform the user +// interface about its progress with the backup +// Created: 2005/11/20 +// +// -------------------------------------------------------------------------- + +class BackupClientContext; +class BackupClientDirectoryRecord; + +class ProgressNotifier +{ + public: + virtual ~ProgressNotifier() { } + virtual void NotifyIDMapsSetup(BackupClientContext& rContext) = 0; + virtual void NotifyScanDirectory( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileStatFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyDirListFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyMountPointSkipped( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyDirExcluded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyUnsupportedFileType( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileReadFailed( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const std::string& rErrorMsg) = 0; + virtual void NotifyFileModifiedInFuture( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileSkippedServerFull( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadException( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + const BoxException& rException) = 0; + virtual void NotifyFileUploadServerError( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int type, int subtype) = 0; + virtual void NotifyFileUploading( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploadingPatch( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t EstimatedBytesToUpload) = 0; + virtual void NotifyFileUploadingAttributes( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath) = 0; + virtual void NotifyFileUploaded( + const BackupClientDirectoryRecord* pDirRecord, + const std::string& rLocalPath, + int64_t FileSize, int64_t UploadedSize, int64_t ObjectID) = 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; + virtual void NotifyFileDeleted( + int64_t ObjectID, + const std::string& rRemotePath) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, box_time_t finish) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset, + int64_t length) = 0; + virtual void NotifyReadProgress(int64_t readSize, int64_t offset) = 0; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: LocationResolver +// Purpose: Interface for classes that can resolve locations to paths, +// like BackupDaemon +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +class LocationResolver +{ +public: + virtual ~LocationResolver() { } + virtual bool FindLocationPathName(const std::string &rLocationName, + std::string &rPathOut) const = 0; +}; + +#endif // BACKUPDAEMONINTERFACE__H diff --git a/lib/bbackupd/Win32BackupService.cpp b/lib/bbackupd/Win32BackupService.cpp new file mode 100644 index 00000000..6d027abf --- /dev/null +++ b/lib/bbackupd/Win32BackupService.cpp @@ -0,0 +1,48 @@ +// Win32 service functions for Box Backup, by Nick Knight + +#ifdef WIN32 + +#include "Box.h" +#include "BackupDaemon.h" +#include "MainHelper.h" +#include "BoxPortsAndFiles.h" +#include "BackupStoreException.h" + +#include "MemLeakFindOn.h" + +#include "Win32BackupService.h" + +Win32BackupService* gpDaemonService = NULL; +extern HANDLE gStopServiceEvent; +extern DWORD gServiceReturnCode; + +unsigned int WINAPI RunService(LPVOID lpParameter) +{ + DWORD retVal = gpDaemonService->WinService((const char*) lpParameter); + gServiceReturnCode = retVal; + SetEvent(gStopServiceEvent); + return retVal; +} + +void TerminateService(void) +{ + gpDaemonService->SetTerminateWanted(); +} + +DWORD Win32BackupService::WinService(const char* pConfigFileName) +{ + DWORD ret; + + if (pConfigFileName != NULL) + { + ret = this->Main(pConfigFileName); + } + else + { + ret = this->Main(BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE); + } + + return ret; +} + +#endif // WIN32 diff --git a/lib/bbackupd/Win32BackupService.h b/lib/bbackupd/Win32BackupService.h new file mode 100644 index 00000000..e7f077f2 --- /dev/null +++ b/lib/bbackupd/Win32BackupService.h @@ -0,0 +1,21 @@ +// Box Backup service daemon implementation by Nick Knight + +#ifndef WIN32BACKUPSERVICE_H +#define WIN32BACKUPSERVICE_H + +#ifdef WIN32 + +class Configuration; +class ConfigurationVerify; +class BackupDaemon; + +class Win32BackupService : public BackupDaemon +{ +public: + DWORD WinService(const char* pConfigFileName); +}; + +#endif // WIN32 + +#endif // WIN32BACKUPSERVICE_H + diff --git a/lib/bbackupd/Win32ServiceFunctions.cpp b/lib/bbackupd/Win32ServiceFunctions.cpp new file mode 100644 index 00000000..2df914a7 --- /dev/null +++ b/lib/bbackupd/Win32ServiceFunctions.cpp @@ -0,0 +1,384 @@ +//*************************************************************** +// From the book "Win32 System Services: The Heart of Windows 98 +// and Windows 2000" +// by Marshall Brain +// Published by Prentice Hall +// Copyright 1995 Prentice Hall. +// +// This code implements the Windows API Service interface +// for the Box Backup for Windows native port. +// Adapted for Box Backup by Nick Knight. +//*************************************************************** + +#ifdef WIN32 + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif +#ifdef HAVE_PROCESS_H + #include <process.h> +#endif + +extern void TerminateService(void); +extern unsigned int WINAPI RunService(LPVOID lpParameter); + +// Global variables + +TCHAR* gServiceName = TEXT("Box Backup Service"); +SERVICE_STATUS gServiceStatus; +SERVICE_STATUS_HANDLE gServiceStatusHandle = 0; +HANDLE gStopServiceEvent = 0; +DWORD gServiceReturnCode = 0; + +#define SERVICE_NAME "boxbackup" + +void ShowMessage(char *s) +{ + MessageBox(0, s, "Box Backup Message", + MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); +} + +void ErrorHandler(char *s, DWORD err) +{ + char buf[256]; + memset(buf, 0, sizeof(buf)); + _snprintf(buf, sizeof(buf)-1, "%s: %s", s, + GetErrorMessage(err).c_str()); + BOX_ERROR(buf); + MessageBox(0, buf, "Error", + MB_OK | MB_SETFOREGROUND | MB_DEFAULT_DESKTOP_ONLY); + ExitProcess(err); +} + +void WINAPI ServiceControlHandler( DWORD controlCode ) +{ + switch ( controlCode ) + { + case SERVICE_CONTROL_INTERROGATE: + break; + + case SERVICE_CONTROL_SHUTDOWN: + case SERVICE_CONTROL_STOP: + Beep(1000,100); + TerminateService(); + gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + SetEvent(gStopServiceEvent); + return; + + case SERVICE_CONTROL_PAUSE: + break; + + case SERVICE_CONTROL_CONTINUE: + break; + + default: + if ( controlCode >= 128 && controlCode <= 255 ) + // user defined control code + break; + else + // unrecognised control code + break; + } + + SetServiceStatus( gServiceStatusHandle, &gServiceStatus ); +} + +// ServiceMain is called when the SCM wants to +// start the service. When it returns, the service +// has stopped. It therefore waits on an event +// just before the end of the function, and +// that event gets set when it is time to stop. +// It also returns on any error because the +// service cannot start if there is an eror. + +static char* spConfigFileName; + +VOID ServiceMain(DWORD argc, LPTSTR *argv) +{ + // initialise service status + gServiceStatus.dwServiceType = SERVICE_WIN32; + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + gServiceStatus.dwControlsAccepted = 0; + gServiceStatus.dwWin32ExitCode = NO_ERROR; + gServiceStatus.dwServiceSpecificExitCode = NO_ERROR; + gServiceStatus.dwCheckPoint = 0; + gServiceStatus.dwWaitHint = 0; + + gServiceStatusHandle = RegisterServiceCtrlHandler(gServiceName, + ServiceControlHandler); + + if (gServiceStatusHandle) + { + // service is starting + gServiceStatus.dwCurrentState = SERVICE_START_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // do initialisation here + gStopServiceEvent = CreateEvent(0, TRUE, FALSE, 0); + if (!gStopServiceEvent) + { + gServiceStatus.dwControlsAccepted &= + ~(SERVICE_ACCEPT_STOP | + SERVICE_ACCEPT_SHUTDOWN); + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + return; + } + + HANDLE ourThread = (HANDLE)_beginthreadex( + NULL, + 0, + RunService, + spConfigFileName, + CREATE_SUSPENDED, + NULL); + + SetThreadPriority(ourThread, THREAD_PRIORITY_LOWEST); + ResumeThread(ourThread); + + // we are now running so tell the SCM + gServiceStatus.dwControlsAccepted |= + (SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + gServiceStatus.dwCurrentState = SERVICE_RUNNING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // do cleanup here + WaitForSingleObject(gStopServiceEvent, INFINITE); + CloseHandle(gStopServiceEvent); + gStopServiceEvent = 0; + + // service was stopped + gServiceStatus.dwCurrentState = SERVICE_STOP_PENDING; + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + + // service is now stopped + gServiceStatus.dwControlsAccepted &= + ~(SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN); + + gServiceStatus.dwCurrentState = SERVICE_STOPPED; + + if (gServiceReturnCode != 0) + { + gServiceStatus.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR; + gServiceStatus.dwServiceSpecificExitCode = gServiceReturnCode; + } + + SetServiceStatus(gServiceStatusHandle, &gServiceStatus); + } +} + +int OurService(const char* pConfigFileName) +{ + spConfigFileName = strdup(pConfigFileName); + + SERVICE_TABLE_ENTRY serviceTable[] = + { + { SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION) ServiceMain }, + { NULL, NULL } + }; + BOOL success; + + // Register with the SCM + success = StartServiceCtrlDispatcher(serviceTable); + + free(spConfigFileName); + spConfigFileName = NULL; + + if (!success) + { + ErrorHandler("Failed to start service. Did you start " + "Box Backup from the Service Control Manager? " + "(StartServiceCtrlDispatcher)", GetLastError()); + return 1; + } + + return 0; +} + +int InstallService(const char* pConfigFileName, const std::string& rServiceName) +{ + if (pConfigFileName != NULL) + { + EMU_STRUCT_STAT st; + + if (emu_stat(pConfigFileName, &st) != 0) + { + BOX_LOG_SYS_ERROR("Failed to open configuration file " + "'" << pConfigFileName << "'"); + return 1; + } + + if (!(st.st_mode & S_IFREG)) + { + + BOX_ERROR("Failed to open configuration file '" << + pConfigFileName << "': not a file"); + return 1; + } + } + + SC_HANDLE scm = OpenSCManager(0, 0, SC_MANAGER_CREATE_SERVICE); + + if (!scm) + { + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); + return 1; + } + + char cmd[MAX_PATH]; + GetModuleFileName(NULL, cmd, sizeof(cmd)-1); + cmd[sizeof(cmd)-1] = 0; + + std::string cmdWithArgs(cmd); + cmdWithArgs += " -s -S \"" + rServiceName + "\""; + + if (pConfigFileName != NULL) + { + cmdWithArgs += " \""; + cmdWithArgs += pConfigFileName; + cmdWithArgs += "\""; + } + + std::string serviceDesc = "Box Backup (" + rServiceName + ")"; + + SC_HANDLE newService = CreateService( + scm, + rServiceName.c_str(), + serviceDesc.c_str(), + SERVICE_ALL_ACCESS, + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + cmdWithArgs.c_str(), + 0,0,0,0,0); + + DWORD err = GetLastError(); + CloseServiceHandle(scm); + + if (!newService) + { + switch (err) + { + case ERROR_SERVICE_EXISTS: + { + BOX_ERROR("Failed to create Box Backup " + "service: it already exists"); + } + break; + + case ERROR_SERVICE_MARKED_FOR_DELETE: + { + BOX_ERROR("Failed to create Box Backup " + "service: it is waiting to be deleted"); + } + break; + + case ERROR_DUPLICATE_SERVICE_NAME: + { + BOX_ERROR("Failed to create Box Backup " + "service: a service with this name " + "already exists"); + } + break; + + default: + { + BOX_ERROR("Failed to create Box Backup " + "service: error " << + GetErrorMessage(GetLastError())); + } + } + + return 1; + } + + BOX_INFO("Created Box Backup service"); + + SERVICE_DESCRIPTION desc; + desc.lpDescription = "Backs up your data files over the Internet"; + + if (!ChangeServiceConfig2(newService, SERVICE_CONFIG_DESCRIPTION, + &desc)) + { + BOX_WARNING("Failed to set description for Box Backup " + "service: " << GetErrorMessage(GetLastError())); + } + + CloseServiceHandle(newService); + + return 0; +} + +int RemoveService(const std::string& rServiceName) +{ + SC_HANDLE scm = OpenSCManager(0,0,SC_MANAGER_CREATE_SERVICE); + + if (!scm) + { + BOX_ERROR("Failed to open service control manager: " << + GetErrorMessage(GetLastError())); + return 1; + } + + SC_HANDLE service = OpenService(scm, rServiceName.c_str(), + SERVICE_ALL_ACCESS|DELETE); + DWORD err = GetLastError(); + CloseServiceHandle(scm); + + if (!service) + { + if (err == ERROR_SERVICE_DOES_NOT_EXIST || + err == ERROR_IO_PENDING) + // hello microsoft? anyone home? + { + BOX_ERROR("Failed to open Box Backup service: " + "not installed or not found"); + } + else + { + BOX_ERROR("Failed to open Box Backup service: " << + GetErrorMessage(err)); + } + return 1; + } + + SERVICE_STATUS status; + if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) + { + err = GetLastError(); + if (err != ERROR_SERVICE_NOT_ACTIVE) + { + BOX_WARNING("Failed to stop Box Backup service: " << + GetErrorMessage(err)); + } + } + + BOOL deleted = DeleteService(service); + err = GetLastError(); + CloseServiceHandle(service); + + if (deleted) + { + BOX_INFO("Box Backup service deleted"); + return 0; + } + else if (err == ERROR_SERVICE_MARKED_FOR_DELETE) + { + BOX_ERROR("Failed to remove Box Backup service: " + "it is already being deleted"); + } + else + { + BOX_ERROR("Failed to remove Box Backup service: " << + GetErrorMessage(err)); + } + + return 1; +} + +#endif // WIN32 diff --git a/lib/bbackupd/Win32ServiceFunctions.h b/lib/bbackupd/Win32ServiceFunctions.h new file mode 100644 index 00000000..e04c368f --- /dev/null +++ b/lib/bbackupd/Win32ServiceFunctions.h @@ -0,0 +1,19 @@ +//*************************************************************** +// From the book "Win32 System Services: The Heart of Windows 98 +// and Windows 2000" +// by Marshall Brain +// Published by Prentice Hall +// Copyright 1995 Prentice Hall. +// +// This code implements the Windows API Service interface +// for the Box Backup for Windows native port. +//*************************************************************** + +#ifndef WIN32SERVICEFUNCTIONS_H +#define WIN32SERVICEFUNCTIONS_H + +int RemoveService (const std::string& rServiceName); +int InstallService (const char* pConfigFilePath, const std::string& rServiceName); +int OurService (const char* pConfigFileName); + +#endif diff --git a/lib/bbackupquery/BackupQueries.cpp b/lib/bbackupquery/BackupQueries.cpp new file mode 100644 index 00000000..bcb1827e --- /dev/null +++ b/lib/bbackupquery/BackupQueries.cpp @@ -0,0 +1,2410 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupQueries.cpp +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + +#ifdef HAVE_DIRENT_H + #include <dirent.h> +#endif + +#include <algorithm> +#include <cstring> +#include <limits> +#include <iostream> +#include <ostream> +#include <set> + +#include "BackupClientFileAttributes.h" +#include "BackupClientMakeExcludeList.h" +#include "BackupClientRestore.h" +#include "BackupQueries.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BoxTimeToText.h" +#include "CommonException.h" +#include "Configuration.h" +#include "ExcludeList.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "IOStream.h" +#include "Logging.h" +#include "PathUtils.h" +#include "SelfFlushingStream.h" +#include "Utils.h" +#include "autogen_BackupProtocol.h" +#include "autogen_CipherException.h" + +#include "MemLeakFindOn.h" + +// min() and max() macros from stdlib.h break numeric_limits<>::min(), etc. +#undef min +#undef max + +#define COMPARE_RETURN_SAME 1 +#define COMPARE_RETURN_DIFFERENT 2 +#define COMPARE_RETURN_ERROR 3 +#define COMMAND_RETURN_ERROR 4 + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::BackupQueries() +// Purpose: Constructor +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +BackupQueries::BackupQueries(BackupProtocolCallable &rConnection, + const Configuration &rConfiguration, bool readWrite) + : mReadWrite(readWrite), + mrConnection(rConnection), + mrConfiguration(rConfiguration), + mQuitNow(false), + mRunningAsRoot(false), + mWarnedAboutOwnerAttributes(false), + mReturnCode(0) // default return code +{ + #ifdef WIN32 + mRunningAsRoot = TRUE; + #else + mRunningAsRoot = (::geteuid() == 0); + #endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::~BackupQueries() +// Purpose: Destructor +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +BackupQueries::~BackupQueries() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::DoCommand(const char *, bool) +// Purpose: Perform a command +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::DoCommand(ParsedCommand& rCommand) +{ + // Check... + + if(rCommand.mFailed) + { + BOX_ERROR("Parse failed: unknown command '" << + rCommand.mCmdElements[0] << "' or failed to convert " + "encoding of arguments"); + return; + } + + if(rCommand.mCmdElements.size() < 1) + { + // blank command + return; + } + + if(rCommand.pSpec->type == Command_sh && + rCommand.mCmdElements.size() == 2) + { + // Yes, run shell command + int result = ::system(rCommand.mCmdElements[1].c_str()); + if(result != 0) + { + BOX_WARNING("System command returned error code " << + result); + SetReturnCode(ReturnCode::Command_Error); + } + return; + } + + if(rCommand.pSpec->type == Command_Unknown) + { + // No such command + BOX_ERROR("Unrecognised command: " << rCommand.mCmdElements[0]); + return; + } + + // Arguments + std::vector<std::string> args(rCommand.mCmdElements.begin() + 1, + rCommand.mCmdElements.end()); + + // Set up options + bool opts[256]; + for(int o = 0; o < 256; ++o) opts[o] = false; + // BLOCK + { + // options + const char *c = rCommand.mOptions.c_str(); + while(*c != 0) + { + // Valid option? + if(::strchr(rCommand.pSpec->opts, *c) == NULL) + { + BOX_ERROR("Invalid option '" << *c << "' for " + "command " << rCommand.pSpec->name); + return; + } + opts[(int)*c] = true; + ++c; + } + } + + if(rCommand.pSpec->type != Command_Quit) + { + // If not a quit command, set the return code to zero + SetReturnCode(ReturnCode::Command_OK); + } + + // Handle command + switch(rCommand.pSpec->type) + { + case Command_Quit: + mQuitNow = true; + break; + + case Command_List: + CommandList(args, opts); + break; + + case Command_pwd: + { + // Simple implementation, so do it here + BOX_NOTICE(GetCurrentDirectoryName() << " (" << + BOX_FORMAT_OBJECTID(GetCurrentDirectoryID()) << + ")"); + } + break; + + case Command_cd: + CommandChangeDir(args, opts); + break; + + case Command_lcd: + CommandChangeLocalDir(args); + break; + + case Command_sh: + BOX_ERROR("The command to run must be specified as an argument."); + break; + + case Command_GetObject: + CommandGetObject(args, opts); + break; + + case Command_Get: + CommandGet(args, opts); + break; + + case Command_Compare: + CommandCompare(args, opts); + break; + + case Command_Restore: + CommandRestore(args, opts); + break; + + case Command_Usage: + CommandUsage(opts); + break; + + case Command_Help: + CommandHelp(args); + break; + + case Command_Undelete: + CommandUndelete(args, opts); + break; + + case Command_Delete: + CommandDelete(args, opts); + break; + + default: + BOX_ERROR("Unknown command: " << rCommand.mCmdElements[0]); + break; + } +} + +#define LIST_OPTION_TIMES_ATTRIBS 'a' +#define LIST_OPTION_SORT_NO_DIRS_FIRST 'D' +#define LIST_OPTION_NOFLAGS 'F' +#define LIST_OPTION_DISPLAY_HASH 'h' +#define LIST_OPTION_SORT_ID 'i' +#define LIST_OPTION_NOOBJECTID 'I' +#define LIST_OPTION_SORT_REVERSE 'r' +#define LIST_OPTION_RECURSIVE 'R' +#define LIST_OPTION_SIZEINBLOCKS 's' +#define LIST_OPTION_SORT_SIZE 'S' +#define LIST_OPTION_TIMES_LOCAL 't' +#define LIST_OPTION_TIMES_UTC 'T' +#define LIST_OPTION_SORT_NONE 'U' + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandList(const std::vector<std::string> &, const bool *) +// Purpose: List directories (optionally recursive) +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandList(const std::vector<std::string> &args, const bool *opts) +{ + // default to using the current directory + int64_t rootDir = GetCurrentDirectoryID(); + + // name of base directory + std::string listRoot; // blank + + // Got a directory in the arguments? + if(args.size() > 0) + { +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) + return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Attempt to find the directory + rootDir = FindDirectoryObjectID(storeDirEncoded, + opts[LIST_OPTION_ALLOWOLD], + opts[LIST_OPTION_ALLOWDELETED]); + + if(rootDir == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found " + "on store."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + } + + // List it + List(rootDir, listRoot, opts, true /* first level to list */); +} + +static std::string GetTimeString(BackupStoreDirectory::Entry& en, + bool useLocalTime, bool showAttrModificationTimes) +{ + std::ostringstream out; + box_time_t originalTime, newAttributesTime; + + // there is no attribute modification time in the directory + // entry, unfortunately, so we can't display it. + originalTime = en.GetModificationTime(); + out << BoxTimeToISO8601String(originalTime, useLocalTime); + + if(en.HasAttributes()) + { + const StreamableMemBlock &storeAttr(en.GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + + box_time_t NewModificationTime, NewAttrModificationTime; + attr.GetModificationTimes(&NewModificationTime, + &NewAttrModificationTime); + + if (showAttrModificationTimes) + { + newAttributesTime = NewAttrModificationTime; + } + else + { + newAttributesTime = NewModificationTime; + } + + if (newAttributesTime == originalTime) + { + out << "*"; + } + else + { + out << "~" << BoxTimeToISO8601String(newAttributesTime, + useLocalTime); + } + } + else + { + out << " "; + } + + return out.str(); +} + +/* We need a way to pass options to sort functions for sorting. The algorithm + * doesn't seem to provide a way to do this, so I'm using a global variable. + * Which is not thread safe, but we don't currently use threads so that should + * be OK. Do not use threads without checking! + */ +const bool *gThreadUnsafeOptions; + +int DirsFirst(BackupStoreDirectory::Entry* a, + BackupStoreDirectory::Entry* b) +{ + if (a->IsDir() && !b->IsDir()) + { + return -1; // a < b + } + else if (!a->IsDir() && b->IsDir()) + { + return 1; // b > a + } + else + { + return 0; // continue comparison + } +} + +#define MAYBE_DIRS_FIRST(a, b) \ + if (!gThreadUnsafeOptions[LIST_OPTION_SORT_NO_DIRS_FIRST]) \ + { \ + int result = DirsFirst(a, b); \ + if (result < 0) return true; /* a < b */ \ + else if (result > 0) return false; /* a > b */ \ + /* else: fall through */ \ + } + +#define MAYBE_REVERSE(result) \ + (result != gThreadUnsafeOptions[LIST_OPTION_SORT_REVERSE]) +// result is false, opts[reverse] is false => return false +// result is false, opts[reverse] is true => return true +// result is true, opts[reverse] is false => return true +// result is true, opts[reverse] is true => return false +// this is logical XOR, for which the boolean operator is !=. + +bool SortById(BackupStoreDirectory::Entry* a, + BackupStoreDirectory::Entry* b) +{ + MAYBE_DIRS_FIRST(a, b); + bool result = (a->GetObjectID() < b->GetObjectID()); + return MAYBE_REVERSE(result); +} + +bool SortBySize(BackupStoreDirectory::Entry* a, + BackupStoreDirectory::Entry* b) +{ + MAYBE_DIRS_FIRST(a, b); + bool result = (a->GetSizeInBlocks() < b->GetSizeInBlocks()); + return MAYBE_REVERSE(result); +} + +bool SortByName(BackupStoreDirectory::Entry* a, + BackupStoreDirectory::Entry* b) +{ + MAYBE_DIRS_FIRST(a, b); + BackupStoreFilenameClear afc(a->GetName()); + BackupStoreFilenameClear bfc(b->GetName()); + std::string an = afc.GetClearFilename(); + std::string bn = bfc.GetClearFilename(); + bool result = (an < bn); + return MAYBE_REVERSE(result); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::List(int64_t, const std::string &, const bool *, bool) +// Purpose: Do the actual listing of directories and files +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +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 = 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, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + // both files and directories + excludeFlags, + true /* want attributes */); + } + catch (std::exception &e) + { + BOX_ERROR("Failed to list directory: " << e.what()); + SetReturnCode(ReturnCode::Command_Error); + return; + } + catch (...) + { + BOX_ERROR("Failed to list directory: unknown error"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Store entry pointers in a std::vector for sorting + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + std::vector<BackupStoreDirectory::Entry*> sorted_entries; + while((en = i.Next()) != 0) + { + sorted_entries.push_back(en); + } + + // Typedef to avoid mind-bending while dealing with pointers to functions. + typedef bool (EntryComparator_t)(BackupStoreDirectory::Entry* a, + BackupStoreDirectory::Entry* b); + // Default is no comparator, i.e. no sorting. + EntryComparator_t* pComparator = NULL; + + if (opts[LIST_OPTION_SORT_ID]) + { + pComparator = &SortById; + } + else if (opts[LIST_OPTION_SORT_SIZE]) + { + pComparator = &SortBySize; + } + else if (opts[LIST_OPTION_SORT_NONE]) + { + // do nothing + } + else // sort by name + { + pComparator = &SortByName; + } + + if (pComparator != NULL) + { + gThreadUnsafeOptions = opts; + sort(sorted_entries.begin(), sorted_entries.end(), + pComparator); + gThreadUnsafeOptions = NULL; + } + + for (std::vector<BackupStoreDirectory::Entry*>::const_iterator + i = sorted_entries.begin(); + i != sorted_entries.end(); i++) + { + en = *i; + std::ostringstream buf; + + // Display this entry + BackupStoreFilenameClear clear(en->GetName()); + + // Object ID? + if(!opts[LIST_OPTION_NOOBJECTID]) + { + // add object ID to line + buf << std::hex << std::internal << std::setw(8) << + std::setfill('0') << en->GetObjectID() << + std::dec << " "; + } + + // Flags? + if(!opts[LIST_OPTION_NOFLAGS]) + { + static const char *flags = BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES; + char displayflags[16]; + // make sure f is big enough + ASSERT(sizeof(displayflags) >= sizeof(BACKUPSTOREDIRECTORY_ENTRY_FLAGS_DISPLAY_NAMES) + 3); + // Insert flags + char *f = displayflags; + const char *t = flags; + int16_t en_flags = en->GetFlags(); + while(*t != 0) + { + *f = ((en_flags&1) == 0)?'-':*t; + en_flags >>= 1; + f++; + t++; + } + // attributes flags + *(f++) = (en->HasAttributes())?'a':'-'; + + // terminate + *(f++) = ' '; + *(f++) = '\0'; + buf << displayflags; + + if(en_flags != 0) + { + buf << "[ERROR: Entry has additional flags set] "; + } + } + + if(opts[LIST_OPTION_TIMES_UTC]) + { + // Show UTC times... + buf << GetTimeString(*en, false, + opts[LIST_OPTION_TIMES_ATTRIBS]) << " "; + } + + if(opts[LIST_OPTION_TIMES_LOCAL]) + { + // Show local times... + buf << GetTimeString(*en, true, + opts[LIST_OPTION_TIMES_ATTRIBS]) << " "; + } + + if(opts[LIST_OPTION_DISPLAY_HASH]) + { + buf << std::hex << std::internal << std::setw(16) << + std::setfill('0') << en->GetAttributesHash() << + std::dec; + } + + if(opts[LIST_OPTION_SIZEINBLOCKS]) + { + buf << std::internal << std::setw(5) << + std::setfill('0') << en->GetSizeInBlocks() << + " "; + } + + // add name + if(!FirstLevel) + { +#ifdef WIN32 + std::string listRootDecoded; + if(!ConvertUtf8ToConsole(rListRoot.c_str(), + listRootDecoded)) return; + listRootDecoded += "/"; + buf << listRootDecoded; + WriteConsole(hOut, listRootDecoded.c_str(), + strlen(listRootDecoded.c_str()), &n_chars, NULL); +#else + 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)) + { + fileName = fileNameUtf8 + " [convert encoding failed]"; + } +#endif + + buf << fileName; + + if(!en->GetName().IsEncrypted()) + { + buf << " [FILENAME NOT ENCRYPTED]"; + } + + 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) + { + // Recurse? + if(opts[LIST_OPTION_RECURSIVE]) + { + std::string subroot(rListRoot); + if(!FirstLevel) subroot += '/'; + subroot += clear.GetClearFilename(); + List(en->GetObjectID(), subroot, opts, + false /* not the first level to list */, + pOut); + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::FindDirectoryObjectID(const +// std::string &) +// Purpose: Find the object ID of a directory on the store, +// or return 0 for not found. If pStack != 0, the +// object is set to the stack of directories. +// Will start from the current directory stack. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::FindDirectoryObjectID(const std::string &rDirName, + bool AllowOldVersion, bool AllowDeletedDirs, + std::vector<std::pair<std::string, int64_t> > *pStack) +{ + // Split up string into elements + std::vector<std::string> dirElements; + SplitString(rDirName, '/', dirElements); + + // Start from current stack, or root, whichever is required + std::vector<std::pair<std::string, int64_t> > stack; + int64_t dirID = BackupProtocolListDirectory::RootDirectory; + if(rDirName.size() > 0 && rDirName[0] == '/') + { + // Root, do nothing + } + else + { + // Copy existing stack + stack = mDirStack; + if(stack.size() > 0) + { + dirID = stack[stack.size() - 1].second; + } + } + + // Generate exclude flags + 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) + { + if(dirElements[e].size() > 0) + { + if(dirElements[e] == ".") + { + // Ignore. + } + else if(dirElements[e] == "..") + { + // Up one! + if(stack.size() > 0) + { + // Remove top element + stack.pop_back(); + + // New dir ID + dirID = (stack.size() > 0)?(stack[stack.size() - 1].second):BackupProtocolListDirectory::RootDirectory; + } + else + { + // At root anyway + dirID = BackupProtocolListDirectory::RootDirectory; + } + } + else + { + // Not blank element. Read current directory. + std::auto_ptr<BackupProtocolSuccess> dirreply(mrConnection.QueryListDirectory( + dirID, + BackupProtocolListDirectory::Flags_Dir, // just directories + excludeFlags, + true /* want attributes */)); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Then... find the directory within it + BackupStoreDirectory::Iterator i(dir); + BackupStoreFilenameClear dirname(dirElements[e]); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(dirname); + if(en == 0) + { + // Not found + return 0; + } + + // Object ID for next round of searching + dirID = en->GetObjectID(); + + // Push onto stack + stack.push_back(std::pair<std::string, int64_t>(dirElements[e], dirID)); + } + } + } + + // If required, copy the new stack to the caller + if(pStack) + { + *pStack = stack; + } + + return dirID; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::GetCurrentDirectoryID() +// Purpose: Returns the ID of the current directory +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::GetCurrentDirectoryID() +{ + // Special case for root + if(mDirStack.size() == 0) + { + return BackupProtocolListDirectory::RootDirectory; + } + + // Otherwise, get from the last entry on the stack + return mDirStack[mDirStack.size() - 1].second; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::GetCurrentDirectoryName() +// Purpose: Gets the name of the current directory +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +std::string BackupQueries::GetCurrentDirectoryName() +{ + // Special case for root + if(mDirStack.size() == 0) + { + return std::string("/"); + } + + // Build path + std::string r; + for(unsigned int l = 0; l < mDirStack.size(); ++l) + { + r += "/"; +#ifdef WIN32 + std::string dirName; + if(!ConvertUtf8ToConsole(mDirStack[l].first.c_str(), dirName)) + return "error"; + r += dirName; +#else + r += mDirStack[l].first; +#endif + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandChangeDir(const std::vector<std::string> &) +// Purpose: Change directory command +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandChangeDir(const std::vector<std::string> &args, const bool *opts) +{ + if(args.size() != 1 || args[0].size() == 0) + { + BOX_ERROR("Incorrect usage. cd [-o] [-d] <directory>"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + +#ifdef WIN32 + std::string dirName; + if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) return; +#else + const std::string& dirName(args[0]); +#endif + + std::vector<std::pair<std::string, int64_t> > newStack; + int64_t id = FindDirectoryObjectID(dirName, opts['o'], opts['d'], + &newStack); + + if(id == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Store new stack + mDirStack = newStack; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &) +// Purpose: Change local directory command +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandChangeLocalDir(const std::vector<std::string> &args) +{ + if(args.size() != 1 || args[0].size() == 0) + { + BOX_ERROR("Incorrect usage. lcd <local-directory>"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Try changing directory +#ifdef WIN32 + std::string dirName; + if(!ConvertConsoleToUtf8(args[0].c_str(), dirName)) + { + BOX_ERROR("Failed to convert path from console encoding."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + int result = ::chdir(dirName.c_str()); +#else + int result = ::chdir(args[0].c_str()); +#endif + if(result != 0) + { + if(errno == ENOENT || errno == ENOTDIR) + { + BOX_ERROR("Directory '" << args[0] << "' does not exist."); + } + else + { + BOX_LOG_SYS_ERROR("Failed to change to directory " + "'" << args[0] << "'"); + } + + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Report current dir + char wd[PATH_MAX]; + if(::getcwd(wd, PATH_MAX) == 0) + { + BOX_LOG_SYS_ERROR("Error getting current directory"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + +#ifdef WIN32 + if(!ConvertUtf8ToConsole(wd, dirName)) + { + BOX_ERROR("Failed to convert new path from console encoding."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + BOX_INFO("Local current directory is now '" << dirName << "'."); +#else + BOX_INFO("Local current directory is now '" << wd << "'."); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandGetObject(const std::vector<std::string> &, const bool *) +// Purpose: Gets an object without any translation. +// Created: 2003/10/11 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandGetObject(const std::vector<std::string> &args, const bool *opts) +{ + // Check args + if(args.size() != 2) + { + BOX_ERROR("Incorrect usage. getobject <object-id> " + "<local-filename>"); + return; + } + + 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): " << + args[0]); + return; + } + + // Does file exist? + EMU_STRUCT_STAT st; + if(EMU_STAT(args[1].c_str(), &st) == 0 || errno != ENOENT) + { + BOX_ERROR("The local file '" << args[1] << " already exists."); + return; + } + + // Open file + FileStream out(args[1].c_str(), O_WRONLY | O_CREAT | O_EXCL); + + // Request that object + try + { + // Request object + std::auto_ptr<BackupProtocolSuccess> getobj(mrConnection.QueryGetObject(id)); + + // Stream that object out to the file + std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream()); + objectStream->CopyStreamTo(out); + + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(id) << + " fetched successfully."); + } + catch(ConnectionException &e) + { + if(mrConnection.GetLastErrorType() == BackupProtocolError::Err_DoesNotExist) + { + BOX_ERROR("Object ID " << BOX_FORMAT_OBJECTID(id) << + " does not exist on store."); + ::unlink(args[1].c_str()); + } + else + { + BOX_ERROR("Error occured fetching object."); + ::unlink(args[1].c_str()); + } + } + catch(...) + { + ::unlink(args[1].c_str()); + BOX_ERROR("Error occured fetching object."); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::FindFileID(const std::string& +// rNameOrIdString, const bool *options, +// int64_t *pDirIdOut, std::string* pFileNameOut) +// Purpose: Locate a file on the store (either by name or by +// object ID, depending on opts['i'], where name can +// include a path) and return the file ID, placing the +// directory ID in *pDirIdOut and the filename part +// of the path in *pFileNameOut (if not NULL). +// Created: 2008-09-12 +// +// -------------------------------------------------------------------------- +int64_t BackupQueries::FindFileID(const std::string& rNameOrIdString, + const bool *opts, int64_t *pDirIdOut, std::string* pFileNameOut, + int16_t flagsInclude, int16_t flagsExclude, int16_t* pFlagsOut) +{ + // Find object ID somehow + int64_t fileId; + int64_t dirId = GetCurrentDirectoryID(); + std::string fileName = rNameOrIdString; + + if(!opts['i']) + { + // does this remote filename include a path? + std::string::size_type index = fileName.rfind('/'); + if(index != std::string::npos) + { + std::string dirName(fileName.substr(0, index)); + fileName = fileName.substr(index + 1); + + dirId = FindDirectoryObjectID(dirName); + if(dirId == 0) + { + BOX_ERROR("Directory '" << dirName << + "' not found."); + return 0; + } + } + } + + BackupStoreFilenameClear fn(fileName); + + // Need to look it up in the current directory + mrConnection.QueryListDirectory( + dirId, flagsInclude, flagsExclude, + true /* do want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + BackupStoreDirectory::Entry *en; + + if(opts['i']) + { + // Specified as ID. + fileId = ::strtoll(rNameOrIdString.c_str(), 0, 16); + if(fileId == std::numeric_limits<long long>::min() || + fileId == std::numeric_limits<long long>::max() || + fileId == 0) + { + BOX_ERROR("Not a valid object ID (specified in hex): " + << rNameOrIdString); + return 0; + } + + // Check that the item is actually in the directory + en = dir.FindEntryByID(fileId); + if(en == 0) + { + BOX_ERROR("File ID " << + BOX_FORMAT_OBJECTID(fileId) << + " not found in current directory on store.\n" + "(You can only access files by ID from the " + "current directory.)"); + return 0; + } + } + else + { + // Specified by name, find the object in the directory to get the ID + BackupStoreDirectory::Iterator i(dir); + en = i.FindMatchingClearName(fn); + if(en == 0) + { + BOX_ERROR("Filename '" << rNameOrIdString << "' " + "not found in current directory on store.\n" + "(Subdirectories in path not searched.)"); + return 0; + } + + fileId = en->GetObjectID(); + } + + *pDirIdOut = dirId; + + if(pFlagsOut) + { + *pFlagsOut = en->GetFlags(); + } + + if(pFileNameOut) + { + BackupStoreFilenameClear entryName(en->GetName()); + *pFileNameOut = entryName.GetClearFilename(); + } + + return fileId; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandGet(const std::vector<std::string> &, const bool *) +// Purpose: Command to get a file from the store +// Created: 2003/10/12 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandGet(std::vector<std::string> args, const bool *opts) +{ + // At least one argument? + // Check args + if(args.size() < 1 || (opts['i'] && args.size() != 2) || args.size() > 2) + { + BOX_ERROR("Incorrect usage.\n" + "get <remote-filename> [<local-filename>] or\n" + "get -i <object-id> <local-filename>"); + return; + } + + // Find object ID somehow + int64_t fileId, dirId; + std::string localName; + +#ifdef WIN32 + for (std::vector<std::string>::iterator + i = args.begin(); i != args.end(); i++) + { + std::string out; + if(!ConvertConsoleToUtf8(i->c_str(), out)) + { + BOX_ERROR("Failed to convert encoding."); + return; + } + *i = out; + } +#endif + + int16_t flagsExclude; + + if(opts['i']) + { + // can retrieve anything by ID + flagsExclude = BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING; + } + else + { + // only current versions by name + flagsExclude = + BackupProtocolListDirectory::Flags_OldVersion | + BackupProtocolListDirectory::Flags_Deleted; + } + + + fileId = FindFileID(args[0], opts, &dirId, &localName, + BackupProtocolListDirectory::Flags_File, // just files + flagsExclude, NULL /* don't care about flags found */); + + if (fileId == 0) + { + // error already reported + return; + } + + if(opts['i']) + { + // Specified as ID. Must have a local name in the arguments + // (check at beginning of function ensures this) + localName = args[1]; + } + else + { + // Specified by name. Local name already set by FindFileID, + // but may be overridden by user supplying a second argument. + if(args.size() == 2) + { + localName = args[1]; + } + } + + // Does local file already exist? (don't want to overwrite) + EMU_STRUCT_STAT st; + if(EMU_STAT(localName.c_str(), &st) == 0 || errno != ENOENT) + { + BOX_ERROR("The local file " << localName << " already exists, " + "will not overwrite it."); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + // Request it from the store + try + { + // Request object + mrConnection.QueryGetFile(dirId, fileId); + + // Stream containing encoded file + std::auto_ptr<IOStream> objectStream(mrConnection.ReceiveStream()); + + // Decode it + BackupStoreFile::DecodeFile(*objectStream, localName.c_str(), mrConnection.GetTimeout()); + + // Done. + BOX_INFO("Object ID " << BOX_FORMAT_OBJECTID(fileId) << + " fetched successfully."); + } + catch (BoxException &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to fetch file: " << + e.what()); + ::unlink(localName.c_str()); + } + catch(...) + { + BOX_ERROR("Failed to fetch file: unknown error"); + ::unlink(localName.c_str()); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CompareParams::CompareParams() +// Purpose: Constructor +// Created: 29/1/04 +// +// -------------------------------------------------------------------------- +BackupQueries::CompareParams::CompareParams(bool QuickCompare, + bool IgnoreExcludes, bool IgnoreAttributes, + box_time_t LatestFileUploadTime) +: BoxBackupCompareParams(QuickCompare, IgnoreExcludes, IgnoreAttributes, + LatestFileUploadTime), + mDifferences(0), + mDifferencesExplainedByModTime(0), + mUncheckedFiles(0), + mExcludedDirs(0), + mExcludedFiles(0) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandCompare(const std::vector<std::string> &, const bool *) +// Purpose: Command to compare data on the store with local data +// Created: 2003/10/12 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandCompare(const std::vector<std::string> &args, const bool *opts) +{ + box_time_t LatestFileUploadTime = GetCurrentBoxTime(); + + // Try and work out the time before which all files should be on the server + { + std::string syncTimeFilename(mrConfiguration.GetKeyValue("DataDirectory") + DIRECTORY_SEPARATOR_ASCHAR); + syncTimeFilename += "last_sync_start"; + // Stat it to get file time + EMU_STRUCT_STAT st; + if(EMU_STAT(syncTimeFilename.c_str(), &st) == 0) + { + // Files modified after this time shouldn't be on the server, so report errors slightly differently + LatestFileUploadTime = FileModificationTime(st) - + SecondsToBoxTime(mrConfiguration.GetKeyValueInt("MinimumFileAge")); + } + else + { + BOX_WARNING("Failed to determine the time of the last " + "synchronisation -- checks not performed."); + } + } + + // Parameters, including count of differences + BackupQueries::CompareParams params(opts['q'], // quick compare? + opts['E'], // ignore excludes + opts['A'], // ignore attributes + LatestFileUploadTime); + + params.mQuietCompare = opts['Q']; + + // Quick compare? + if(params.QuickCompare()) + { + BOX_WARNING("Quick compare used -- file attributes are not " + "checked."); + } + + if(!opts['l'] && opts['a'] && args.size() == 0) + { + // Compare all locations + const Configuration &rLocations( + mrConfiguration.GetSubConfiguration("BackupLocations")); + std::vector<std::string> locNames = + rLocations.GetSubConfigurationNames(); + for(std::vector<std::string>::iterator + pLocName = locNames.begin(); + pLocName != locNames.end(); + pLocName++) + { + CompareLocation(*pLocName, params); + } + } + else if(opts['l'] && !opts['a'] && args.size() == 1) + { + // Compare one location + CompareLocation(args[0], params); + } + else if(!opts['l'] && !opts['a'] && args.size() == 2) + { + // Compare directory to directory + + // Can't be bothered to do all the hard work to work out which location it's on, and hence which exclude list + if(!params.IgnoreExcludes()) + { + BOX_ERROR("Cannot use excludes on directory to directory comparison -- use -E flag to specify ignored excludes."); + return; + } + else + { + // Do compare + Compare(args[0], args[1], params); + } + } + else + { + BOX_ERROR("Incorrect usage.\ncompare -a\n or compare -l <location-name>\n or compare <store-dir-name> <local-dir-name>"); + return; + } + + if (!params.mQuietCompare) + { + BOX_INFO("[ " << + params.mDifferencesExplainedByModTime << " (of " << + params.mDifferences << ") differences probably " + "due to file modifications after the last upload ]"); + } + + BOX_INFO("Differences: " << params.mDifferences << " (" << + params.mExcludedDirs << " dirs excluded, " << + params.mExcludedFiles << " files excluded, " << + params.mUncheckedFiles << " files not checked)"); + + // Set return code? + if(opts['c']) + { + if (params.mUncheckedFiles != 0) + { + SetReturnCode(ReturnCode::Compare_Error); + } + else if (params.mDifferences != 0) + { + SetReturnCode(ReturnCode::Compare_Different); + } + else + { + SetReturnCode(ReturnCode::Compare_Same); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CompareLocation(const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a location +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams) +{ + // Find the location's sub configuration + const Configuration &locations(mrConfiguration.GetSubConfiguration("BackupLocations")); + if(!locations.SubConfigurationExists(rLocation.c_str())) + { + BOX_ERROR("Location " << rLocation << " does not exist."); + return; + } + const Configuration &loc(locations.GetSubConfiguration(rLocation.c_str())); + + #ifdef WIN32 + { + std::string path = loc.GetKeyValue("Path"); + if (path.size() > 0 && path[path.size()-1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + BOX_WARNING("Location '" << rLocation << "' path ends " + "with '" DIRECTORY_SEPARATOR "', " + "compare may fail!"); + } + } + #endif + + // Generate the exclude lists + if(!rParams.IgnoreExcludes()) + { + rParams.LoadExcludeLists(loc); + } + + // Then get it compared + Compare(std::string("/") + rLocation, loc.GetKeyValue("Path"), rParams); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::Compare(const std::string &, +// const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a store directory against a local directory +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::Compare(const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) +{ +#ifdef WIN32 + std::string localDirEncoded; + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(rLocalDir.c_str(), localDirEncoded)) return; + if(!ConvertConsoleToUtf8(rStoreDir.c_str(), storeDirEncoded)) return; +#else + const std::string& localDirEncoded(rLocalDir); + const std::string& storeDirEncoded(rStoreDir); +#endif + + // Get the directory ID of the directory -- only use current data + int64_t dirID = FindDirectoryObjectID(storeDirEncoded); + + // Found? + if(dirID == 0) + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyRemoteFileMissing(localDirEncoded, + storeDirEncoded, modifiedAfterLastSync); + return; + } + + // Go! + 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); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::Compare(int64_t, const std::string &, +// const std::string &, BackupQueries::CompareParams &) +// Purpose: Compare a store directory against a local directory +// Created: 2003/10/13 +// +// -------------------------------------------------------------------------- +void BackupQueries::Compare(int64_t DirID, const std::string &rStoreDir, + const std::string &rLocalDir, BoxBackupCompareParams &rParams) +{ + rParams.NotifyDirComparing(rLocalDir, rStoreDir); + + // Get info on the local directory + EMU_STRUCT_STAT st; + if(EMU_LSTAT(rLocalDir.c_str(), &st) != 0) + { + // What kind of error? + if(errno == ENOTDIR || errno == ENOENT) + { + rParams.NotifyLocalDirMissing(rLocalDir, rStoreDir); + } + else + { + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); + } + return; + } + + // Get the directory listing from the store + mrConnection.QueryListDirectory( + DirID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + // get everything + BackupProtocolListDirectory::Flags_OldVersion | + BackupProtocolListDirectory::Flags_Deleted, + // except for old versions and deleted files + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr<IOStream> dirstream(mrConnection.ReceiveStream()); + dir.ReadFromStream(*dirstream, mrConnection.GetTimeout()); + + // Test out the attributes + if(!dir.HasAttributes()) + { + rParams.NotifyStoreDirMissingAttributes(rLocalDir, rStoreDir); + } + else + { + // Fetch the attributes + const StreamableMemBlock &storeAttr(dir.GetAttributes()); + BackupClientFileAttributes attr(storeAttr); + + // Get attributes of local directory + BackupClientFileAttributes localAttr; + localAttr.ReadAttributes(rLocalDir.c_str(), + true /* directories have zero mod times */); + + if(attr.Compare(localAttr, true, true /* ignore modification times */)) + { + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + false, false /* actually we didn't check :) */); + } + else + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(rLocalDir.c_str(), &st) == 0) + { + if(FileAttrModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyDirCompared(rLocalDir, rStoreDir, + true, modifiedAfterLastSync); + } + } + + // Open the local directory + DIR *dirhandle = ::opendir(rLocalDir.c_str()); + if(dirhandle == 0) + { + rParams.NotifyLocalDirAccessFailed(rLocalDir, rStoreDir); + return; + } + + try + { + // Read the files and directories into sets + std::set<std::string> localFiles; + std::set<std::string> localDirs; + struct dirent *localDirEn = 0; + while((localDirEn = readdir(dirhandle)) != 0) + { + // Not . and ..! + if(localDirEn->d_name[0] == '.' && + (localDirEn->d_name[1] == '\0' || (localDirEn->d_name[1] == '.' && localDirEn->d_name[2] == '\0'))) + { + // ignore, it's . or .. + +#ifdef HAVE_VALID_DIRENT_D_TYPE + if (localDirEn->d_type != DT_DIR) + { + BOX_ERROR("d_type does not really " + "work on your platform. " + "Reconfigure Box!"); + return; + } +#endif + + continue; + } + + std::string localDirPath(MakeFullPath(rLocalDir, + localDirEn->d_name)); + std::string storeDirPath(rStoreDir + "/" + + localDirEn->d_name); + +#ifndef HAVE_VALID_DIRENT_D_TYPE + EMU_STRUCT_STAT st; + if(EMU_LSTAT(localDirPath.c_str(), &st) != 0) + { + // Check whether dir is excluded before trying + // to stat it, to fix problems with .gvfs + // directories that are not readable by root + // causing compare to crash: + // http://lists.boxbackup.org/pipermail/boxbackup/2010-January/000013.html + if(rParams.IsExcludedDir(localDirPath)) + { + rParams.NotifyExcludedDir(localDirPath, + storeDirPath); + continue; + } + else + { + THROW_EXCEPTION_MESSAGE(CommonException, + OSFileError, localDirPath); + } + } + + // Entry -- file or dir? + if(S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) + { + // File or symbolic link + localFiles.insert(std::string(localDirEn->d_name)); + } + else if(S_ISDIR(st.st_mode)) + { + // Directory + localDirs.insert(std::string(localDirEn->d_name)); + } +#else + // Entry -- file or dir? + if(localDirEn->d_type == DT_REG || localDirEn->d_type == DT_LNK) + { + // File or symbolic link + localFiles.insert(std::string(localDirEn->d_name)); + } + else if(localDirEn->d_type == DT_DIR) + { + // Directory + localDirs.insert(std::string(localDirEn->d_name)); + } +#endif + } + // Close directory + if(::closedir(dirhandle) != 0) + { + BOX_LOG_SYS_ERROR("Failed to close local directory " + "'" << rLocalDir << "'"); + } + dirhandle = 0; + + // Do the same for the store directories + std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeFiles; + std::set<std::pair<std::string, BackupStoreDirectory::Entry *> > storeDirs; + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *storeDirEn = 0; + while((storeDirEn = i.Next()) != 0) + { + // Decrypt filename + BackupStoreFilenameClear name(storeDirEn->GetName()); + + // What is it? + if((storeDirEn->GetFlags() & BackupStoreDirectory::Entry::Flags_File) == BackupStoreDirectory::Entry::Flags_File) + { + // File + storeFiles.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn)); + } + else + { + // Dir + storeDirs.insert(std::pair<std::string, BackupStoreDirectory::Entry *>(name.GetClearFilename(), storeDirEn)); + } + } + +#ifdef _MSC_VER + typedef std::set<std::string>::iterator string_set_iter_t; +#else + typedef std::set<std::string>::const_iterator string_set_iter_t; +#endif + + // Now compare files. + for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeFiles.begin(); i != storeFiles.end(); ++i) + { + const std::string& fileName(i->first); + + std::string localPath(MakeFullPath(rLocalDir, fileName)); + std::string storePath(rStoreDir + "/" + fileName); + + rParams.NotifyFileComparing(localPath, storePath); + + // Does the file exist locally? + string_set_iter_t local(localFiles.find(fileName)); + if(local == localFiles.end()) + { + // Not found -- report + rParams.NotifyLocalFileMissing(localPath, + storePath); + } + else + { + CompareOneFile(DirID, i->second, localPath, + storePath, rParams); + + // Remove from set so that we know it's been compared + localFiles.erase(local); + } + } + + // Report any files which exist locally, but not on the store + for(string_set_iter_t i = localFiles.begin(); i != localFiles.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); + + // Should this be ignored (ie is excluded)? + if(!rParams.IsExcludedFile(localPath)) + { + bool modifiedAfterLastSync = false; + + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0) + { + if(FileModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); + } + else + { + rParams.NotifyExcludedFile(localPath, + storePath); + } + } + + // Finished with the files, clear the sets to reduce memory usage slightly + localFiles.clear(); + storeFiles.clear(); + + // Now do the directories, recursively to check subdirectories + for(std::set<std::pair<std::string, BackupStoreDirectory::Entry *> >::const_iterator i = storeDirs.begin(); i != storeDirs.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, i->first)); + std::string storePath(rStoreDir + "/" + i->first); + + // Does the directory exist locally? + string_set_iter_t local(localDirs.find(i->first)); + if(local == localDirs.end() && + rParams.IsExcludedDir(localPath)) + { + rParams.NotifyExcludedFileNotDeleted(localPath, + storePath); + } + else if(local == localDirs.end()) + { + // Not found -- report + rParams.NotifyLocalFileMissing(localPath, + storePath); + } + else if(rParams.IsExcludedDir(localPath)) + { + // don't recurse into excluded directories + } + else + { + // Compare directory + Compare(i->second->GetObjectID(), + storePath, localPath, rParams); + + // Remove from set so that we know it's been compared + localDirs.erase(local); + } + } + + // Report any directories which exist locally, but not on the store + for(std::set<std::string>::const_iterator + i = localDirs.begin(); + i != localDirs.end(); ++i) + { + std::string localPath(MakeFullPath(rLocalDir, *i)); + std::string storePath(rStoreDir + "/" + *i); + + // Should this be ignored (ie is excluded)? + if(!rParams.IsExcludedDir(localPath)) + { + bool modifiedAfterLastSync = false; + + // Check the dir modification time + EMU_STRUCT_STAT st; + if(EMU_STAT(localPath.c_str(), &st) == 0 && + FileModificationTime(st) > + rParams.LatestFileUploadTime()) + { + modifiedAfterLastSync = true; + } + + rParams.NotifyRemoteFileMissing(localPath, + storePath, modifiedAfterLastSync); + } + else + { + rParams.NotifyExcludedDir(localPath, storePath); + } + } + } + catch(...) + { + if(dirhandle != 0) + { + ::closedir(dirhandle); + } + throw; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandRestore(const std::vector<std::string> &, const bool *) +// Purpose: Restore a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandRestore(const std::vector<std::string> &args, const bool *opts) +{ + // Check arguments + if(args.size() < 1 || args.size() > 2) + { + BOX_ERROR("Incorrect usage. restore [-drif] <remote-name> " + "[<local-name>]"); + return; + } + + // Restoring deleted things? + bool restoreDeleted = opts['d']; + + std::string storeDirEncoded; + + // Get directory ID + int64_t dirID = 0; + if(opts['i']) + { + // Specified as ID. + 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): " + << args[0]); + return; + } + std::ostringstream oss; + oss << BOX_FORMAT_OBJECTID(args[0]); + storeDirEncoded = oss.str(); + } + else + { +#ifdef WIN32 + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) + return; +#else + storeDirEncoded = args[0]; +#endif + + // Look up directory ID + dirID = FindDirectoryObjectID(storeDirEncoded, + false /* no old versions */, + restoreDeleted /* find deleted dirs */); + } + + // Allowable? + if(dirID == 0) + { + BOX_ERROR("Directory '" << args[0] << "' not found on server"); + return; + } + + if(dirID == BackupProtocolListDirectory::RootDirectory) + { + BOX_ERROR("Cannot restore the root directory -- restore locations individually."); + return; + } + + std::string localName; + + 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; + + try + { + // At TRACE level, we print a line for each file and + // directory, so we don't need dots. + + result = BackupClientRestore(mrConnection, dirID, + storeDirEncoded.c_str(), localName.c_str(), + true /* print progress dots */, restoreDeleted, + false /* don't undelete after restore! */, + opts['r'] /* resume? */, + opts['f'] /* force continue after errors */); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to restore: " << e.what()); + SetReturnCode(ReturnCode::Command_Error); + return; + } + catch(...) + { + BOX_ERROR("Failed to restore: unknown exception"); + SetReturnCode(ReturnCode::Command_Error); + return; + } + + switch(result) + { + case Restore_Complete: + BOX_INFO("Restore complete."); + break; + + case Restore_CompleteWithErrors: + BOX_WARNING("Restore complete, but some files could not be " + "restored."); + break; + + case Restore_ResumePossible: + BOX_ERROR("Resume possible -- repeat command with -r flag " + "to resume."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_TargetExists: + BOX_ERROR("The target directory exists. You cannot restore " + "over an existing directory."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_TargetPathNotFound: + BOX_ERROR("The target directory path does not exist.\n" + "To restore to a directory whose parent " + "does not exist, create the parent first."); + SetReturnCode(ReturnCode::Command_Error); + break; + + case Restore_UnknownError: + BOX_ERROR("Unknown error during restore."); + SetReturnCode(ReturnCode::Command_Error); + break; + + default: + BOX_ERROR("Unknown restore result " << result << "."); + SetReturnCode(ReturnCode::Command_Error); + break; + } +} + + + +// These are autogenerated by a script. +extern const char *help_commands[]; +extern const char *help_text[]; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandHelp(const std::vector<std::string> &args) +// Purpose: Display help on commands +// Created: 15/2/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandHelp(const std::vector<std::string> &args) +{ + if(args.size() == 0) + { + // Display a list of all commands + printf("Available commands are:\n"); + for(int c = 0; help_commands[c] != 0; ++c) + { + printf(" %s\n", help_commands[c]); + } + printf("Type \"help <command>\" for more information on a command.\n\n"); + } + else + { + // Display help on a particular command + int c; + for(c = 0; help_commands[c] != 0; ++c) + { + if(::strcmp(help_commands[c], args[0].c_str()) == 0) + { + // Found the command, print help + printf("\n%s\n", help_text[c]); + break; + } + } + if(help_commands[c] == 0) + { + printf("No help found for command '%s'\n", args[0].c_str()); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUsage() +// Purpose: Display storage space used on server +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUsage(const bool *opts) +{ + bool MachineReadable = opts['m']; + + // Request full details from the server + std::auto_ptr<BackupProtocolAccountUsage> usage(mrConnection.QueryGetAccountUsage()); + + // Display each entry in turn + int64_t hardLimit = usage->GetBlocksHardLimit(); + int32_t blockSize = usage->GetBlockSize(); + CommandUsageDisplayEntry("Used", usage->GetBlocksUsed(), hardLimit, + blockSize, MachineReadable); + CommandUsageDisplayEntry("Old files", usage->GetBlocksInOldFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Deleted files", usage->GetBlocksInDeletedFiles(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Directories", usage->GetBlocksInDirectories(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Soft limit", usage->GetBlocksSoftLimit(), + hardLimit, blockSize, MachineReadable); + CommandUsageDisplayEntry("Hard limit", hardLimit, hardLimit, blockSize, + MachineReadable); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUsageDisplayEntry(const char *, +// int64_t, int64_t, int32_t, bool) +// Purpose: Display an entry in the usage table +// Created: 19/4/04 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUsageDisplayEntry(const char *Name, int64_t Size, +int64_t HardLimit, int32_t BlockSize, bool MachineReadable) +{ + std::cout << FormatUsageLineStart(Name, MachineReadable) << + FormatUsageBar(Size, Size * BlockSize, HardLimit * BlockSize, + MachineReadable) << std::endl; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandUndelete(const std::vector<std::string> &, const bool *) +// Purpose: Undelete a directory +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandUndelete(const std::vector<std::string> &args, const bool *opts) +{ + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + + // Check arguments + if(args.size() != 1) + { + BOX_ERROR("Incorrect usage. undelete <name> or undelete -i <object-id>"); + return; + } + +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + /* include old and deleted files */ + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + &flagsOut); + + if (fileId == 0) + { + // error already reported + return; + } + + // Undelete it on the store + try + { + // Undelete object + if(flagsOut & BackupProtocolListDirectory::Flags_File) + { + mrConnection.QueryUndeleteFile(parentId, fileId); + } + else + { + mrConnection.QueryUndeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to undelete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to undelete object: unknown error"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupQueries::CommandDelete(const +// std::vector<std::string> &, const bool *) +// Purpose: Deletes a file +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +void BackupQueries::CommandDelete(const std::vector<std::string> &args, + const bool *opts) +{ + if (!mReadWrite) + { + BOX_ERROR("This command requires a read-write connection. " + "Please reconnect with the -w option."); + return; + } + + // Check arguments + if(args.size() != 1) + { + BOX_ERROR("Incorrect usage. delete <name>"); + return; + } + +#ifdef WIN32 + std::string storeDirEncoded; + if(!ConvertConsoleToUtf8(args[0].c_str(), storeDirEncoded)) return; +#else + const std::string& storeDirEncoded(args[0]); +#endif + + // Find object ID somehow + int64_t fileId, parentId; + std::string fileName; + int16_t flagsOut; + + fileId = FindFileID(storeDirEncoded, opts, &parentId, &fileName, + /* include files and directories */ + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + /* exclude old and deleted files */ + BackupProtocolListDirectory::Flags_OldVersion | + BackupProtocolListDirectory::Flags_Deleted, + &flagsOut); + + if (fileId == 0) + { + // error already reported + return; + } + + BackupStoreFilenameClear fn(fileName); + + // Delete it on the store + try + { + // Delete object + if(flagsOut & BackupProtocolListDirectory::Flags_File) + { + mrConnection.QueryDeleteFile(parentId, fn); + } + else + { + mrConnection.QueryDeleteDirectory(fileId); + } + } + catch (BoxException &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(std::exception &e) + { + BOX_ERROR("Failed to delete object: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Failed to delete object: unknown error"); + } +} diff --git a/lib/bbackupquery/BackupQueries.h b/lib/bbackupquery/BackupQueries.h new file mode 100644 index 00000000..96df34f5 --- /dev/null +++ b/lib/bbackupquery/BackupQueries.h @@ -0,0 +1,440 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupQueries.h +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPQUERIES__H +#define BACKUPQUERIES__H + +#include <iostream> +#include <string> +#include <vector> + +#include "BoxTime.h" +#include "BoxBackupCompareParams.h" +#include "BackupStoreDirectory.h" + +class BackupProtocolCallable; +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 +// Name: BackupQueries +// Purpose: Perform various queries on the backup store server. +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +class BackupQueries +{ +public: + BackupQueries(BackupProtocolCallable &rConnection, + const Configuration &rConfiguration, + bool readWrite); + ~BackupQueries(); +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(); } + bool IsFailed() { return mFailed; } + }; + + void DoCommand(ParsedCommand& rCommand); + + // Ready to stop? + bool Stop() {return mQuitNow;} + + // Return code? + int GetReturnCode() {return mReturnCode;} + + 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); + void CommandGet(std::vector<std::string> args, const bool *opts); + void CommandCompare(const std::vector<std::string> &args, const bool *opts); + void CommandRestore(const std::vector<std::string> &args, const bool *opts); + void CommandUndelete(const std::vector<std::string> &args, const bool *opts); + void CommandDelete(const std::vector<std::string> &args, + const bool *opts); + void CommandUsage(const bool *opts); + void CommandUsageDisplayEntry(const char *Name, int64_t Size, + int64_t HardLimit, int32_t BlockSize, bool MachineReadable); + void CommandHelp(const std::vector<std::string> &args); + + class CompareParams : public BoxBackupCompareParams + { + public: + CompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime); + + bool mQuietCompare; + int mDifferences; + int mDifferencesExplainedByModTime; + int mUncheckedFiles; + int mExcludedDirs; + int mExcludedFiles; + + std::string ConvertForConsole(const std::string& rUtf8String) + { + #ifdef WIN32 + std::string output; + + if(!ConvertUtf8ToConsole(rUtf8String.c_str(), output)) + { + BOX_WARNING("Character set conversion failed " + "on string: " << rUtf8String); + return rUtf8String; + } + + return output; + #else + return rUtf8String; + #endif + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "does not exist, but remote directory does."); + mDifferences ++; + } + + virtual void NotifyLocalDirAccessFailed( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_LOG_SYS_WARNING("Failed to access local directory " + "'" << ConvertForConsole(rLocalPath) << "'"); + mUncheckedFiles ++; + } + + virtual void NotifyStoreDirMissingAttributes( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Store directory '" << + ConvertForConsole(rRemotePath) << "' " + "doesn't have attributes."); + } + + virtual void NotifyRemoteFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "exists, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "does not."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the file above was modified after " + "the last sync time -- might be " + "reason for difference)"); + } + } + + virtual void NotifyLocalFileMissing( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Remote file '" << + ConvertForConsole(rRemotePath) << "' " + "exists, but local file '" << + ConvertForConsole(rLocalPath) << "' does not."); + mDifferences ++; + } + + virtual void NotifyExcludedFileNotDeleted( + const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "is excluded, but remote file '" << + ConvertForConsole(rRemotePath) << "' " + "still exists."); + mDifferences ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + BoxException& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what() << " (" << + rException.GetType() << "/" << + rException.GetSubType() << ")"); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + std::exception& rException) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath) << "': " << + rException.what()); + mUncheckedFiles ++; + } + + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) + { + BOX_ERROR("Failed to download remote file '" << + ConvertForConsole(rRemotePath)); + 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) + { + mExcludedFiles ++; + } + + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) + { + mExcludedDirs ++; + } + + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_INFO("Comparing directory: " << rLocalPath); + } + + virtual void NotifyDirCompared( + const std::string& rLocalPath, + const std::string& rRemotePath, + bool HasDifferentAttributes, + bool modifiedAfterLastSync) + { + if(HasDifferentAttributes) + { + BOX_WARNING("Local directory '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store directory '" << + ConvertForConsole(rRemotePath) << "'."); + mDifferences ++; + + if(modifiedAfterLastSync) + { + mDifferencesExplainedByModTime ++; + BOX_INFO("(the directory above was " + "modified after the last sync " + "time -- might be reason for " + "difference)"); + } + } + } + + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) + { + BOX_TRACE("Comparing file: " << rLocalPath); + } + + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool ModifiedAfterLastSync, bool NewAttributesApplied) + { + int NewDifferences = 0; + + if(HasDifferentAttributes) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different attributes to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentContents) + { + BOX_WARNING("Local file '" << + ConvertForConsole(rLocalPath) << "' " + "has different contents to " + "store file '" << + ConvertForConsole(rRemotePath) << "'."); + NewDifferences ++; + } + + if(HasDifferentAttributes || HasDifferentContents) + { + if(ModifiedAfterLastSync) + { + mDifferencesExplainedByModTime += + NewDifferences; + BOX_INFO("(the file above was modified " + "after the last sync time -- " + "might be reason for difference)"); + } + else if(NewAttributesApplied) + { + BOX_INFO("(the file above has had new " + "attributes applied)\n"); + } + } + + mDifferences += NewDifferences; + } + }; + void CompareLocation(const std::string &rLocation, + BoxBackupCompareParams &rParams); + void Compare(const std::string &rStoreDir, + 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: + typedef enum { + Command_OK = 0, + Compare_Same = 1, + Compare_Different, + Compare_Error, + Command_Error, + } Type; + }; + + // 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); + std::string GetCurrentDirectoryName(); + void SetReturnCode(int code) {mReturnCode = code;} + +private: + bool mReadWrite; + BackupProtocolCallable &mrConnection; + const Configuration &mrConfiguration; + bool mQuitNow; + std::vector<std::pair<std::string, int64_t> > mDirStack; + bool mRunningAsRoot; + bool mWarnedAboutOwnerAttributes; + int mReturnCode; +}; + +typedef std::vector<std::string> (*CompletionHandler) + (BackupQueries::ParsedCommand& rCommand, const std::string& prefix, + BackupProtocolCallable& rProtocol, const Configuration& rConfig, + BackupQueries& rQueries); + +std::vector<std::string> CompleteCommand(BackupQueries::ParsedCommand& rCommand, + const std::string& prefix, BackupProtocolCallable& rProtocol, + const Configuration& rConfig, BackupQueries& rQueries); +std::vector<std::string> CompleteOptions(BackupQueries::ParsedCommand& rCommand, + const std::string& prefix, BackupProtocolCallable& 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/lib/bbackupquery/BoxBackupCompareParams.h b/lib/bbackupquery/BoxBackupCompareParams.h new file mode 100644 index 00000000..655df947 --- /dev/null +++ b/lib/bbackupquery/BoxBackupCompareParams.h @@ -0,0 +1,112 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxBackupCompareParams.h +// Purpose: Parameters and notifiers for a compare operation +// Created: 2008/12/30 +// +// -------------------------------------------------------------------------- + +#ifndef BOXBACKUPCOMPAREPARAMS__H +#define BOXBACKUPCOMPAREPARAMS__H + +#include <memory> +#include <string> + +#include "BoxTime.h" +#include "ExcludeList.h" +#include "BackupClientMakeExcludeList.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: BoxBackupCompareParams +// Purpose: Parameters and notifiers for a compare operation +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +class BoxBackupCompareParams +{ +private: + std::auto_ptr<const ExcludeList> mapExcludeFiles, mapExcludeDirs; + bool mQuickCompare; + bool mIgnoreExcludes; + bool mIgnoreAttributes; + box_time_t mLatestFileUploadTime; + +public: + BoxBackupCompareParams(bool QuickCompare, bool IgnoreExcludes, + bool IgnoreAttributes, box_time_t LatestFileUploadTime) + : mQuickCompare(QuickCompare), + mIgnoreExcludes(IgnoreExcludes), + mIgnoreAttributes(IgnoreAttributes), + mLatestFileUploadTime(LatestFileUploadTime) + { } + + virtual ~BoxBackupCompareParams() { } + + bool QuickCompare() { return mQuickCompare; } + bool IgnoreExcludes() { return mIgnoreExcludes; } + bool IgnoreAttributes() { return mIgnoreAttributes; } + box_time_t LatestFileUploadTime() { return mLatestFileUploadTime; } + + void LoadExcludeLists(const Configuration& rLoc) + { + mapExcludeFiles.reset(BackupClientMakeExcludeList_Files(rLoc)); + mapExcludeDirs.reset(BackupClientMakeExcludeList_Dirs(rLoc)); + } + bool IsExcludedFile(const std::string& rLocalPath) + { + if (!mapExcludeFiles.get()) return false; + return mapExcludeFiles->IsExcluded(rLocalPath); + } + bool IsExcludedDir(const std::string& rLocalPath) + { + if (!mapExcludeDirs.get()) return false; + return mapExcludeDirs->IsExcluded(rLocalPath); + } + + virtual void NotifyLocalDirMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyLocalDirAccessFailed(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyStoreDirMissingAttributes(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyRemoteFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath, + bool modifiedAfterLastSync) = 0; + virtual void NotifyLocalFileMissing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedFileNotDeleted(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + 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; + virtual void NotifyDownloadFailed(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes) = 0; + virtual void NotifyExcludedFile(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyExcludedDir(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyDirCompared(const std::string& rLocalPath, + const std::string& rRemotePath, bool HasDifferentAttributes, + bool modifiedAfterLastSync) = 0; + virtual void NotifyFileComparing(const std::string& rLocalPath, + const std::string& rRemotePath) = 0; + virtual void NotifyFileCompared(const std::string& rLocalPath, + const std::string& rRemotePath, int64_t NumBytes, + bool HasDifferentAttributes, bool HasDifferentContents, + bool modifiedAfterLastSync, bool newAttributesApplied) = 0; +}; + +#endif // BOXBACKUPCOMPAREPARAMS__H diff --git a/lib/bbackupquery/CommandCompletion.cpp b/lib/bbackupquery/CommandCompletion.cpp new file mode 100644 index 00000000..761fc97e --- /dev/null +++ b/lib/bbackupquery/CommandCompletion.cpp @@ -0,0 +1,604 @@ +// -------------------------------------------------------------------------- +// +// 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, \ + BackupProtocolCallable& 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, BackupProtocolCallable& 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", "adDFhiIorRsStTU", 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? You can't have options if there's no + // command before them, so treat the options as a command (which + // doesn't exist, so it will fail to parse) in that case. + else if(currentArg.empty() && *c == '-' && !mCmdElements.empty()) + { + 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/lib/bbackupquery/Makefile.extra b/lib/bbackupquery/Makefile.extra new file mode 100644 index 00000000..e1049b6d --- /dev/null +++ b/lib/bbackupquery/Makefile.extra @@ -0,0 +1,6 @@ + +# AUTOGEN SEEDING +autogen_Documentation.cpp: makedocumentation.pl documentation.txt + $(_PERL) makedocumentation.pl + + diff --git a/lib/bbackupquery/documentation.txt b/lib/bbackupquery/documentation.txt new file mode 100644 index 00000000..b16a6f7c --- /dev/null +++ b/lib/bbackupquery/documentation.txt @@ -0,0 +1,194 @@ + +bbackupquery utility -- examine store, compare files, restore, etc. + +This file has markers for automatic help generation script -- '>' marks a start of a command/help topic, +and '<' marks the end of a section. + +Command line: +============= + +> bbackupquery [-q] [-c configfile] [commands ...] + + -q -- quiet, no information prompts + -c -- specify another bbackupd configuation file + +The commands following the options are executed, then (if there was no quit +command) an interactive mode is entered. + +If a command contains a space, enclose it in quotes. Example + + bbackupquery "list testdir1" quit + +to list the contents of testdir1, and then exit without interactive mode. +< + +Commands: +========= + +All directory names relative to a "current" directory, or from root if they +start with '/'. The initial directory is always the root directory. + + +> ls [options] [directory-name] + + List contents of current directory, or specified directory. + + -R -- recursively list all files + -d -- list deleted files/directories + -o -- list old versions of files/directories + -I -- don't display object ID + -F -- don't display flags + -t -- show file modification time in local time + (and attr mod time if has the object has attributes, ~ separated) + -T -- show file modification time in GMT + -a -- show updated attribute instead of file modification time + -s -- show file size in blocks used on server + (only very approximate indication of size locally) + -h -- show file attributes hash + -D -- sort directories together with files (not dirs first) + -i -- sort by object ID (the old default) + -S -- sort by object size in blocks + -U -- don't sort the results (new default is to sort by name) + +list can be used as an alias. +< + +> list + + Alias for 'ls'. Type 'help ls' for options. +< + +> cd [options] <directory-name> + + Change directory + + -d -- consider deleted directories for traversal + -o -- consider old versions of directories for traversal + (this option should never be useful in a correctly formed store) +< + +> pwd + + Print current directory, always root relative. +< + +> lcd <local-directory-name> + + Change local directory. + + Type "sh ls" to list the contents. +< + +> sh <shell command> + + All of the parameters after the "sh" are run as a shell command. + + For example, to list the contents of the location directory, type "sh ls" +< + +> get <object-filename> [<local-filename>] +get -i <object-id> <local-filename> + + Gets a file from the store. Object is specified as the filename within + the current directory, and local filename is optional. Ignores old and + deleted files when searching the directory for the file to retrieve. + + To get an old or deleted file, use the -i option and select the object + as a hex object ID (first column in listing). The local filename must + be specified. +< + +> compare -a +compare -l <location-name> +compare <store-dir-name> <local-dir-name> + + Compares the (current) data on the store with the data on the disc. + All the data will be downloaded -- this is potentially a very long + operation. + + -a -- compare all locations + -l -- compare one backup location as specified in the configuration file. + -c -- set return code + -q -- quick compare. Only checks file contents against checksums, + doesn't do a full download + -A -- ignore attribute differences + -E -- ignore exclusion settings + + Comparing with the root directory is an error, use -a option instead. + + If -c is set, then the return code (if quit is the next command) will be + 1 Comparison was exact + 2 Differences were found + 3 An error occured + This can be used for automated tests. +< + +> 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). 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. + + -d -- restore a deleted directory or deleted files inside + -r -- resume an interrupted restoration + -i -- directory name is actually an ID + -f -- force restore to continue if errors are encountered + + If a restore operation is interrupted for any reason, it can be restarted + using the -r switch. Restore progress information is saved in a file at + regular intervals during the restore operation to allow restarts. +< + +> getobject <object-id> <local-filename> + + Gets the object specified by the object id (in hex) and stores the raw + contents in the local file specified. + + This is only useful for debugging as it does not decode files from the + stored format, which is encrypted and compressed. +< + +> usage [-m] + + Show space used on the server for this account. + + -m -- display the output in machine-readable form + + Used: Total amount of space used on the server. + Old files: Space used by old files + Deleted files: Space used by deleted files + Directories: Space used by the directory structure. + + When Used exceeds the soft limit, the server will start to remove old and + deleted files until the usage drops below the soft limit. + + After a while, you would expect to see the usage stay at just below the + soft limit. You only need more space if the space used by old and deleted + files is near zero. +< + +> undelete <directory-name> +undelete -i <object-id> + + Removes the deleted flag from the specified directory name (in the + current directory) or hex object ID. Be careful not to use this + command where a directory already exists with the same name which is + not marked as deleted. +< + +> delete <file-name> + + Sets the deleted flag on the specified file name (in the current + directory, or with a relative path). +< + +> quit + + End session and exit. +< + + diff --git a/lib/bbackupquery/makedocumentation.pl.in b/lib/bbackupquery/makedocumentation.pl.in new file mode 100755 index 00000000..530c4ff6 --- /dev/null +++ b/lib/bbackupquery/makedocumentation.pl.in @@ -0,0 +1,75 @@ +#!@PERL@ +use strict; + +print "Creating built-in documentation for bbackupquery...\n"; + +open DOC,"documentation.txt" or die "Can't open documentation.txt file"; +my $section; +my %help; +my @in_order; + +while(<DOC>) +{ + if(m/\A>\s+(\w+)/) + { + $section = $1; + m/\A>\s+(.+)\Z/; + $help{$section} = $1."\n"; + push @in_order,$section; + } + elsif(m/\A</) + { + $section = ''; + } + elsif($section ne '') + { + $help{$section} .= $_; + } +} + +close DOC; + +open OUT,">autogen_Documentation.cpp" or die "Can't open output file for writing"; + +print OUT <<__E; +// +// Automatically generated file, do not edit. +// + +#include "Box.h" + +#include "MemLeakFindOn.h" + +const char *help_commands[] = +{ +__E + +for(@in_order) +{ + print OUT qq:\t"$_",\n:; +} + +print OUT <<__E; + 0 +}; + +const char *help_text[] = +{ +__E + +for(@in_order) +{ + my $t = $help{$_}; + $t =~ s/\t/ /g; + $t =~ s/\n/\\n/g; + $t =~ s/"/\\"/g; + print OUT qq:\t"$t",\n:; +} + +print OUT <<__E; + 0 +}; + +__E + +close OUT; diff --git a/lib/bbstored/BBStoreDHousekeeping.cpp b/lib/bbstored/BBStoreDHousekeeping.cpp new file mode 100644 index 00000000..86d6409c --- /dev/null +++ b/lib/bbstored/BBStoreDHousekeeping.cpp @@ -0,0 +1,261 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BBStoreDHousekeeping.cpp +// Purpose: Implementation of housekeeping functions for bbstored +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> + +#include "BackupStoreDaemon.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "HousekeepStoreAccount.h" +#include "BoxTime.h" +#include "Configuration.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::HousekeepingProcess() +// Purpose: Do housekeeping +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::HousekeepingInit() +{ + + mLastHousekeepingRun = 0; +} + +void BackupStoreDaemon::HousekeepingProcess() +{ + HousekeepingInit(); + + // Get the time between housekeeping runs + const Configuration &rconfig(GetConfiguration()); + int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); + + while(!StopRun()) + { + RunHousekeepingIfNeeded(); + + // Stop early? + if(StopRun()) + { + break; + } + + // Calculate how long should wait before doing the next + // housekeeping run + int64_t timeNow = GetCurrentBoxTime(); + time_t secondsToGo = BoxTimeToSeconds( + (mLastHousekeepingRun + housekeepingInterval) - + timeNow); + if(secondsToGo < 1) secondsToGo = 1; + if(secondsToGo > 60) secondsToGo = 60; + int32_t millisecondsToGo = ((int)secondsToGo) * 1000; + + // Check to see if there's any message pending + CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); + } +} + +void BackupStoreDaemon::RunHousekeepingIfNeeded() +{ + // Get the time between housekeeping runs + const Configuration &rconfig(GetConfiguration()); + int64_t housekeepingInterval = SecondsToBoxTime(rconfig.GetKeyValueInt("TimeBetweenHousekeeping")); + + // Time now + int64_t timeNow = GetCurrentBoxTime(); + + // 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; + BOX_INFO("Starting housekeeping"); + + // Get the list of accounts + std::vector<int32_t> accounts; + if(mpAccountDatabase) + { + mpAccountDatabase->GetAllAccountIDs(accounts); + } + + SetProcessTitle("housekeeping, active"); + + // Check them all + for(std::vector<int32_t>::const_iterator i = accounts.begin(); i != accounts.end(); ++i) + { + try + { + 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 + mpAccounts->GetAccountRoot(*i, rootDir, discSet); + + // 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) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what() << " (" << + e.GetType() << "/" << e.GetSubType() << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(*i) << " threw exception, " + "aborting run for this account: " + "unknown exception"); + } + + int64_t timeNow = GetCurrentBoxTime(); + time_t secondsToGo = BoxTimeToSeconds( + (mLastHousekeepingRun + housekeepingInterval) - + timeNow); + if(secondsToGo < 1) secondsToGo = 1; + if(secondsToGo > 60) secondsToGo = 60; + int32_t millisecondsToGo = ((int)secondsToGo) * 1000; + + // Check to see if there's any message pending + CheckForInterProcessMsg(0 /* no account */, millisecondsToGo); + + // Stop early? + if(StopRun()) + { + break; + } + } + + BOX_INFO("Finished housekeeping"); + + // Placed here for accuracy, if StopRun() is true, for example. + SetProcessTitle("housekeeping, idle"); +} + +void BackupStoreDaemon::OnIdle() +{ + if (!IsSingleProcess()) + { + return; + } + + if (!mHousekeepingInited) + { + HousekeepingInit(); + mHousekeepingInited = true; + } + + RunHousekeepingIfNeeded(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::CheckForInterProcessMsg(int, int) +// Purpose: Process a message, returning true if the housekeeping process +// should abort for the specified account. +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +bool BackupStoreDaemon::CheckForInterProcessMsg(int AccountNum, int MaximumWaitTime) +{ + if(!mInterProcessCommsSocket.IsOpened()) + { + return false; + } + + // First, check to see if it's EOF -- this means something has gone wrong, and the housekeeping should terminate. + if(mInterProcessComms.IsEOF()) + { + SetTerminateWanted(); + return true; + } + + // Get a line, and process the message + std::string line; + if(mInterProcessComms.GetLine(line, false /* no pre-processing */, MaximumWaitTime)) + { + BOX_TRACE("Housekeeping received command '" << line << + "' over interprocess comms"); + + int account = 0; + + if(line == "h") + { + // HUP signal received by main process + SetReloadConfigWanted(); + return true; + } + else if(line == "t") + { + // Terminate signal received by main process + SetTerminateWanted(); + return true; + } + else if(sscanf(line.c_str(), "r%x", &account) == 1) + { + // Main process is trying to lock an account -- are we processing it? + if(account == AccountNum) + { + // Yes! -- need to stop now so when it retries to get the lock, it will succeed + BOX_INFO("Housekeeping on account " << + BOX_FORMAT_ACCOUNT(AccountNum) << + "giving way to client connection"); + return true; + } + } + } + + return false; +} + + diff --git a/lib/bbstored/BackupStoreDaemon.cpp b/lib/bbstored/BackupStoreDaemon.cpp new file mode 100644 index 00000000..8fddf125 --- /dev/null +++ b/lib/bbstored/BackupStoreDaemon.cpp @@ -0,0 +1,377 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreDaemon.cpp +// Purpose: Backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#ifdef HAVE_SYSLOG_H + #include <syslog.h> +#endif + +#include "BackupStoreContext.h" +#include "BackupStoreDaemon.h" +#include "BackupStoreConfigVerify.h" +#include "autogen_BackupProtocol.h" +#include "RaidFileController.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "BannerText.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::BackupStoreDaemon() +// Purpose: Constructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreDaemon::BackupStoreDaemon() + : mpAccountDatabase(0), + mpAccounts(0), + mExtendedLogging(false), + mHaveForkedHousekeeping(false), + mIsHousekeepingProcess(false), + mHousekeepingInited(false), + mInterProcessComms(mInterProcessCommsSocket), + mpTestHook(NULL) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::~BackupStoreDaemon() +// Purpose: Destructor +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +BackupStoreDaemon::~BackupStoreDaemon() +{ + // Must delete this one before the database ... + if(mpAccounts != 0) + { + delete mpAccounts; + mpAccounts = 0; + } + // ... as the mpAccounts object has a reference to it + if(mpAccountDatabase != 0) + { + delete mpAccountDatabase; + mpAccountDatabase = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::DaemonName() +// Purpose: Name of daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +const char *BackupStoreDaemon::DaemonName() const +{ + return "bbstored"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::DaemonBanner() +// Purpose: Daemon banner +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string BackupStoreDaemon::DaemonBanner() const +{ + return BANNER_TEXT("Backup Store Server"); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::GetConfigVerify() +// Purpose: Configuration definition +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *BackupStoreDaemon::GetConfigVerify() const +{ + return &BackupConfigFileVerify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::SetupInInitialProcess() +// Purpose: Setup before we fork -- get raid file controller going +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::SetupInInitialProcess() +{ + const Configuration &config(GetConfiguration()); + + // Initialise the raid files controller + RaidFileController &rcontroller = RaidFileController::GetController(); + + std::string raidFileConfig; + + #ifdef WIN32 + if (!config.KeyExists("RaidFileConf")) + { + raidFileConfig = BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE; + } + else + { + raidFileConfig = config.GetKeyValue("RaidFileConf"); + } + #else + raidFileConfig = config.GetKeyValue("RaidFileConf"); + #endif + + rcontroller.Initialise(raidFileConfig); + + // Load the account database + std::auto_ptr<BackupStoreAccountDatabase> pdb(BackupStoreAccountDatabase::Read(config.GetKeyValue("AccountDatabase").c_str())); + mpAccountDatabase = pdb.release(); + + // Create a accounts object + mpAccounts = new BackupStoreAccounts(*mpAccountDatabase); + + // Ready to go! +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Run() +// Purpose: Run shim for the store daemon -- read some config details +// Created: 2003/10/24 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Run() +{ + // Get extended logging flag + mExtendedLogging = false; + const Configuration &config(GetConfiguration()); + mExtendedLogging = config.GetKeyValueBool("ExtendedLogging"); + + // Fork off housekeeping daemon -- must only do this the first + // time Run() is called. Housekeeping runs synchronously on Win32 + // because IsSingleProcess() is always true + +#ifndef WIN32 + if(!IsSingleProcess() && !mHaveForkedHousekeeping) + { + // Open a socket pair for communication + int sv[2] = {-1,-1}; + if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) + { + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + int whichSocket = 0; + + // Fork + switch(::fork()) + { + case -1: + { + // Error + THROW_EXCEPTION(ServerException, ServerForkError) + } + break; + case 0: + { + // In child process + mIsHousekeepingProcess = true; + SetProcessTitle("housekeeping, idle"); + whichSocket = 1; + // Change the log name + ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); + // Log that housekeeping started + BOX_INFO("Housekeeping process started"); + // Ignore term and hup + // Parent will handle these and alert the + // child via the socket, don't want to + // randomly die! + ::signal(SIGHUP, SIG_IGN); + ::signal(SIGTERM, SIG_IGN); + } + break; + default: + { + // Parent process + whichSocket = 0; + } + break; + } + + // Mark that this has been, so -HUP doesn't try and do this again + mHaveForkedHousekeeping = true; + + // Attach the comms thing to the right socket, and close the other one + mInterProcessCommsSocket.Attach(sv[whichSocket]); + + if(::close(sv[(whichSocket == 0)?1:0]) != 0) + { + THROW_EXCEPTION(ServerException, SocketCloseError) + } + } +#endif // WIN32 + + if(mIsHousekeepingProcess) + { + // Housekeeping process -- do other stuff + HousekeepingProcess(); + } + else + { + // In server process -- use the base class to do the magic + ServerTLS<BOX_PORT_BBSTORED>::Run(); + + if (!mInterProcessCommsSocket.IsOpened()) + { + return; + } + + // Why did it stop? Tell the housekeeping process to do the same + if(IsReloadConfigWanted()) + { + mInterProcessCommsSocket.Write("h\n", 2); + } + + if(IsTerminateWanted()) + { + mInterProcessCommsSocket.Write("t\n", 2); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Connection(SocketStreamTLS &) +// Purpose: Handles a connection, by catching exceptions and +// delegating to Connection2 +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Connection(std::auto_ptr<SocketStreamTLS> apStream) +{ + try + { + Connection2(apStream); + } + catch(BoxException &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what() << " (" << e.GetType() << "/" << + e.GetSubType() << ")"); + } + catch(std::exception &e) + { + BOX_ERROR("Error in child process, terminating connection: " << + e.what()); + } + catch(...) + { + BOX_ERROR("Error in child process, terminating connection: " << + "unknown exception"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BackupStoreDaemon::Connection2(SocketStreamTLS &) +// Purpose: Handles a connection from bbackupd +// Created: 2006/11/12 +// +// -------------------------------------------------------------------------- +void BackupStoreDaemon::Connection2(std::auto_ptr<SocketStreamTLS> apStream) +{ + // Get the common name from the certificate + std::string clientCommonName(apStream->GetPeerCommonName()); + + // Log the name + BOX_INFO("Client certificate CN: " << clientCommonName); + + // Check it + int32_t id; + if(::sscanf(clientCommonName.c_str(), "BACKUP-%x", &id) != 1) + { + // Bad! Disconnect immediately + BOX_WARNING("Failed login: invalid client common name: " << + clientCommonName); + return; + } + + // Make ps listings clearer + std::ostringstream tag; + tag << "client=" << BOX_FORMAT_ACCOUNT(id); + SetProcessTitle(tag.str().c_str()); + Logging::Tagger tagWithClientID(tag.str()); + + // Create a context, using this ID + BackupStoreContext context(id, this, GetConnectionDetails()); + + if (mpTestHook) + { + context.SetTestHook(*mpTestHook); + } + + // See if the client has an account? + if(mpAccounts && mpAccounts->AccountExists(id)) + { + std::string root; + int discSet; + mpAccounts->GetAccountRoot(id, root, discSet); + context.SetClientHasAccount(root, discSet); + } + + // Handle a connection with the backup protocol + std::auto_ptr<SocketStream> apPlainStream(apStream); + BackupProtocolServer server(apPlainStream); + server.SetLogToSysLog(mExtendedLogging); + server.SetTimeout(BACKUP_STORE_TIMEOUT); + try + { + server.DoServer(context); + } + catch(...) + { + LogConnectionStats(id, context.GetAccountName(), server); + throw; + } + LogConnectionStats(id, context.GetAccountName(), server); + context.CleanUp(); +} + +void BackupStoreDaemon::LogConnectionStats(uint32_t accountId, + const std::string& accountName, const BackupProtocolServer &server) +{ + // Log the amount of data transferred + BOX_NOTICE("Connection statistics for " << + BOX_FORMAT_ACCOUNT(accountId) << " " + "(name=" << accountName << "):" + " IN=" << server.GetBytesRead() << + " OUT=" << server.GetBytesWritten() << + " NET_IN=" << (server.GetBytesRead() - server.GetBytesWritten()) << + " TOTAL=" << (server.GetBytesRead() + server.GetBytesWritten())); +} diff --git a/lib/bbstored/BackupStoreDaemon.h b/lib/bbstored/BackupStoreDaemon.h new file mode 100644 index 00000000..a2dab5e5 --- /dev/null +++ b/lib/bbstored/BackupStoreDaemon.h @@ -0,0 +1,101 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BackupStoreDaemon.h +// Purpose: Backup store daemon +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BACKUPSTOREDAEMON__H +#define BACKUPSTOREDAEMON__H + +#include "ServerTLS.h" +#include "BoxPortsAndFiles.h" +#include "BackupConstants.h" +#include "BackupStoreContext.h" +#include "HousekeepStoreAccount.h" +#include "IOStreamGetLine.h" + +class BackupStoreAccounts; +class BackupStoreAccountDatabase; + +// -------------------------------------------------------------------------- +// +// Class +// Name: BackupStoreDaemon +// Purpose: Backup store daemon implementation +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +class BackupStoreDaemon : public ServerTLS<BOX_PORT_BBSTORED>, + HousekeepingInterface, HousekeepingCallback +{ +public: + BackupStoreDaemon(); + ~BackupStoreDaemon(); +private: + BackupStoreDaemon(const BackupStoreDaemon &rToCopy); +public: + + // For BackupStoreContext to communicate with housekeeping process + void SendMessageToHousekeepingProcess(const void *Msg, int MsgLen) + { +#ifndef WIN32 + mInterProcessCommsSocket.Write(Msg, MsgLen); +#endif + } + +protected: + + virtual void SetupInInitialProcess(); + + virtual void Run(); + + virtual void Connection(std::auto_ptr<SocketStreamTLS> apStream); + void Connection2(std::auto_ptr<SocketStreamTLS> apStream); + + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + + const ConfigurationVerify *GetConfigVerify() const; + + // Housekeeping functions + void HousekeepingProcess(); + + void LogConnectionStats(uint32_t accountId, + const std::string& accountName, const BackupProtocolServer &server); + +public: + // HousekeepingInterface implementation + virtual bool CheckForInterProcessMsg(int AccountNum = 0, int MaximumWaitTime = 0); + void RunHousekeepingIfNeeded(); + +private: + BackupStoreAccountDatabase *mpAccountDatabase; + BackupStoreAccounts *mpAccounts; + bool mExtendedLogging; + bool mHaveForkedHousekeeping; + bool mIsHousekeepingProcess; + bool mHousekeepingInited; + + SocketStream mInterProcessCommsSocket; + IOStreamGetLine mInterProcessComms; + + virtual void OnIdle(); + void HousekeepingInit(); + int64_t mLastHousekeepingRun; + +public: + void SetTestHook(BackupStoreContext::TestHook& rTestHook) + { + mpTestHook = &rTestHook; + } + +private: + BackupStoreContext::TestHook* mpTestHook; +}; + + +#endif // BACKUPSTOREDAEMON__H + diff --git a/lib/common/Archive.h b/lib/common/Archive.h new file mode 100644 index 00000000..2b27b303 --- /dev/null +++ b/lib/common/Archive.h @@ -0,0 +1,230 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Archive.h +// Purpose: Backup daemon state archive +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- + +#ifndef ARCHIVE__H +#define ARCHIVE__H + +#include <vector> +#include <string> +#include <memory> + +#include "IOStream.h" +#include "Guards.h" + +#define ARCHIVE_GET_SIZE(hdr) (( ((uint8_t)((hdr)[0])) | ( ((uint8_t)((hdr)[1])) << 8)) >> 2) + +#define ARCHIVE_MAGIC_VALUE_RECURSE 0x4449525F +#define ARCHIVE_MAGIC_VALUE_NOOP 0x5449525F + +class Archive +{ +public: + Archive(IOStream &Stream, int Timeout) + : mrStream(Stream) + { + mTimeout = Timeout; + } +private: + // no copying + Archive(const Archive &); + Archive & operator=(const Archive &); +public: + ~Archive() + { + } + + // + // + // + void Write(bool Item) + { + Write((int) Item); + } + void WriteExact(uint32_t Item) { Write((int)Item); } + // TODO FIXME: use of "int" here is dangerous and deprecated. It can lead to + // incompatible serialisation on non-32-bit machines. Passing anything other + // than one of the specifically supported fixed size types should be forbidden. + void Write(int Item) + { + int32_t privItem = htonl(Item); + mrStream.Write(&privItem, sizeof(privItem), mTimeout); + } + void Write(int64_t Item) + { + int64_t privItem = box_hton64(Item); + mrStream.Write(&privItem, sizeof(privItem), mTimeout); + } + void WriteInt16(uint16_t Item) + { + uint16_t privItem = htons(Item); + mrStream.Write(&privItem, sizeof(privItem), mTimeout); + } + void WriteExact(uint64_t Item) { Write(Item); } + void Write(uint64_t Item) + { + uint64_t privItem = box_hton64(Item); + mrStream.Write(&privItem, sizeof(privItem), mTimeout); + } + void Write(uint8_t Item) + { + int privItem = Item; + Write(privItem); + } + void Write(const std::string &Item) + { + int size = Item.size(); + Write(size); + mrStream.Write(Item.c_str(), size, mTimeout); + } + // + // + // + void Read(bool &rItemOut) + { + int privItem; + Read(privItem); + + if(privItem) + { + rItemOut = true; + } + else + { + rItemOut = false; + } + } + 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); } + void Read(int &rItemOut) + { + int32_t privItem; + if(!mrStream.ReadFullBuffer(&privItem, sizeof(privItem), + 0 /* not interested in bytes read if this fails */, + mTimeout)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead); + } + rItemOut = ntohl(privItem); + } + void ReadFullBuffer(void* Buffer, size_t Size) + { + if(!mrStream.ReadFullBuffer(Buffer, Size, + 0 /* not interested in bytes read if this fails */, + mTimeout)) + { + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead); + } + } + void ReadIfPresent(int &rItemOut, int ValueIfNotPresent) + { + int32_t privItem; + int bytesRead; + if(mrStream.ReadFullBuffer(&privItem, sizeof(privItem), + &bytesRead, mTimeout)) + { + rItemOut = ntohl(privItem); + } + else if(bytesRead == 0) + { + // item is simply not present + rItemOut = ValueIfNotPresent; + } + else + { + // bad number of remaining bytes + THROW_EXCEPTION(CommonException, ArchiveBlockIncompleteRead); + } + } + void Read(int64_t &rItemOut) + { + int64_t privItem; + ReadFullBuffer(&privItem, sizeof(privItem)); + rItemOut = box_ntoh64(privItem); + } + void ReadExact(uint64_t &rItemOut) { Read(rItemOut); } + void Read(uint64_t &rItemOut) + { + uint64_t privItem; + ReadFullBuffer(&privItem, sizeof(privItem)); + rItemOut = box_ntoh64(privItem); + } + void ReadInt16(uint16_t &rItemOut) + { + uint16_t privItem; + ReadFullBuffer(&privItem, sizeof(privItem)); + rItemOut = ntohs(privItem); + } + void Read(uint8_t &rItemOut) + { + int privItem; + Read(privItem); + rItemOut = privItem; + } + void ReadIfPresent(std::string &rItemOut, const std::string& ValueIfNotPresent) + { + ReadString(rItemOut, &ValueIfNotPresent); + } + void Read(std::string &rItemOut) + { + ReadString(rItemOut, NULL); + } +private: + void ReadString(std::string &rItemOut, const std::string* pValueIfNotPresent) + { + int size; + int bytesRead; + if(!mrStream.ReadFullBuffer(&size, sizeof(size), &bytesRead, mTimeout)) + { + if(bytesRead == 0 && pValueIfNotPresent != NULL) + { + // item is simply not present + rItemOut = *pValueIfNotPresent; + return; + } + else + { + // bad number of remaining bytes + THROW_EXCEPTION(CommonException, + ArchiveBlockIncompleteRead) + } + } + size = ntohl(size); + + // Assume most strings are relatively small + char buf[256]; + if(size < (int) sizeof(buf)) + { + // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us + ReadFullBuffer(buf, size); + // assign to this string, storing the header and the extra payload + rItemOut.assign(buf, size); + } + else + { + // Block of memory to hold it + MemoryBlockGuard<char*> dataB(size); + char *ppayload = dataB; + + // Fetch rest of pPayload, relying on the Protocol to error on stupidly large sizes for us + ReadFullBuffer(ppayload, size); + // assign to this string, storing the header and the extra pPayload + rItemOut.assign(ppayload, size); + } + } +private: + IOStream &mrStream; + int mTimeout; +}; + +#endif // ARCHIVE__H diff --git a/lib/common/BannerText.h b/lib/common/BannerText.h new file mode 100644 index 00000000..9ca0c11c --- /dev/null +++ b/lib/common/BannerText.h @@ -0,0 +1,22 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BannerText.h +// Purpose: Banner text for daemons and utilities +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef BANNERTEXT__H +#define BANNERTEXT__H + +#ifdef NEED_BOX_VERSION_H +# include "BoxVersion.h" +#endif + +#define BANNER_TEXT(UtilityName) \ + "Box " UtilityName " v" BOX_VERSION ", (c) Ben Summers and " \ + "contributors 2003-2014" + +#endif // BANNERTEXT__H + diff --git a/lib/common/BeginStructPackForWire.h b/lib/common/BeginStructPackForWire.h new file mode 100644 index 00000000..e73bb886 --- /dev/null +++ b/lib/common/BeginStructPackForWire.h @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BeginStructPackForWire.h +// Purpose: Begin structure packing for wire +// Created: 25/11/03 +// +// -------------------------------------------------------------------------- + +// No header guard -- this is intentional + +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS + +#pragma pack(1) + +#else + + logical error -- check BoxPlatform.h and including file + +#endif + + + diff --git a/lib/common/Box.h b/lib/common/Box.h new file mode 100644 index 00000000..8ce2a625 --- /dev/null +++ b/lib/common/Box.h @@ -0,0 +1,201 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Box.h +// Purpose: Main header file for the Box project +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef BOX__H +#define BOX__H + +// Use the same changes as gcc3 for gcc4 +#ifdef PLATFORM_GCC4 + #define PLATFORM_GCC3 +#endif + +#include "BoxPlatform.h" + +#include <memory> + +// 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 + +// Show backtraces on exceptions in release builds until further notice +// (they are only logged at TRACE level anyway) +#ifdef HAVE_EXECINFO_H + #define SHOW_BACKTRACE_ON_EXCEPTION +#endif + +#ifdef SHOW_BACKTRACE_ON_EXCEPTION + #include "Utils.h" + #define OPTIONAL_DO_BACKTRACE DumpStackBacktrace(); +#else + #define OPTIONAL_DO_BACKTRACE +#endif + +#include "CommonException.h" +#include "Logging.h" + +#ifndef BOX_RELEASE_BUILD + extern bool AssertFailuresToSyslog; + #define ASSERT_FAILS_TO_SYSLOG_ON {AssertFailuresToSyslog = true;} + void BoxDebugAssertFailed(const char *cond, const char *file, int line); + #define ASSERT(cond) \ + { \ + if(!(cond)) \ + { \ + BoxDebugAssertFailed(#cond, __FILE__, __LINE__); \ + THROW_EXCEPTION_MESSAGE(CommonException, \ + AssertFailed, #cond); \ + } \ + } + + // Note that syslog tracing is independent of BoxDebugTraceOn, + // but stdout tracing is not + extern bool BoxDebugTraceToSyslog; + #define TRACE_TO_SYSLOG(x) {BoxDebugTraceToSyslog = x;} + extern bool BoxDebugTraceToStdout; + #define TRACE_TO_STDOUT(x) {BoxDebugTraceToStdout = x;} + + extern bool BoxDebugTraceOn; + int BoxDebug_printf(const char *format, ...); + int BoxDebugTrace(const char *format, ...); + + #ifndef PLATFORM_DISABLE_MEM_LEAK_TESTING + #define BOX_MEMORY_LEAK_TESTING + #endif + + // Exception names + #define EXCEPTION_CODENAMES_EXTENDED +#else + #define ASSERT_FAILS_TO_SYSLOG_ON + #define ASSERT(cond) + + #define TRACE_TO_SYSLOG(x) {} + #define TRACE_TO_STDOUT(x) {} + + // Box Backup builds release get extra information for exception logging + #define EXCEPTION_CODENAMES_EXTENDED + #define EXCEPTION_CODENAMES_EXTENDED_WITH_DESCRIPTION +#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 + +#ifdef BOX_MEMORY_LEAK_TESTING + // Memory leak testing + #include "MemLeakFinder.h" + #define DEBUG_NEW new(__FILE__,__LINE__) + #define MEMLEAKFINDER_NOT_A_LEAK(x) memleakfinder_notaleak(x); + #define MEMLEAKFINDER_NO_LEAKS MemLeakSuppressionGuard _guard; + #define MEMLEAKFINDER_INIT memleakfinder_init(); + #define MEMLEAKFINDER_START {memleakfinder_global_enable = true;} + #define MEMLEAKFINDER_STOP {memleakfinder_global_enable = false;} +#else + #define DEBUG_NEW new + #define MEMLEAKFINDER_NOT_A_LEAK(x) + #define MEMLEAKFINDER_NO_LEAKS + #define MEMLEAKFINDER_INIT + #define MEMLEAKFINDER_START + #define MEMLEAKFINDER_STOP +#endif + +#define THROW_EXCEPTION(type, subtype) \ + { \ + if((!HideExceptionMessageGuard::ExceptionsHidden() \ + && !HideSpecificExceptionGuard::IsHidden( \ + type::ExceptionType, type::subtype))) \ + { \ + OPTIONAL_DO_BACKTRACE \ + BOX_WARNING("Exception thrown: " \ + #type "(" #subtype ") " \ + "at " __FILE__ "(" << __LINE__ << ")") \ + } \ + throw type(type::subtype); \ + } + +#define THROW_EXCEPTION_MESSAGE(type, subtype, message) \ + { \ + std::ostringstream _box_throw_line; \ + _box_throw_line << message; \ + if((!HideExceptionMessageGuard::ExceptionsHidden() \ + && !HideSpecificExceptionGuard::IsHidden( \ + type::ExceptionType, type::subtype))) \ + { \ + OPTIONAL_DO_BACKTRACE \ + BOX_WARNING("Exception thrown: " \ + #type "(" #subtype ") (" << \ + _box_throw_line.str() << \ + ") at " __FILE__ ":" << __LINE__) \ + } \ + throw type(type::subtype, _box_throw_line.str()); \ + } + +// extra macros for converting to network byte order +#ifdef HAVE_NETINET_IN_H + #include <netinet/in.h> +#endif + +// Always define a swap64 function, as it's useful. +inline uint64_t box_swap64(uint64_t x) +{ + return ((x & 0xff) << 56 | + (x & 0xff00LL) << 40 | + (x & 0xff0000LL) << 24 | + (x & 0xff000000LL) << 8 | + (x & 0xff00000000LL) >> 8 | + (x & 0xff0000000000LL) >> 24 | + (x & 0xff000000000000LL) >> 40 | + (x & 0xff00000000000000LL) >> 56); +} + +#ifdef WORDS_BIGENDIAN + #define box_hton64(x) (x) + #define box_ntoh64(x) (x) +#elif defined(HAVE_BSWAP64) + #ifdef HAVE_SYS_ENDIAN_H + #include <sys/endian.h> + #endif + #ifdef HAVE_ASM_BYTEORDER_H + #include <asm/byteorder.h> + #endif + + #define box_hton64(x) BSWAP64(x) + #define box_ntoh64(x) BSWAP64(x) +#else + #define box_hton64(x) box_swap64(x) + #define box_ntoh64(x) box_swap64(x) +#endif + +// overloaded auto-conversion functions +inline uint64_t hton(uint64_t in) { return box_hton64(in); } +inline uint32_t hton(uint32_t in) { return htonl(in); } +inline uint16_t hton(uint16_t in) { return htons(in); } +inline uint8_t hton(uint8_t in) { return in; } +inline int64_t hton(int64_t in) { return box_hton64(in); } +inline int32_t hton(int32_t in) { return htonl(in); } +inline int16_t hton(int16_t in) { return htons(in); } +inline int8_t hton(int8_t in) { return in; } +inline uint64_t ntoh(uint64_t in) { return box_ntoh64(in); } +inline uint32_t ntoh(uint32_t in) { return ntohl(in); } +inline uint16_t ntoh(uint16_t in) { return ntohs(in); } +inline uint8_t ntoh(uint8_t in) { return in; } +inline int64_t ntoh(int64_t in) { return box_ntoh64(in); } +inline int32_t ntoh(int32_t in) { return ntohl(in); } +inline int16_t ntoh(int16_t in) { return ntohs(in); } +inline int8_t ntoh(int8_t in) { return in; } + +#endif // BOX__H + diff --git a/lib/common/BoxConfig-MSVC.h b/lib/common/BoxConfig-MSVC.h new file mode 100644 index 00000000..82ab4997 --- /dev/null +++ b/lib/common/BoxConfig-MSVC.h @@ -0,0 +1,408 @@ +/* lib/common/BoxConfig.h. Generated by configure. */ +/* lib/common/BoxConfig.h.in. Generated from configure.ac by autoheader. */ +/* Hacked by hand to work for MSVC by Chris Wilson */ + +// using std::min/max +#define NOMINMAX + +/* Define to major version for BDB_VERSION */ +/* #undef BDB_VERSION_MAJOR */ + +/* Define to minor version for BDB_VERSION */ +/* #undef BDB_VERSION_MINOR */ + +/* Define to point version for BDB_VERSION */ +/* #undef BDB_VERSION_POINT */ + +/* Name of the 64 bit endian swapping function */ +/* #undef BSWAP64 */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +#define CLOSEDIR_VOID 1 + +/* Define to 1 if non-aligned int16 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT16 */ + +/* Define to 1 if non-aligned int32 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT32 */ + +/* Define to 1 if non-aligned int64 access will fail */ +/* #undef HAVE_ALIGNED_ONLY_INT64 */ + +/* Define to 1 if you have the <asm/byteorder.h> header file. */ +/* #undef HAVE_ASM_BYTEORDER_H */ + +/* Define to 1 if BSWAP64 is defined to the name of a valid 64 bit endian + swapping function */ +/* #undef HAVE_BSWAP64 */ + +/* Define to 1 if you have the <db.h> header file. */ +/* #undef HAVE_DB_H */ + +/* Define to 1 if you have the declaration of `F_SETLK', and to 0 if you + don't. */ +#define HAVE_DECL_F_SETLK 0 + +/* Define to 1 if you have the declaration of `INFTIM', and to 0 if you don't. + */ +#define HAVE_DECL_INFTIM 0 + +/* Define to 1 if you have the declaration of `O_EXLOCK', and to 0 if you + don't. */ +#define HAVE_DECL_O_EXLOCK 0 + +/* Define to 1 if you have the declaration of `SO_PEERCRED', and to 0 if you + don't. */ +#define HAVE_DECL_SO_PEERCRED 0 + +/* Define to 1 if you have the declaration of `XATTR_NOFOLLOW', and to 0 if + you don't. */ +#define HAVE_DECL_XATTR_NOFOLLOW 0 + +/* Define to 1 if you have the declaration of `O_BINARY', and to 0 if you + don't. */ +#define HAVE_DECL_O_BINARY 1 + +/* Define to 1 if #define of pragmas works */ +/* #undef HAVE_DEFINE_PRAGMA */ + +/* Define to 1 if you have the <dirent.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_DIRENT_H */ + +/* Define to 1 if you have the <editline/readline.h> header file. */ +/* #undef HAVE_EDITLINE_READLINE_H */ + +/* define if the compiler supports exceptions */ +#define HAVE_EXCEPTIONS + +/* Define to 1 if you have the <execinfo.h> header file. */ +/* #undef HAVE_EXECINFO_H */ + +/* Define to 1 if you have the <fcntl.h> header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `flock' function. */ +/* #undef HAVE_FLOCK */ + +/* Define to 1 if you have the `getmntent' function. */ +/* #undef HAVE_GETMNTENT */ + +/* Define to 1 if you have the `getpeereid' function. */ +/* #undef HAVE_GETPEEREID */ + +/* Define to 1 if you have the `getpid' function. */ +// #define HAVE_GETPID 1 + +/* Define to 1 if you have the `getxattr' function. */ +/* #undef HAVE_GETXATTR */ + +/* Define to 1 if you have the <history.h> header file. */ +/* #undef HAVE_HISTORY_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +// #define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `kqueue' function. */ +/* #undef HAVE_KQUEUE */ + +/* Define to 1 if you have the `lchown' function. */ +/* #undef HAVE_LCHOWN */ + +/* Define to 1 if you have the `lgetxattr' function. */ +/* #undef HAVE_LGETXATTR */ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#define HAVE_LIBCRYPTO 1 + +/* Define if you have a readline compatible library */ +/* #undef HAVE_LIBREADLINE */ + +/* Define to 1 if you have the `ssl' library (-lssl). */ +#define HAVE_LIBSSL 1 + +/* Define to 1 if you have the `z' library (-lz). */ +#define HAVE_LIBZ 1 + +/* Define to 1 if you have the `listxattr' function. */ +/* #undef HAVE_LISTXATTR */ + +/* Define to 1 if you have the `llistxattr' function. */ +/* #undef HAVE_LLISTXATTR */ + +/* Define to 1 if syscall lseek requires a dummy middle parameter */ +/* #undef HAVE_LSEEK_DUMMY_PARAM */ + +/* Define to 1 if you have the `lsetxattr' function. */ +/* #undef HAVE_LSETXATTR */ + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the <mntent.h> header file. */ +/* #undef HAVE_MNTENT_H */ + +/* Define to 1 if this platform supports mounts */ +/* #undef HAVE_MOUNTS */ + +/* define if the compiler implements namespaces */ +#define HAVE_NAMESPACES + +/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the <netinet/in.h> header file. */ +/* #undef HAVE_NETINET_IN_H */ + +/* Define to 1 if SSL is pre-0.9.7 */ +/* #undef HAVE_OLD_SSL */ + +/* Define to 1 if you have the <openssl/ssl.h> header file. */ +#define HAVE_OPENSSL_SSL_H 1 + +/* Define to 1 if you have the <process.h> header file. */ +#define HAVE_PROCESS_H 1 + +/* Define to 1 if you have the <pwd.h> header file. */ +/* #undef HAVE_PWD_H */ + +/* Define to 1 (and set RANDOM_DEVICE) if a random device is available */ +/* #undef HAVE_RANDOM_DEVICE */ + +/* Define to 1 if you have the <readline.h> header file. */ +/* #undef HAVE_READLINE_H */ + +/* Define if your readline library has add_history */ +/* #undef HAVE_READLINE_HISTORY */ + +/* Define to 1 if you have the <readline/history.h> header file. */ +/* #undef HAVE_READLINE_HISTORY_H */ + +/* Define to 1 if you have the <readline/readline.h> header file. */ +/* #undef HAVE_READLINE_READLINE_H */ + +/* Define to 1 if you have the <regex.h> header file. */ +/* #undef HAVE_REGEX_H */ +#define HAVE_PCREPOSIX_H 1 +#define HAVE_REGEX_SUPPORT 1 + +/* Define to 1 if you have the `setproctitle' function. */ +/* #undef HAVE_SETPROCTITLE */ +#define HAVE_SETPROCTITLE 1 +/* Define to 1 if you have the `setxattr' function. */ +/* #undef HAVE_SETXATTR */ + +/* Define to 1 if you have the <signal.h> header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if SSL is available */ +#define HAVE_SSL 1 + +/* Define to 1 if you have the `statfs' function. */ +/* #undef HAVE_STATFS */ + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if stdbool.h conforms to C99. */ +#define HAVE_STDBOOL_H 1 + +/* Define to 1 if you have the <stdint.h> header file. */ +// #define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if `d_type' is member of `struct dirent'. */ +/* #undef HAVE_STRUCT_DIRENT_D_TYPE */ + +/* Define to 1 if `mnt_dir' is member of `struct mntent'. */ +/* #undef HAVE_STRUCT_MNTENT_MNT_DIR */ + +/* Define to 1 if `mnt_mountp' is member of `struct mnttab'. */ +/* #undef HAVE_STRUCT_MNTTAB_MNT_MOUNTP */ + +/* Define to 1 if `sin_len' is member of `struct sockaddr_in'. */ +/* #undef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN */ + +/* Define to 1 if `f_mntonname' is member of `struct statfs'. */ +/* #undef HAVE_STRUCT_STATFS_F_MNTONNAME */ + +/* Define to 1 if `st_flags' is member of `struct stat'. */ +/* #undef HAVE_STRUCT_STAT_ST_FLAGS */ + +/* Define to 1 if `st_mtimespec' is member of `struct stat'. */ +/* #undef HAVE_STRUCT_STAT_ST_MTIMESPEC */ + +/* Define to 1 if you have the `syscall' function. */ +/* #undef HAVE_SYSCALL */ + +/* Define to 1 if you have the <syslog.h> header file. */ +/* #undef HAVE_SYSLOG_H */ + +/* Define to 1 if you have the <sys/dir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the <sys/endian.h> header file. */ +/* #undef HAVE_SYS_ENDIAN_H */ + +/* Define to 1 if you have the <sys/mnttab.h> header file. */ +/* #undef HAVE_SYS_MNTTAB_H */ + +/* Define to 1 if you have the <sys/mount.h> header file. */ +/* #undef HAVE_SYS_MOUNT_H */ + +/* Define to 1 if you have the <sys/ndir.h> header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the <sys/param.h> header file. */ +// #define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the <sys/socket.h> header file. */ +/* #undef HAVE_SYS_SOCKET_H */ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/syscall.h> header file. */ +/* #undef HAVE_SYS_SYSCALL_H */ + +/* Define to 1 if you have the <sys/time.h> header file. */ +// #define HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +// #define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <sys/wait.h> header file. */ +/* #undef HAVE_SYS_WAIT_H */ + +/* Define to 1 if you have the <sys/xattr.h> header file. */ +/* #undef HAVE_SYS_XATTR_H */ + +/* Define to 1 if you have the <time.h> header file. */ +#define HAVE_TIME_H 1 + +/* Define to 1 if the system has the type `uint16_t'. */ +#define HAVE_UINT16_T 1 + +/* Define to 1 if the system has the type `uint32_t'. */ +#define HAVE_UINT32_T 1 + +/* Define to 1 if the system has the type `uint64_t'. */ +#define HAVE_UINT64_T 1 + +/* Define to 1 if the system has the type `uint8_t'. */ +#define HAVE_UINT8_T 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +// #define HAVE_UNISTD_H 1 + +/* Define to 1 if the system has the type `u_int16_t'. */ +/* #undef HAVE_U_INT16_T */ + +/* Define to 1 if the system has the type `u_int32_t'. */ +/* #undef HAVE_U_INT32_T */ + +/* Define to 1 if the system has the type `u_int64_t'. */ +/* #undef HAVE_U_INT64_T */ + +/* Define to 1 if the system has the type `u_int8_t'. */ +/* #undef HAVE_U_INT8_T */ + +/* Define to 1 if struct dirent.d_type is valid */ +/* #undef HAVE_VALID_DIRENT_D_TYPE */ + +/* Define to 1 if the system has the type `_Bool'. */ +/* #undef HAVE__BOOL */ + +/* Define to 1 if you have the `__syscall' function. */ +/* #undef HAVE___SYSCALL */ + +/* Define to 1 if __syscall is available but needs a definition */ +/* #undef HAVE___SYSCALL_NEED_DEFN */ + +/* max value of long long calculated by configure */ +/* #undef LLONG_MAX */ + +/* min value of long long calculated by configure */ +/* #undef LLONG_MIN */ + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +/* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "box@fluffy.co.uk" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "Box Backup" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "Box Backup 0.11" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "box-backup" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.09" + +/* Define to the filename of the random device (and set HAVE_RANDOM_DEVICE) */ +/* #undef RANDOM_DEVICE */ + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* TMP directory name */ +#define TEMP_DIRECTORY_NAME "/tmp" + +/* Define to 1 if you can safely include both <sys/time.h> and <time.h>. */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to 1 if your <sys/time.h> declares `struct tm'. */ +/* #undef TM_IN_SYS_TIME */ + +/* Define to 1 if your processor stores words with the most significant byte + first (like Motorola and SPARC, unlike Intel and VAX). */ +/* #undef WORDS_BIGENDIAN */ + +/* Number of bits in a file offset, on hosts where this is settable. */ +/* #undef _FILE_OFFSET_BITS */ + +/* Define for large files, on AIX-style hosts. */ +/* #undef _LARGE_FILES */ + +/* Define to 1 if __USE_MALLOC is required work around STL memory leaks */ +/* #undef __USE_MALLOC */ + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to `int' if <sys/types.h> doesn't define. */ +#define gid_t int + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef mode_t */ + +/* Define to `long' if <sys/types.h> does not define. */ +/* #undef off_t */ + +/* Define to `int' if <sys/types.h> does not define. */ +/* #undef pid_t */ + +/* Define to `unsigned' if <sys/types.h> does not define. */ +/* #undef size_t */ + +/* Define to `int' if <sys/types.h> doesn't define. */ +#define uid_t int diff --git a/lib/common/BoxException.cpp b/lib/common/BoxException.cpp new file mode 100644 index 00000000..2503ca63 --- /dev/null +++ b/lib/common/BoxException.cpp @@ -0,0 +1,21 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxException.cpp +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BoxException.h" + +#include "MemLeakFindOn.h" + +BoxException::BoxException() +{ +} + +BoxException::~BoxException() throw () +{ +} diff --git a/lib/common/BoxException.h b/lib/common/BoxException.h new file mode 100644 index 00000000..361f04e8 --- /dev/null +++ b/lib/common/BoxException.h @@ -0,0 +1,39 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxException.h +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#ifndef BOXEXCEPTION__H +#define BOXEXCEPTION__H + +#include <exception> +#include <string> + +// -------------------------------------------------------------------------- +// +// Class +// Name: BoxException +// Purpose: Exception +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +class BoxException : public std::exception +{ +public: + BoxException(); + ~BoxException() throw (); + + virtual unsigned int GetType() const throw() = 0; + virtual unsigned int GetSubType() const throw() = 0; + virtual const std::string& GetMessage() const = 0; + +private: +}; + + +#endif // BOXEXCEPTION__H + diff --git a/lib/common/BoxPlatform.h b/lib/common/BoxPlatform.h new file mode 100644 index 00000000..35ad7a2c --- /dev/null +++ b/lib/common/BoxPlatform.h @@ -0,0 +1,160 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxPlatform.h +// Purpose: Specifies what each platform supports in more detail, and includes +// extra files to get basic support for types. +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- + +#ifndef BOXPLATFORM__H +#define BOXPLATFORM__H + +#ifdef WIN32 +#define DIRECTORY_SEPARATOR "\\" +#define DIRECTORY_SEPARATOR_ASCHAR '\\' +#else +#define DIRECTORY_SEPARATOR "/" +#define DIRECTORY_SEPARATOR_ASCHAR '/' +#endif + +#define PLATFORM_DEV_NULL "/dev/null" + +#ifdef _MSC_VER +#include "BoxConfig-MSVC.h" +#define NEED_BOX_VERSION_H +#else +#include "BoxConfig.h" +#endif + +#ifdef WIN32 + #ifdef __MSVCRT_VERSION__ + #if __MSVCRT_VERSION__ < 0x0601 + #error Must include Box.h before sys/types.h + #endif + #else + // need msvcrt version 6.1 or higher for _gmtime64() + // must define this before importing <sys/types.h> + #define __MSVCRT_VERSION__ 0x0601 + #endif +#endif + +#include "emu.h" + +#ifdef HAVE_SYS_TYPES_H + #include <sys/types.h> +#endif + +#ifdef HAVE_INTTYPES_H + #include <inttypes.h> +#else + #ifdef HAVE_STDINT_H + #include <stdint.h> + #endif +#endif + +// Slight hack; disable interception in raidfile test on Darwin and Windows +#if defined __APPLE__ || defined WIN32 + // TODO: Replace with autoconf test + #define PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE +#endif + +// Disable memory testing under Darwin, it just doesn't like it very much. +#ifdef __APPLE__ + // TODO: We really should get some decent leak detection code. + #define PLATFORM_DISABLE_MEM_LEAK_TESTING +#endif + +// Darwin also has a weird idea of permissions and dates on symlinks: +// perms are fixed at creation time by your umask, and dates can't be +// changed. This breaks unit tests if we try to compare these things. +// See: http://lists.apple.com/archives/darwin-kernel/2006/Dec/msg00057.html +#ifdef __APPLE__ + #define PLATFORM_DISABLE_SYMLINK_ATTRIB_COMPARE +#endif + +// Find out if credentials on UNIX sockets can be obtained +#ifdef HAVE_GETPEEREID + // +#elif HAVE_DECL_SO_PEERCRED + // +#elif defined HAVE_UCRED_H && HAVE_GETPEERUCRED + // +#else + #define PLATFORM_CANNOT_FIND_PEER_UID_OF_UNIX_SOCKET +#endif + +#ifdef HAVE_DEFINE_PRAGMA + // set packing to one bytes (can't use push/pop on gcc) + #define BEGIN_STRUCTURE_PACKING_FOR_WIRE #pragma pack(1) + + // Use default packing + #define END_STRUCTURE_PACKING_FOR_WIRE #pragma pack() +#else + #define STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#endif + +// Handle differing xattr APIs +#ifdef HAVE_SYS_XATTR_H + #if !defined(HAVE_LLISTXATTR) && defined(HAVE_LISTXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define llistxattr(a,b,c) listxattr(a,b,c,XATTR_NOFOLLOW) + #endif + #if !defined(HAVE_LGETXATTR) && defined(HAVE_GETXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define lgetxattr(a,b,c,d) getxattr(a,b,c,d,0,XATTR_NOFOLLOW) + #endif + #if !defined(HAVE_LSETXATTR) && defined(HAVE_SETXATTR) && HAVE_DECL_XATTR_NOFOLLOW + #define lsetxattr(a,b,c,d,e) setxattr(a,b,c,d,0,(e)|XATTR_NOFOLLOW) + #endif +#endif + +#if !HAVE_DECL_INFTIM + #define INFTIM -1 +#endif + +// 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 + +#ifndef O_BINARY + #define O_BINARY 0 +#endif + +#ifdef WIN32 + typedef uint64_t InodeRefType; +#else + typedef ino_t InodeRefType; +#endif + +#ifdef WIN32 + #define WIN32_LEAN_AND_MEAN +#endif + +#ifdef WIN32 + #define INVALID_FILE INVALID_HANDLE_VALUE + typedef HANDLE tOSFileHandle; +#else + #define INVALID_FILE -1 + typedef int tOSFileHandle; +#endif + +// Solaris has no dirfd(x) macro or function, and we need one for +// intercept tests. We cannot define macros with arguments directly +// using AC_DEFINE, so do it here instead of in configure.ac. + +#if ! defined PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE && ! HAVE_DECL_DIRFD + #ifdef HAVE_DIR_D_FD + #define dirfd(x) (x)->d_fd + #elif defined HAVE_DIR_DD_FD + #define dirfd(x) (x)->dd_fd + #else + #error No way to get file descriptor from DIR structure + #endif +#endif + +#endif // BOXPLATFORM__H diff --git a/lib/common/BoxPortsAndFiles.h.in b/lib/common/BoxPortsAndFiles.h.in new file mode 100644 index 00000000..047a828f --- /dev/null +++ b/lib/common/BoxPortsAndFiles.h.in @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxPortsAndFiles.h +// Purpose: Central list of which tcp/ip ports and hardcoded file locations +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef BOXPORTSANDFILES__H +#define BOXPORTSANDFILES__H + +#define BOX_PORT_BASE 2200 + + +// Backup store daemon +#define BOX_PORT_BBSTORED (BOX_PORT_BASE+1) +#define BOX_PORT_BBSTORED_TEST 22011 + +// directory within the RAIDFILE root for the backup store daemon +#define BOX_RAIDFILE_ROOT_BBSTORED "backup" + +// configuration file paths +#ifdef WIN32 + // no default config file path, use these macros to call + // GetDefaultConfigFilePath() instead. + + #define BOX_GET_DEFAULT_BBACKUPD_CONFIG_FILE \ + GetDefaultConfigFilePath("bbackupd.conf").c_str() + #define BOX_GET_DEFAULT_RAIDFILE_CONFIG_FILE \ + GetDefaultConfigFilePath("raidfile.conf").c_str() + #define BOX_GET_DEFAULT_BBSTORED_CONFIG_FILE \ + GetDefaultConfigFilePath("bbstored.conf").c_str() +#else + #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") +#endif + +#endif // BOXPORTSANDFILES__H + diff --git a/lib/common/BoxTime.cpp b/lib/common/BoxTime.cpp new file mode 100644 index 00000000..78269def --- /dev/null +++ b/lib/common/BoxTime.cpp @@ -0,0 +1,150 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTime.cpp +// Purpose: Time for the box +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#ifdef HAVE_TIME_H + #include <time.h> +#endif + +#include <errno.h> +#include <string.h> + +#include "BoxTime.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: GetCurrentBoxTime() +// Purpose: Returns the current time as a box time. +// (1 sec precision, or better if supported by system) +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- +box_time_t GetCurrentBoxTime() +{ + #ifdef HAVE_GETTIMEOFDAY + struct timeval tv; + if (gettimeofday(&tv, NULL) != 0) + { + BOX_LOG_SYS_ERROR("Failed to gettimeofday(), " + "dropping precision"); + } + else + { + box_time_t timeNow = (tv.tv_sec * MICRO_SEC_IN_SEC_LL) + + tv.tv_usec; + return timeNow; + } + #endif + + return SecondsToBoxTime(time(0)); +} + +std::string FormatTime(box_time_t time, bool includeDate, bool showMicros) +{ + std::ostringstream buf; + + time_t seconds = BoxTimeToSeconds(time); + int micros = BoxTimeToMicroSeconds(time) % MICRO_SEC_IN_SEC; + + struct tm tm_now, *tm_ptr = &tm_now; + + #ifdef WIN32 + if ((tm_ptr = localtime(&seconds)) != NULL) + #else + if (localtime_r(&seconds, &tm_now) != NULL) + #endif + { + buf << std::setfill('0'); + + if (includeDate) + { + buf << std::setw(4) << (tm_ptr->tm_year + 1900) << "-" << + std::setw(2) << (tm_ptr->tm_mon + 1) << "-" << + std::setw(2) << (tm_ptr->tm_mday) << " "; + } + + buf << std::setw(2) << tm_ptr->tm_hour << ":" << + std::setw(2) << tm_ptr->tm_min << ":" << + std::setw(2) << tm_ptr->tm_sec; + + if (showMicros) + { + buf << "." << std::setw(3) << (int)(micros / 1000); + } + } + else + { + buf << strerror(errno); + } + + return buf.str(); +} + +// -------------------------------------------------------------------------- +// +// 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 " << BOX_FORMAT_MICROSECONDS(duration)); + } + +#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) * 1000; + + box_time_t start_time = GetCurrentBoxTime(); + + 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"); + } + + box_time_t sleep_time = GetCurrentBoxTime() - start_time; + BOX_TRACE("Actually slept for " << BOX_FORMAT_MICROSECONDS(sleep_time) << + ", was aiming for " << BOX_FORMAT_MICROSECONDS(duration)); +#endif +} + diff --git a/lib/common/BoxTime.h b/lib/common/BoxTime.h new file mode 100644 index 00000000..6afaada3 --- /dev/null +++ b/lib/common/BoxTime.h @@ -0,0 +1,53 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTime.h +// Purpose: How time is represented +// Created: 2003/10/08 +// +// -------------------------------------------------------------------------- + +#ifndef BOXTIME__H +#define BOXTIME__H + +// Time is presented as a signed 64 bit integer, in microseconds +typedef int64_t box_time_t; + +#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) +#define MICRO_SEC_IN_MILLI_SEC (1000) +#define MILLI_SEC_IN_SEC (1000) +#define MILLI_SEC_IN_SEC_LL (1000LL) + +box_time_t GetCurrentBoxTime(); + +inline box_time_t SecondsToBoxTime(time_t Seconds) +{ + return ((box_time_t)Seconds * MICRO_SEC_IN_SEC_LL); +} +inline uint64_t MilliSecondsToBoxTime(int64_t milliseconds) +{ + return ((box_time_t)milliseconds * 1000); +} +inline time_t BoxTimeToSeconds(box_time_t Time) +{ + return Time / MICRO_SEC_IN_SEC_LL; +} +inline uint64_t BoxTimeToMilliSeconds(box_time_t Time) +{ + return Time / MILLI_SEC_IN_SEC_LL; +} +inline uint64_t BoxTimeToMicroSeconds(box_time_t Time) +{ + return Time; +} + +std::string FormatTime(box_time_t time, bool includeDate, + bool showMicros = false); + +void ShortSleep(box_time_t duration, bool logDuration); + +#endif // BOXTIME__H diff --git a/lib/common/BoxTimeToText.cpp b/lib/common/BoxTimeToText.cpp new file mode 100644 index 00000000..dbeefe1b --- /dev/null +++ b/lib/common/BoxTimeToText.cpp @@ -0,0 +1,76 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToText.cpp +// Purpose: Convert box time to text +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> +#include <time.h> +#include <stdio.h> + +#include "BoxTimeToText.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxTimeToISO8601String(box_time_t, bool) +// Purpose: Convert a 64 bit box time to a ISO 8601 compliant +// string, either in local or UTC time +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- +std::string BoxTimeToISO8601String(box_time_t Time, bool localTime) +{ + time_t timeInSecs = BoxTimeToSeconds(Time); + char str[128]; // more than enough space + +#ifdef WIN32 + struct tm *time; + __time64_t winTime = timeInSecs; + + if(localTime) + { + time = _localtime64(&winTime); + } + else + { + time = _gmtime64(&winTime); + } + + if(time == NULL) + { + // ::sprintf(str, "%016I64x ", bob); + return std::string("unable to convert time"); + } + + sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time->tm_year + 1900, + time->tm_mon + 1, time->tm_mday, time->tm_hour, + time->tm_min, time->tm_sec); +#else // ! WIN32 + struct tm time; + + if(localTime) + { + localtime_r(&timeInSecs, &time); + } + else + { + gmtime_r(&timeInSecs, &time); + } + + sprintf(str, "%04d-%02d-%02dT%02d:%02d:%02d", time.tm_year + 1900, + time.tm_mon + 1, time.tm_mday, time.tm_hour, + time.tm_min, time.tm_sec); +#endif // WIN32 + + return std::string(str); +} + + diff --git a/lib/common/BoxTimeToText.h b/lib/common/BoxTimeToText.h new file mode 100644 index 00000000..21fa5d57 --- /dev/null +++ b/lib/common/BoxTimeToText.h @@ -0,0 +1,19 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToText.h +// Purpose: Convert box time to text +// Created: 2003/10/10 +// +// -------------------------------------------------------------------------- + +#ifndef BOXTIMETOTEXT__H +#define BOXTIMETOTEXT__H + +#include <string> +#include "BoxTime.h" + +std::string BoxTimeToISO8601String(box_time_t Time, bool localTime); + +#endif // BOXTIMETOTEXT__H + diff --git a/lib/common/BoxTimeToUnix.h b/lib/common/BoxTimeToUnix.h new file mode 100644 index 00000000..f8a8797e --- /dev/null +++ b/lib/common/BoxTimeToUnix.h @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BoxTimeToUnix.h +// Purpose: Convert times in 64 bit values to UNIX structures +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- + +#ifndef FILEMODIFICATIONTIMETOTIMEVAL__H +#define FILEMODIFICATIONTIMETOTIMEVAL__H + +#ifdef WIN32 +#include <time.h> +#else +#include <sys/time.h> +#endif + +#include "BoxTime.h" + +inline void BoxTimeToTimeval(box_time_t Time, struct timeval &tv) +{ + tv.tv_sec = (long)(Time / MICRO_SEC_IN_SEC_LL); + tv.tv_usec = (long)(Time % MICRO_SEC_IN_SEC_LL); +} + +inline void BoxTimeToTimespec(box_time_t Time, struct timespec &tv) +{ + tv.tv_sec = (time_t)(Time / MICRO_SEC_IN_SEC_LL); + tv.tv_nsec = ((long)(Time % MICRO_SEC_IN_SEC_LL)) * NANO_SEC_IN_USEC; +} + +#endif // FILEMODIFICATIONTIMETOTIMEVAL__H + diff --git a/lib/common/BufferedStream.cpp b/lib/common/BufferedStream.cpp new file mode 100644 index 00000000..847cf66c --- /dev/null +++ b/lib/common/BufferedStream.cpp @@ -0,0 +1,209 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedStream.cpp +// Purpose: Buffering read-only wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BufferedStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::BufferedStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +BufferedStream::BufferedStream(IOStream& rSource) +: mrSource(rSource), mBufferSize(0), mBufferPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int BufferedStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if (mBufferSize == mBufferPosition) + { + // buffer is empty, fill it. + + int numBytesRead = mrSource.Read(mBuffer, sizeof(mBuffer), + Timeout); + + if (numBytesRead < 0) + { + return numBytesRead; + } + + mBufferSize = numBytesRead; + } + + int sizeToReturn = mBufferSize - mBufferPosition; + + if (sizeToReturn > NBytes) + { + sizeToReturn = NBytes; + } + + memcpy(pBuffer, mBuffer + mBufferPosition, sizeToReturn); + mBufferPosition += sizeToReturn; + + if (mBufferPosition == mBufferSize) + { + // clear out the buffer + mBufferSize = 0; + mBufferPosition = 0; + } + + return sizeToReturn; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedStream::BytesLeftToRead() +{ + return mrSource.BytesLeftToRead() + mBufferSize - mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedStream::GetPosition() const +{ + return mrSource.GetPosition() - mBufferSize + mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + switch (SeekType) + { + case SeekType_Absolute: + { + // just go there + mrSource.Seek(Offset, SeekType); + } + break; + + case SeekType_Relative: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to subtract that amount from the seek + // to seek forward that much less, putting the + // real pointer in the right place. + mrSource.Seek(Offset - mBufferSize + mBufferPosition, + SeekType); + } + break; + + case SeekType_End: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to add that amount to the seek + // to seek backwards that much more, putting the + // real pointer in the right place. + mrSource.Seek(Offset + mBufferSize - mBufferPosition, + SeekType); + } + } + + // always clear the buffer for now (may be slightly wasteful) + mBufferSize = 0; + mBufferPosition = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedStream::StreamDataLeft() +{ + // Return true if either the source has data left to read, or we have + // buffered data still to be read. + return mrSource.StreamDataLeft() || (mBufferPosition < mBufferSize); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedStream::StreamClosed() +{ + return mrSource.StreamClosed(); +} + diff --git a/lib/common/BufferedStream.h b/lib/common/BufferedStream.h new file mode 100644 index 00000000..3984aceb --- /dev/null +++ b/lib/common/BufferedStream.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedStream.h +// Purpose: Buffering read-only wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#ifndef BUFFEREDSTREAM__H +#define BUFFEREDSTREAM__H + +#include "IOStream.h" + +class BufferedStream : public IOStream +{ +private: + IOStream& mrSource; + char mBuffer[4096]; + int mBufferSize; + int mBufferPosition; + +public: + BufferedStream(IOStream& rSource); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + virtual std::string ToString() const + { + return std::string("Buffered ") + mrSource.ToString(); + } +private: + BufferedStream(const BufferedStream &rToCopy) + : mrSource(rToCopy.mrSource) { /* do not call */ } +}; + +#endif // BUFFEREDSTREAM__H + + diff --git a/lib/common/BufferedWriteStream.cpp b/lib/common/BufferedWriteStream.cpp new file mode 100644 index 00000000..8fbabe9b --- /dev/null +++ b/lib/common/BufferedWriteStream.cpp @@ -0,0 +1,181 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedWriteStream.cpp +// Purpose: Buffering write-only wrapper around IOStreams +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "BufferedWriteStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::BufferedWriteStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +BufferedWriteStream::BufferedWriteStream(IOStream& rSink) +: mrSink(rSink), mBufferPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Read(void *, int) +// Purpose: Reads bytes from the file - throws exception +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int BufferedWriteStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedWriteStream::BytesLeftToRead() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + int numBytesRemain = NBytes; + + do + { + int maxWritable = sizeof(mBuffer) - mBufferPosition; + int numBytesToWrite = (numBytesRemain < maxWritable) ? + numBytesRemain : maxWritable; + + if(numBytesToWrite > 0) + { + memcpy(mBuffer + mBufferPosition, pBuffer, + numBytesToWrite); + mBufferPosition += numBytesToWrite; + pBuffer = ((const char *)pBuffer) + numBytesToWrite; + numBytesRemain -= numBytesToWrite; + } + + if(numBytesRemain > 0) + { + Flush(); + } + } + while(numBytesRemain > 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type BufferedWriteStream::GetPosition() const +{ + return mrSink.GetPosition() + mBufferPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + // Always flush the buffer before seeking + Flush(); + + mrSink.Seek(Offset, SeekType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Flush(); +// Purpose: Write out current buffer contents and invalidate +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Flush(int Timeout) +{ + if(mBufferPosition > 0) + { + mrSink.Write(mBuffer, mBufferPosition); + } + + mBufferPosition = 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void BufferedWriteStream::Close() +{ + Flush(); + mrSink.Close(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedWriteStream::StreamDataLeft() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BufferedWriteStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool BufferedWriteStream::StreamClosed() +{ + return mrSink.StreamClosed(); +} + diff --git a/lib/common/BufferedWriteStream.h b/lib/common/BufferedWriteStream.h new file mode 100644 index 00000000..5f6d5f19 --- /dev/null +++ b/lib/common/BufferedWriteStream.h @@ -0,0 +1,45 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: BufferedWriteStream.h +// Purpose: Buffering write-only wrapper around IOStreams +// Created: 2010/09/13 +// +// -------------------------------------------------------------------------- + +#ifndef BUFFEREDWRITESTREAM__H +#define BUFFEREDWRITESTREAM__H + +#include "IOStream.h" + +class BufferedWriteStream : public IOStream +{ +private: + IOStream& mrSink; + char mBuffer[4096]; + int mBufferPosition; + +public: + BufferedWriteStream(IOStream& rSource); + virtual ~BufferedWriteStream() { Close(); } + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Flush(int Timeout = IOStream::TimeOutInfinite); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + BufferedWriteStream(const BufferedWriteStream &rToCopy) + : mrSink(rToCopy.mrSink) { /* do not call */ } +}; + +#endif // BUFFEREDWRITESTREAM__H + + diff --git a/lib/common/CollectInBufferStream.cpp b/lib/common/CollectInBufferStream.cpp new file mode 100644 index 00000000..47b271f0 --- /dev/null +++ b/lib/common/CollectInBufferStream.cpp @@ -0,0 +1,274 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CollectInBufferStream.cpp +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "CollectInBufferStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +#define INITIAL_BUFFER_SIZE 1024 +#define MAX_BUFFER_ADDITION (1024*64) + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::CollectInBufferStream() +// Purpose: Constructor +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +CollectInBufferStream::CollectInBufferStream() + : mBuffer(INITIAL_BUFFER_SIZE), + mBufferSize(INITIAL_BUFFER_SIZE), + mBytesInBuffer(0), + mReadPosition(0), + mInWritePhase(true) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::~CollectInBufferStream() +// Purpose: Destructor +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +CollectInBufferStream::~CollectInBufferStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Read(void *, int, int) +// Purpose: As interface. But only works in read phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +int CollectInBufferStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Adjust to number of bytes left + if(NBytes > (mBytesInBuffer - mReadPosition)) + { + NBytes = (mBytesInBuffer - mReadPosition); + } + ASSERT(NBytes >= 0); + if(NBytes <= 0) return 0; // careful now + + // Copy in the requested number of bytes and adjust the read pointer + ::memcpy(pBuffer, ((char*)mBuffer) + mReadPosition, NBytes); + mReadPosition += NBytes; + + return NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::BytesLeftToRead() +// Purpose: As interface. But only works in read phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type CollectInBufferStream::BytesLeftToRead() +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + return (mBytesInBuffer - mReadPosition); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Write(void *, int) +// Purpose: As interface. But only works in write phase +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Enough space in the buffer + if((mBytesInBuffer + NBytes) > mBufferSize) + { + // Need to reallocate... what's the block size we'll use? + int allocateBlockSize = mBufferSize; + if(allocateBlockSize > MAX_BUFFER_ADDITION) + { + allocateBlockSize = MAX_BUFFER_ADDITION; + } + + // Write it the easy way. Although it's not the most efficient... + int newSize = mBufferSize; + while(newSize < (mBytesInBuffer + NBytes)) + { + newSize += allocateBlockSize; + } + + // Reallocate buffer + mBuffer.Resize(newSize); + + // Store new size + mBufferSize = newSize; + } + + // Copy in data and adjust counter + ::memcpy(((char*)mBuffer) + mBytesInBuffer, pBuffer, NBytes); + mBytesInBuffer += NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetPosition() +// Purpose: In write phase, returns the number of bytes written, in read +// phase, the number of bytes to go +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type CollectInBufferStream::GetPosition() const +{ + return mInWritePhase?mBytesInBuffer:mReadPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Seek(pos_type, int) +// Purpose: As interface. But read phase only. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Seek(pos_type Offset, int SeekType) +{ + if(mInWritePhase != false) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + int newPos = 0; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newPos = Offset; + break; + case IOStream::SeekType_Relative: + newPos = mReadPosition + Offset; + break; + case IOStream::SeekType_End: + newPos = mBytesInBuffer + Offset; + break; + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + break; + } + + // Make sure it doesn't go over + if(newPos > mBytesInBuffer) + { + newPos = mBytesInBuffer; + } + // or under + if(newPos < 0) + { + newPos = 0; + } + + // Set the new read position + mReadPosition = newPos; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::StreamDataLeft() +// Purpose: As interface +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool CollectInBufferStream::StreamDataLeft() +{ + return mInWritePhase?(false):(mReadPosition < mBytesInBuffer); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::StreamClosed() +// Purpose: As interface +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool CollectInBufferStream::StreamClosed() +{ + return !mInWritePhase; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::SetForReading() +// Purpose: Switch to read phase, after all data written +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::SetForReading() +{ + if(mInWritePhase != true) { THROW_EXCEPTION(CommonException, CollectInBufferStreamNotInCorrectPhase) } + + // Move to read phase + mInWritePhase = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetBuffer() +// Purpose: Returns the buffer +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void *CollectInBufferStream::GetBuffer() const +{ + return mBuffer.GetPtr(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::GetSize() +// Purpose: Returns the buffer size +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +int CollectInBufferStream::GetSize() const +{ + return mBytesInBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CollectInBufferStream::Reset() +// Purpose: Reset the stream, so it is empty and ready to be written to. +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- +void CollectInBufferStream::Reset() +{ + mInWritePhase = true; + mBytesInBuffer = 0; + mReadPosition = 0; +} + diff --git a/lib/common/CollectInBufferStream.h b/lib/common/CollectInBufferStream.h new file mode 100644 index 00000000..297d2851 --- /dev/null +++ b/lib/common/CollectInBufferStream.h @@ -0,0 +1,67 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CollectInBufferStream.h +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#ifndef COLLECTINBUFFERSTREAM__H +#define COLLECTINBUFFERSTREAM__H + +#include "IOStream.h" +#include "Guards.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CollectInBufferStream +// Purpose: Collect data in a buffer, and then read it out. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +class CollectInBufferStream : public IOStream +{ +public: + CollectInBufferStream(); + ~CollectInBufferStream(); + + // Move constructor: + CollectInBufferStream(CollectInBufferStream& rOther) + : mBuffer(rOther.mBuffer.Release()), + mBufferSize(rOther.mBufferSize), + mBytesInBuffer(rOther.mBytesInBuffer), + mReadPosition(rOther.mReadPosition), + mInWritePhase(rOther.mInWritePhase) + { + rOther.Reset(); + } + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + void SetForReading(); + + void Reset(); + + void *GetBuffer() const; + int GetSize() const; + bool IsSetForReading() const {return !mInWritePhase;} + +private: + MemoryBlockGuard<char*> mBuffer; + int mBufferSize; + int mBytesInBuffer; + int mReadPosition; + bool mInWritePhase; +}; + +#endif // COLLECTINBUFFERSTREAM__H + diff --git a/lib/common/CommonException.h b/lib/common/CommonException.h new file mode 100644 index 00000000..a0eb3bf5 --- /dev/null +++ b/lib/common/CommonException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CommonException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef COMMONEXCEPTION__H +#define COMMONEXCEPTION__H + +// Compatibility header with old non-autogen exception scheme +#include "autogen_CommonException.h" + +#endif // COMMONEXCEPTION__H + diff --git a/lib/common/CommonException.txt b/lib/common/CommonException.txt new file mode 100644 index 00000000..610ba1a8 --- /dev/null +++ b/lib/common/CommonException.txt @@ -0,0 +1,59 @@ + +# NOTE: Exception descriptions are for public distributions of Box Backup only -- do not rely for other applications. + + +EXCEPTION Common 1 + +Internal 0 +AssertFailed 1 +OSFileOpenError 2 Can't open a file -- attempted to load a non-existant config file or bad file referenced within? +OSFileCloseError 3 +FileAlreadyClosed 4 +BadArguments 5 +ConfigNoKey 6 +ConfigNoSubConfig 7 +GetLineNoHandle 8 +OSFileError 9 Error accessing a file. Check permissions. +GetLineEOF 10 +ConfigBadIntValue 11 +GetLineTooLarge 12 Protects against very large lines using up lots of memory. +NotSupported 13 +OSFileReadError 14 +OSFileWriteError 15 +FileClosed 16 +IOStreamBadSeekType 17 +CantWriteToPartialReadStream 18 +CollectInBufferStreamNotInCorrectPhase 19 +NamedLockAlreadyLockingSomething 20 +NamedLockNotHeld 21 +StreamableMemBlockIncompleteRead 22 +MemBlockStreamNotSupported 23 +StreamDoesntHaveRequiredProperty 24 +CannotWriteToReadGatherStream 25 +ReadGatherStreamAddingBadBlock 26 +CouldNotLookUpUsername 27 +CouldNotRestoreProcessUser 28 +CouldNotChangeProcessUser 29 +RegexNotSupportedOnThisPlatform 30 Your platform does not have built in regular expression libraries. +BadRegularExpression 31 +CouldNotCreateKQueue 32 +KEventErrorAdd 33 +KEventErrorWait 34 +KEventErrorRemove 35 +KQueueNotSupportedOnThisPlatform 36 +IOStreamGetLineNotEnoughDataToIgnore 37 Bad value passed to IOStreamGetLine::IgnoreBufferedData() +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. +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 +TimersNotInitialised 51 The timer framework should have been ready at this point +InvalidConfiguration 52 Some required values are missing or incorrect in the configuration file. diff --git a/lib/common/Configuration.cpp b/lib/common/Configuration.cpp new file mode 100644 index 00000000..8ce8d389 --- /dev/null +++ b/lib/common/Configuration.cpp @@ -0,0 +1,926 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Configuration.cpp +// Purpose: Reading configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <limits.h> +#include <stdlib.h> +#include <string.h> + +#include <sstream> + +#include "Configuration.h" +#include "CommonException.h" +#include "Guards.h" +#include "FdGetLine.h" + +#include "MemLeakFindOn.h" + +#include <cstring> + +// utility whitespace function +inline bool iw(int c) +{ + return (c == ' ' || c == '\t' || c == '\v' || c == '\f'); // \r, \n are already excluded +} + +// boolean values +static const char *sValueBooleanStrings[] = {"yes", "true", "no", "false", 0}; +static const bool sValueBooleanValue[] = {true, true, false, false}; + +const ConfigurationCategory ConfigurationVerify::VERIFY_ERROR("VerifyError"); + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + void *testFunction +) +: mName(name), + mHasDefaultValue(false), + mFlags(flags), + mTestFunction(testFunction) +{ } + +// to allow passing NULL for default ListenAddresses + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + NoDefaultValue_t t, + void *testFunction +) +: mName(name), + mHasDefaultValue(false), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + std::string defaultValue, + void *testFunction +) +: mName(name), + mDefaultValue(defaultValue), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + const char *defaultValue, + void *testFunction +) +: mName(name), + mDefaultValue(defaultValue), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ } + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + int defaultValue, + void *testFunction +) +: mName(name), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ + ASSERT(flags & ConfigTest_IsInt); + std::ostringstream val; + val << defaultValue; + mDefaultValue = val.str(); +} + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + std::string name, + int flags, + bool defaultValue, + void *testFunction +) +: mName(name), + mHasDefaultValue(true), + mFlags(flags), + mTestFunction(testFunction) +{ + ASSERT(flags & ConfigTest_IsBool); + mDefaultValue = defaultValue ? "yes" : "no"; +} + +ConfigurationVerifyKey::ConfigurationVerifyKey +( + const ConfigurationVerifyKey& rToCopy +) +: mName(rToCopy.mName), + mDefaultValue(rToCopy.mDefaultValue), + mHasDefaultValue(rToCopy.mHasDefaultValue), + mFlags(rToCopy.mFlags), + mTestFunction(rToCopy.mTestFunction) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Configuration(const std::string &) +// Purpose: Constructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::Configuration(const std::string &rName) + : mName(rName) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Configuration(const Configuration &) +// Purpose: Copy constructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::Configuration(const Configuration &rToCopy) + : mName(rToCopy.mName), + mKeys(rToCopy.mKeys), + mSubConfigurations(rToCopy.mSubConfigurations) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::~Configuration() +// Purpose: Destructor +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +Configuration::~Configuration() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::LoadAndVerify(const std::string &, const ConfigurationVerify *, std::string &) +// Purpose: Loads a configuration file from disc, checks it. Returns NULL if it was faulting, in which +// case they'll be an error message. +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +std::auto_ptr<Configuration> Configuration::LoadAndVerify( + const std::string& rFilename, + const ConfigurationVerify *pVerify, + std::string &rErrorMsg) +{ + // Just to make sure + rErrorMsg.erase(); + + // Open the file + FileHandleGuard<O_RDONLY> file(rFilename); + + // GetLine object + FdGetLine getline(file); + + // Object to create + std::auto_ptr<Configuration> apConfig( + new Configuration(std::string("<root>"))); + + try + { + // Load + LoadInto(*apConfig, getline, rErrorMsg, true); + + if(!rErrorMsg.empty()) + { + // An error occured, return now + BOX_LOG_CATEGORY(Log::ERROR, ConfigurationVerify::VERIFY_ERROR, + "Error in Configuration::LoadInto: " << rErrorMsg); + return std::auto_ptr<Configuration>(0); + } + + // Verify? + if(pVerify) + { + if(!apConfig->Verify(*pVerify, std::string(), rErrorMsg)) + { + BOX_LOG_CATEGORY(Log::ERROR, + ConfigurationVerify::VERIFY_ERROR, + "Error verifying configuration: " << + rErrorMsg.substr(0, rErrorMsg.size() > 0 + ? rErrorMsg.size() - 1 : 0)); + return std::auto_ptr<Configuration>(0); + } + } + } + catch(...) + { + // Clean up + throw; + } + + // Success. Return result. + return apConfig; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: LoadInto(Configuration &, FdGetLine &, std::string &, bool) +// Purpose: Private. Load configuration information from the file into the config object. +// Returns 'abort' flag, if error, will be appended to rErrorMsg. +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool Configuration::LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel) +{ + bool startBlockExpected = false; + std::string blockName; + + //TRACE1("BLOCK: |%s|\n", rConfig.mName.c_str()); + + while(!rGetLine.IsEOF()) + { + std::string line(rGetLine.GetLine(true)); /* preprocess out whitespace and comments */ + + if(line.empty()) + { + // Ignore blank lines + continue; + } + + // Line an open block string? + if(line == "{") + { + if(startBlockExpected) + { + // New config object + Configuration subConfig(blockName); + + // Continue processing into this block + if(!LoadInto(subConfig, rGetLine, rErrorMsg, false)) + { + // Abort error + return false; + } + + startBlockExpected = false; + + // Store... + rConfig.AddSubConfig(blockName, subConfig); + } + else + { + rErrorMsg += "Unexpected start block in " + + rConfig.mName + "\n"; + } + } + else + { + // Close block? + if(line == "}") + { + if(RootLevel) + { + // error -- root level doesn't have a close + rErrorMsg += "Root level has close block -- forgot to terminate subblock?\n"; + // but otherwise ignore + } + else + { + //TRACE0("ENDBLOCK\n"); + return true; // All very good and nice + } + } + // Either a key, or a sub block beginning + else + { + // Can't be a start block + if(startBlockExpected) + { + rErrorMsg += "Block " + blockName + " wasn't started correctly (no '{' on line of it's own)\n"; + startBlockExpected = false; + } + + // Has the line got an = in it? + unsigned int equals = 0; + for(; equals < line.size(); ++equals) + { + if(line[equals] == '=') + { + // found! + break; + } + } + if(equals < line.size()) + { + // Make key value pair + unsigned int keyend = equals; + while(keyend > 0 && iw(line[keyend-1])) + { + keyend--; + } + unsigned int valuestart = equals+1; + while(valuestart < line.size() && iw(line[valuestart])) + { + valuestart++; + } + if(keyend > 0 && valuestart <= line.size()) + { + std::string key(line.substr(0, keyend)); + std::string value(line.substr(valuestart)); + rConfig.AddKeyValue(key, value); + } + else + { + rErrorMsg += "Invalid configuration key: " + line + "\n"; + } + } + else + { + // Start of sub block + blockName = line; + startBlockExpected = true; + } + } + } + } + + // End of file? + if(!RootLevel && rGetLine.IsEOF()) + { + // Error if EOF and this isn't the root level + rErrorMsg += "File ended without terminating all subblocks\n"; + } + + return true; +} + +void Configuration::AddKeyValue(const std::string& rKey, + const std::string& rValue) +{ + // Check for duplicate values + if(mKeys.find(rKey) != mKeys.end()) + { + // Multi-values allowed here, but checked later on + mKeys[rKey] += MultiValueSeparator; + mKeys[rKey] += rValue; + } + else + { + // Store + mKeys[rKey] = rValue; + } +} + +void Configuration::AddSubConfig(const std::string& rName, + const Configuration& rSubConfig) +{ + mSubConfigurations.push_back( + std::pair<std::string, Configuration>(rName, rSubConfig)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::KeyExists(const std::string&) +// Purpose: Checks to see if a key exists +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +bool Configuration::KeyExists(const std::string& rKeyName) const +{ + return mKeys.find(rKeyName) != mKeys.end(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValue(const std::string&) +// Purpose: Returns the value of a configuration variable +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +const std::string &Configuration::GetKeyValue(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + BOX_LOG_CATEGORY(Log::ERROR, ConfigurationVerify::VERIFY_ERROR, + "Missing configuration key: " << rKeyName); + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + return i->second; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueInt(const std::string& rKeyName) +// Purpose: Gets a key value as an integer +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +int Configuration::GetKeyValueInt(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + long value = ::strtol((i->second).c_str(), NULL, + 0 /* C style handling */); + if(value == LONG_MAX || value == LONG_MIN) + { + THROW_EXCEPTION(CommonException, ConfigBadIntValue) + } + return (int)value; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueUint32(const std::string& rKeyName) +// Purpose: Gets a key value as a 32-bit unsigned integer +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +uint32_t Configuration::GetKeyValueUint32(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + errno = 0; + long value = ::strtoul((i->second).c_str(), NULL, + 0 /* C style handling */); + if(errno != 0) + { + THROW_EXCEPTION(CommonException, ConfigBadIntValue) + } + return (int)value; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyValueBool(const std::string&) +// Purpose: Gets a key value as a boolean +// Created: 17/2/04 +// +// -------------------------------------------------------------------------- +bool Configuration::GetKeyValueBool(const std::string& rKeyName) const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.find(rKeyName)); + + if(i == mKeys.end()) + { + THROW_EXCEPTION(CommonException, ConfigNoKey) + } + else + { + bool value = false; + + // Anything this is called for should have been verified as having a correct + // string in the verification section. However, this does default to false + // if it isn't in the string table. + + for(int l = 0; sValueBooleanStrings[l] != 0; ++l) + { + if(::strcasecmp((i->second).c_str(), sValueBooleanStrings[l]) == 0) + { + // Found. + value = sValueBooleanValue[l]; + break; + } + } + + return value; + } + +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetKeyNames() +// Purpose: Returns list of key names +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::vector<std::string> Configuration::GetKeyNames() const +{ + std::map<std::string, std::string>::const_iterator i(mKeys.begin()); + + std::vector<std::string> r; + + for(; i != mKeys.end(); ++i) + { + r.push_back(i->first); + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::SubConfigurationExists(const +// std::string&) +// Purpose: Checks to see if a sub configuration exists +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +bool Configuration::SubConfigurationExists(const std::string& rSubName) const +{ + // Attempt to find it... + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + for(; i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return true; + } + } + + // didn't find it. + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfiguration(const +// std::string&) +// Purpose: Gets a sub configuration +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +const Configuration &Configuration::GetSubConfiguration(const std::string& + rSubName) const +{ + // Attempt to find it... + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + for(; i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return i->second; + } + } + + THROW_EXCEPTION(CommonException, ConfigNoSubConfig) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfiguration(const +// std::string&) +// Purpose: Gets a sub configuration for editing +// Created: 2008/08/12 +// +// -------------------------------------------------------------------------- +Configuration &Configuration::GetSubConfigurationEditable(const std::string& + rSubName) +{ + // Attempt to find it... + + for(SubConfigListType::iterator + i = mSubConfigurations.begin(); + i != mSubConfigurations.end(); ++i) + { + // This the one? + if(i->first == rSubName) + { + // Yes. + return i->second; + } + } + + THROW_EXCEPTION(CommonException, ConfigNoSubConfig) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::GetSubConfigurationNames() +// Purpose: Return list of sub configuration names +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::vector<std::string> Configuration::GetSubConfigurationNames() const +{ + std::list<std::pair<std::string, Configuration> >::const_iterator i(mSubConfigurations.begin()); + + std::vector<std::string> r; + + for(; i != mSubConfigurations.end(); ++i) + { + r.push_back(i->first); + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Configuration::Verify(const ConfigurationVerify &, const std::string &, std::string &) +// Purpose: Checks that the configuration is valid according to the +// supplied verifier +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool Configuration::Verify(const ConfigurationVerify &rVerify, + const std::string &rLevel, std::string &rErrorMsg) +{ + bool ok = true; + + // First... check the keys + if(rVerify.mpKeys != 0) + { + const ConfigurationVerifyKey *pvkey = rVerify.mpKeys; + + bool todo = true; + do + { + // Can the key be found? + if(KeyExists(pvkey->Name())) + { + // Get value + const std::string &rval = GetKeyValue(pvkey->Name()); + const char *val = rval.c_str(); + + // Check it's a number? + if((pvkey->Flags() & ConfigTest_IsInt) == ConfigTest_IsInt) + { + // Test it... + char *end; + long r = ::strtol(val, &end, 0); + if(r == LONG_MIN || r == LONG_MAX || end != (val + rval.size())) + { + // not a good value + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid integer.\n"; + } + } + + // Check it's a number? + if(pvkey->Flags() & ConfigTest_IsUint32) + { + // Test it... + char *end; + errno = 0; + uint32_t r = ::strtoul(val, &end, 0); + if(errno != 0 || end != (val + rval.size())) + { + // not a good value + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid unsigned 32-bit integer.\n"; + } + } + + // Check it's a bool? + if((pvkey->Flags() & ConfigTest_IsBool) == ConfigTest_IsBool) + { + // See if it's one of the allowed strings. + bool found = false; + for(int l = 0; sValueBooleanStrings[l] != 0; ++l) + { + if(::strcasecmp(val, sValueBooleanStrings[l]) == 0) + { + // Found. + found = true; + break; + } + } + + // Error if it's not one of them. + if(!found) + { + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is not a valid boolean value.\n"; + } + } + + // Check for multi valued statments where they're not allowed + if((pvkey->Flags() & ConfigTest_MultiValueAllowed) == 0) + { + // Check to see if this key is a multi-value -- it shouldn't be + if(rval.find(MultiValueSeparator) != rval.npos) + { + ok = false; + rErrorMsg += rLevel + mName +"." + pvkey->Name() + " (key) multi value not allowed (duplicated key?).\n"; + } + } + } + else + { + // Is it required to exist? + if((pvkey->Flags() & ConfigTest_Exists) == ConfigTest_Exists) + { + // Should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + "." + pvkey->Name() + " (key) is missing.\n"; + } + else if(pvkey->HasDefaultValue()) + { + mKeys[pvkey->Name()] = + pvkey->DefaultValue(); + } + } + + if((pvkey->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + // No more! + todo = false; + } + + // next + pvkey++; + + } while(todo); + + // Check for additional keys + for(std::map<std::string, std::string>::const_iterator i = mKeys.begin(); + i != mKeys.end(); ++i) + { + // Is the name in the list? + const ConfigurationVerifyKey *scan = rVerify.mpKeys; + bool found = false; + while(scan) + { + if(scan->Name() == i->first) + { + found = true; + break; + } + + // Next? + if((scan->Flags() & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + if(!found) + { + // Shouldn't exist, but does. + ok = false; + rErrorMsg += rLevel + mName + "." + i->first + " (key) is not a known key. Check spelling and placement.\n"; + } + } + } + + // Then the sub configurations + if(rVerify.mpSubConfigurations) + { + // Find the wildcard entry, if it exists, and check that required subconfigs are there + const ConfigurationVerify *wildcardverify = 0; + + const ConfigurationVerify *scan = rVerify.mpSubConfigurations; + while(scan) + { + if(scan->mName.length() > 0 && scan->mName[0] == '*') + { + wildcardverify = scan; + } + + // Required? + if((scan->Tests & ConfigTest_Exists) == ConfigTest_Exists) + { + if(scan->mName.length() > 0 && + scan->mName[0] == '*') + { + // Check something exists + if(mSubConfigurations.size() < 1) + { + // A sub config should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + ".* (block) is missing (a block must be present).\n"; + } + } + else + { + // Check real thing exists + if(!SubConfigurationExists(scan->mName)) + { + // Should exist, but doesn't. + ok = false; + rErrorMsg += rLevel + mName + "." + scan->mName + " (block) is missing.\n"; + } + } + } + + // Next? + if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + // Go through the sub configurations, one by one + for(SubConfigListType::iterator + i = mSubConfigurations.begin(); + i != mSubConfigurations.end(); ++i) + { + // Can this be found? + const ConfigurationVerify *subverify = 0; + + const ConfigurationVerify *scan = rVerify.mpSubConfigurations; + const char *name = i->first.c_str(); + ASSERT(name); + while(scan) + { + if(scan->mName == name) + { + // found it! + subverify = scan; + } + + // Next? + if((scan->Tests & ConfigTest_LastEntry) == ConfigTest_LastEntry) + { + break; + } + scan++; + } + + // Use wildcard? + if(subverify == 0) + { + subverify = wildcardverify; + } + + // Verify + if(subverify) + { + // override const-ness here... + if(!i->second.Verify(*subverify, mName + '.', + rErrorMsg)) + { + ok = false; + } + } + } + } + + return ok; +} + + diff --git a/lib/common/Configuration.h b/lib/common/Configuration.h new file mode 100644 index 00000000..e6498e80 --- /dev/null +++ b/lib/common/Configuration.h @@ -0,0 +1,156 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Configuration +// Purpose: Reading configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#ifndef CONFIGURATION__H +#define CONFIGURATION__H + +#include <map> +#include <list> +#include <vector> +#include <string> +#include <memory> + +// For defining tests +enum +{ + ConfigTest_LastEntry = 1, + ConfigTest_Exists = 2, + ConfigTest_IsInt = 4, + ConfigTest_IsUint32 = 8, + ConfigTest_MultiValueAllowed = 16, + ConfigTest_IsBool = 32 +}; + +class ConfigurationCategory : public Log::Category +{ + public: + ConfigurationCategory(const std::string& name) + : Log::Category(std::string("Configuration/") + name) + { } +}; + +class ConfigurationVerifyKey +{ +public: + typedef enum + { + NoDefaultValue = 1 + } NoDefaultValue_t; + + ConfigurationVerifyKey(std::string name, int flags, + void *testFunction = NULL); + // to allow passing ConfigurationVerifyKey::NoDefaultValue + // for default ListenAddresses + ConfigurationVerifyKey(std::string name, int flags, + NoDefaultValue_t t, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + std::string defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + const char* defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + int defaultValue, void *testFunction = NULL); + ConfigurationVerifyKey(std::string name, int flags, + bool defaultValue, void *testFunction = NULL); + const std::string& Name() const { return mName; } + const std::string& DefaultValue() const { return mDefaultValue; } + const bool HasDefaultValue() const { return mHasDefaultValue; } + const int Flags() const { return mFlags; } + const void* TestFunction() const { return mTestFunction; } + ConfigurationVerifyKey(const ConfigurationVerifyKey& rToCopy); + +private: + ConfigurationVerifyKey& operator=(const ConfigurationVerifyKey& + noAssign); + + std::string mName; // "*" for all other keys (not implemented yet) + std::string mDefaultValue; // default for when it's not present + bool mHasDefaultValue; + int mFlags; + void *mTestFunction; // set to zero for now, will implement later +}; + +class ConfigurationVerify +{ +public: + std::string mName; // "*" for all other sub config names + const ConfigurationVerify *mpSubConfigurations; + const ConfigurationVerifyKey *mpKeys; + int Tests; + void *TestFunction; // set to zero for now, will implement later + static const ConfigurationCategory VERIFY_ERROR; +}; + +class FdGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Configuration +// Purpose: Loading, checking, and representing configuration files +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- +class Configuration +{ +public: + Configuration(const std::string &rName); + Configuration(const Configuration &rToCopy); + ~Configuration(); + + enum + { + // The character to separate multi-values + MultiValueSeparator = '\x01' + }; + + static std::auto_ptr<Configuration> LoadAndVerify( + const std::string& rFilename, + const ConfigurationVerify *pVerify, + std::string &rErrorMsg); + + static std::auto_ptr<Configuration> Load( + const std::string& rFilename, + std::string &rErrorMsg) + { return LoadAndVerify(rFilename, 0, rErrorMsg); } + + bool KeyExists(const std::string& rKeyName) const; + const std::string &GetKeyValue(const std::string& rKeyName) const; + int GetKeyValueInt(const std::string& rKeyName) const; + uint32_t GetKeyValueUint32(const std::string& rKeyName) const; + bool GetKeyValueBool(const std::string& rKeyName) const; + std::vector<std::string> GetKeyNames() const; + + bool SubConfigurationExists(const std::string& rSubName) const; + const Configuration &GetSubConfiguration(const std::string& rSubName) const; + Configuration &GetSubConfigurationEditable(const std::string& rSubName); + std::vector<std::string> GetSubConfigurationNames() const; + + void AddKeyValue(const std::string& rKey, const std::string& rValue); + void AddSubConfig(const std::string& rName, const Configuration& rSubConfig); + + bool Verify(const ConfigurationVerify &rVerify, std::string &rErrorMsg) + { + return Verify(rVerify, std::string(), rErrorMsg); + } + +private: + std::string mName; + // Order of keys not preserved + std::map<std::string, std::string> mKeys; + // Order of sub blocks preserved + typedef std::list<std::pair<std::string, Configuration> > SubConfigListType; + SubConfigListType mSubConfigurations; + + static bool LoadInto(Configuration &rConfig, FdGetLine &rGetLine, std::string &rErrorMsg, bool RootLevel); + bool Verify(const ConfigurationVerify &rVerify, const std::string &rLevel, + std::string &rErrorMsg); +}; + +#endif // CONFIGURATION__H + diff --git a/lib/common/Conversion.h b/lib/common/Conversion.h new file mode 100644 index 00000000..cba5bb08 --- /dev/null +++ b/lib/common/Conversion.h @@ -0,0 +1,98 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Conversion.h +// Purpose: Convert between various types +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef CONVERSION__H +#define CONVERSION__H + +#include <string> + +namespace BoxConvert +{ + // -------------------------------------------------------------------------- + // + // Function + // Name: BoxConvert::Convert<to_type, from_type>(to_type &, from_type) + // Purpose: Convert from types to types + // Created: 9/4/04 + // + // -------------------------------------------------------------------------- + template<typename to_type, typename from_type> + inline to_type Convert(from_type From) + { + // Default conversion, simply use C++ conversion + return From; + } + + // Specialise for string -> integer + int32_t _ConvertStringToInt(const char *pString, int Size); + template<> + inline int32_t Convert<int32_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 32); + } + template<> + inline int16_t Convert<int16_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 16); + } + template<> + inline int8_t Convert<int8_t, const std::string &>(const std::string &rFrom) + { + return BoxConvert::_ConvertStringToInt(rFrom.c_str(), 8); + } + template<> + inline int32_t Convert<int32_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 32); + } + template<> + inline int16_t Convert<int16_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 16); + } + template<> + inline int8_t Convert<int8_t, const char *>(const char *pFrom) + { + return BoxConvert::_ConvertStringToInt(pFrom, 8); + } + + // Specialise for integer -> string + void _ConvertIntToString(std::string &rTo, int32_t From); + template<> + inline std::string Convert<std::string, int32_t>(int32_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + template<> + inline std::string Convert<std::string, int16_t>(int16_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + template<> + inline std::string Convert<std::string, int8_t>(int8_t From) + { + std::string r; + BoxConvert::_ConvertIntToString(r, From); + return r; + } + + // Specialise for bool -> string + template<> + inline std::string Convert<std::string, bool>(bool From) + { + return std::string(From?"true":"false"); + } +}; + +#endif // CONVERSION__H + diff --git a/lib/common/ConversionException.txt b/lib/common/ConversionException.txt new file mode 100644 index 00000000..91b5fa9a --- /dev/null +++ b/lib/common/ConversionException.txt @@ -0,0 +1,8 @@ + +EXCEPTION Conversion 12 + +Internal 0 +CannotConvertEmptyStringToInt 1 +BadStringRepresentationOfInt 2 +IntOverflowInConvertFromString 3 +BadIntSize 4 diff --git a/lib/common/ConversionString.cpp b/lib/common/ConversionString.cpp new file mode 100644 index 00000000..2d0a8d58 --- /dev/null +++ b/lib/common/ConversionString.cpp @@ -0,0 +1,129 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ConversionString.cpp +// Purpose: Conversions to and from strings +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <errno.h> + +#include "Conversion.h" +#include "autogen_ConversionException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxConvert::_ConvertStringToInt(const char *, int) +// Purpose: Convert from string to integer, with range checking. +// Always does signed -- no point in unsigned as C++ type checking +// isn't up to handling it properly. +// If a null pointer is passed in, then returns 0. +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +int32_t BoxConvert::_ConvertStringToInt(const char *pString, int Size) +{ + // Handle null strings gracefully. + if(pString == 0) + { + return 0; + } + + // Check for initial validity + if(*pString == '\0') + { + THROW_EXCEPTION(ConversionException, CannotConvertEmptyStringToInt) + } + + // Convert. + char *numEnd = 0; + errno = 0; // Some platforms don't reset it. + long r = ::strtol(pString, &numEnd, 0); + + // Check that all the characters were used + if(*numEnd != '\0') + { + THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) + } + + // Error check + if(r == 0 && errno == EINVAL) + { + THROW_EXCEPTION(ConversionException, BadStringRepresentationOfInt) + } + + // Range check from strtol + if((r == LONG_MIN || r == LONG_MAX) && errno == ERANGE) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + + // Check range for size of integer + switch(Size) + { + case 32: + { + // No extra checking needed if long is an int32 + if(sizeof(long) > sizeof(int32_t)) + { + if(r <= (0 - 0x7fffffffL) || r > 0x7fffffffL) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + } + break; + } + + case 16: + { + if(r <= (0 - 0x7fff) || r > 0x7fff) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + break; + } + + case 8: + { + if(r <= (0 - 0x7f) || r > 0x7f) + { + THROW_EXCEPTION(ConversionException, IntOverflowInConvertFromString) + } + break; + } + + default: + { + THROW_EXCEPTION(ConversionException, BadIntSize) + break; + } + } + + // Return number + return r; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: BoxConvert::_ConvertIntToString(std::string &, int32_t) +// Purpose: Convert signed interger to a string +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +void BoxConvert::_ConvertIntToString(std::string &rTo, int32_t From) +{ + char text[64]; // size more than enough + ::sprintf(text, "%d", (int)From); + rTo = text; +} + 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/DebugAssertFailed.cpp b/lib/common/DebugAssertFailed.cpp new file mode 100644 index 00000000..e498d641 --- /dev/null +++ b/lib/common/DebugAssertFailed.cpp @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: AssertFailed.cpp +// Purpose: Assert failure code +// Created: 2003/09/04 +// +// -------------------------------------------------------------------------- + +#ifndef BOX_RELEASE_BUILD + +#include "Box.h" + +#include <stdio.h> + +#ifdef WIN32 + #include "emu.h" +#else + #include <syslog.h> +#endif + +#include "MemLeakFindOn.h" + +bool AssertFailuresToSyslog = false; + +void BoxDebugAssertFailed(const char *cond, const char *file, int line) +{ + printf("ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); + if(AssertFailuresToSyslog) + { + ::syslog(LOG_ERR, "ASSERT FAILED: [%s] at %s(%d)\n", cond, file, line); + } +} + + +#endif // BOX_RELEASE_BUILD + diff --git a/lib/common/DebugMemLeakFinder.cpp b/lib/common/DebugMemLeakFinder.cpp new file mode 100644 index 00000000..81e999c4 --- /dev/null +++ b/lib/common/DebugMemLeakFinder.cpp @@ -0,0 +1,726 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFinder.cpp +// Purpose: Memory leak finder +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef BOX_MEMORY_LEAK_TESTING + +#undef malloc +#undef realloc +#undef free + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <limits.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> + +#include <cstdlib> // for std::atexit +#include <map> +#include <set> + +#include "MemLeakFinder.h" + +static bool memleakfinder_initialised = false; +bool memleakfinder_global_enable = false; + +typedef struct +{ + size_t size; + const char *file; + int line; +} MallocBlockInfo; + +typedef struct +{ + size_t size; + const char *file; + int line; + bool array; +} ObjectInfo; + +namespace +{ + static std::map<void *, MallocBlockInfo> sMallocBlocks; + static std::map<void *, ObjectInfo> sObjectBlocks; + static bool sTrackingDataDestroyed = false; + + static class DestructionWatchdog + { + public: + ~DestructionWatchdog() + { + sTrackingDataDestroyed = true; + } + } + sWatchdog; + + static bool sTrackMallocInSection = false; + static std::set<void *> sSectionMallocBlocks; + static bool sTrackObjectsInSection = false; + static std::map<void *, ObjectInfo> sSectionObjectBlocks; + + static std::set<void *> sNotLeaks; + + void *sNotLeaksPre[1024]; + size_t sNotLeaksPreNum = 0; +} + +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(); +} + +void memleakfinder_init() +{ + ASSERT(!memleakfinder_initialised); + + { + // allocates a permanent buffer on Solaris. + // not a leak? + std::ostringstream oss; + } + + memleakfinder_initialised = true; + + #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 +} + +MemLeakSuppressionGuard::MemLeakSuppressionGuard() +{ + ASSERT(memleakfinder_global_enable); + memleakfinder_global_enable = false; +} + +MemLeakSuppressionGuard::~MemLeakSuppressionGuard() +{ + ASSERT(!memleakfinder_global_enable); + memleakfinder_global_enable = true; +} + +// these functions may well allocate memory, which we don't want to track. +static int sInternalAllocDepth = 0; + +class InternalAllocGuard +{ + public: + InternalAllocGuard () { sInternalAllocDepth++; } + ~InternalAllocGuard() { sInternalAllocDepth--; } +}; + +void memleakfinder_malloc_add_block(void *b, size_t size, const char *file, int line) +{ + InternalAllocGuard guard; + + if(b != 0) + { + MallocBlockInfo i; + i.size = size; + i.file = file; + i.line = line; + sMallocBlocks[b] = i; + + if(sTrackMallocInSection) + { + sSectionMallocBlocks.insert(b); + } + } +} + +void *memleakfinder_malloc(size_t size, const char *file, int line) +{ + InternalAllocGuard guard; + + void *b = std::malloc(size); + + if(!memleakfinder_global_enable) + { + // We may not be tracking this allocation, but if + // someone realloc()s the buffer later then it will + // trigger an untracked buffer warning, which we don't + // want to see either. + memleakfinder_notaleak(b); + return b; + } + + if(!memleakfinder_initialised) return b; + + memleakfinder_malloc_add_block(b, size, file, line); + + //TRACE4("malloc(), %d, %s, %d, %08x\n", size, file, line, b); + return b; +} + +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; +} + +void *memleakfinder_realloc(void *ptr, size_t size) +{ + if(!ptr) + { + return memleakfinder_malloc(size, "realloc", 0); + } + + if(!size) + { + memleakfinder_free(ptr); + return NULL; + } + + InternalAllocGuard guard; + + ASSERT(ptr != NULL); + if(!ptr) return NULL; // defensive + + if(!memleakfinder_global_enable || !memleakfinder_initialised) + { + ptr = std::realloc(ptr, size); + if(!memleakfinder_global_enable) + { + // We may not be tracking this allocation, but if + // someone realloc()s the buffer later then it will + // trigger an untracked buffer warning, which we don't + // want to see either. + memleakfinder_notaleak(ptr); + } + return ptr; + } + + // Check it's been allocated + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + std::set<void *>::iterator j(sNotLeaks.find(ptr)); + + if(i == sMallocBlocks.end() && j == sNotLeaks.end()) + { + BOX_WARNING("Block " << ptr << " realloc()ated, but not " + "in list. Error? Or allocated in startup static " + "objects?"); + } + + if(j != sNotLeaks.end()) + { + // It's in the list of not-leaks, so don't warn about it, + // but it's being reallocated, so remove it from the list too, + // in case it's reassigned, and add the new block below. + sNotLeaks.erase(j); + } + + void *b = std::realloc(ptr, size); + + if(i != sMallocBlocks.end()) + { + // Worked? + if(b != 0) + { + // Update map + MallocBlockInfo inf = i->second; + inf.size = size; + sMallocBlocks.erase(i); + sMallocBlocks[b] = inf; + + if(sTrackMallocInSection) + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + sSectionMallocBlocks.insert(b); + } + } + } + else + { + memleakfinder_malloc_add_block(b, size, "FOUND-IN-REALLOC", 0); + } + + //TRACE3("realloc(), %d, %08x->%08x\n", size, ptr, b); + return b; +} + +void memleakfinder_free(void *ptr) +{ + InternalAllocGuard guard; + + if(memleakfinder_global_enable && memleakfinder_initialised) + { + // Check it's been allocated + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + std::set<void *>::iterator j(sNotLeaks.find(ptr)); + + if(i != sMallocBlocks.end()) + { + sMallocBlocks.erase(i); + } + else if(j != sNotLeaks.end()) + { + // It's in the list of not-leaks, so don't warn + // about it, but it's being freed, so remove it + // from the list too, in case it's reassigned. + sNotLeaks.erase(j); + } + else + { + BOX_WARNING("Block " << ptr << " freed, but not " + "known. Error? Or allocated in startup " + "static allocation?"); + } + + if(sTrackMallocInSection) + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + } + } + + //TRACE1("free(), %08x\n", ptr); + std::free(ptr); +} + + +void memleakfinder_notaleak_insert_pre() +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + + for(size_t l = 0; l < sNotLeaksPreNum; l++) + { + sNotLeaks.insert(sNotLeaksPre[l]); + } + + sNotLeaksPreNum = 0; +} + +bool is_leak(void *ptr) +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + memleakfinder_notaleak_insert_pre(); + return sNotLeaks.find(ptr) == sNotLeaks.end(); +} + +void memleakfinder_notaleak(void *ptr) +{ + InternalAllocGuard guard; + + ASSERT(!sTrackingDataDestroyed); + + memleakfinder_notaleak_insert_pre(); + + if(memleakfinder_initialised) + { + sNotLeaks.insert(ptr); + } + else + { + if ( sNotLeaksPreNum < + sizeof(sNotLeaksPre)/sizeof(*sNotLeaksPre) ) + sNotLeaksPre[sNotLeaksPreNum++] = ptr; + } + + /* + { + std::map<void *, MallocBlockInfo>::iterator i(sMallocBlocks.find(ptr)); + if(i != sMallocBlocks.end()) sMallocBlocks.erase(i); + } + { + std::set<void *>::iterator si(sSectionMallocBlocks.find(ptr)); + if(si != sSectionMallocBlocks.end()) sSectionMallocBlocks.erase(si); + } + { + std::map<void *, ObjectInfo>::iterator i(sObjectBlocks.find(ptr)); + if(i != sObjectBlocks.end()) sObjectBlocks.erase(i); + }*/ +} + + + +// start monitoring a section of code +void memleakfinder_startsectionmonitor() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + sTrackMallocInSection = true; + sSectionMallocBlocks.clear(); + sTrackObjectsInSection = true; + sSectionObjectBlocks.clear(); +} + +// trace all blocks allocated and still allocated since memleakfinder_startsectionmonitor() called +void memleakfinder_traceblocksinsection() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + std::set<void *>::iterator s(sSectionMallocBlocks.begin()); + for(; s != sSectionMallocBlocks.end(); ++s) + { + std::map<void *, MallocBlockInfo>::const_iterator i(sMallocBlocks.find(*s)); + if(i == sMallocBlocks.end()) + { + BOX_WARNING("Logical error in section block finding"); + } + else + { + BOX_TRACE("Block " << i->first << " size " << + i->second.size << " allocated at " << + i->second.file << ":" << i->second.line); + } + } + for(std::map<void *, ObjectInfo>::const_iterator i(sSectionObjectBlocks.begin()); i != sSectionObjectBlocks.end(); ++i) + { + BOX_TRACE("Object" << (i->second.array?" []":"") << " " << + i->first << " size " << i->second.size << + " allocated at " << i->second.file << + ":" << i->second.line); + } +} + +int memleakfinder_numleaks() +{ + InternalAllocGuard guard; + + ASSERT(memleakfinder_initialised); + ASSERT(!sTrackingDataDestroyed); + + int n = 0; + + for(std::map<void *, MallocBlockInfo>::const_iterator i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) + { + if(is_leak(i->first)) ++n; + } + + for(std::map<void *, ObjectInfo>::const_iterator i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) + { + const ObjectInfo& rInfo = i->second; + if(is_leak(i->first)) ++n; + } + + return n; +} + +// 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 +} + +void memleakfinder_reportleaks_file(FILE *file) +{ + InternalAllocGuard guard; + + ASSERT(!sTrackingDataDestroyed); + + for(std::map<void *, MallocBlockInfo>::const_iterator + i(sMallocBlocks.begin()); i != sMallocBlocks.end(); ++i) + { + if(is_leak(i->first)) + { + ::fprintf(file, "Block %p size %d allocated at " + "%s:%d\n", i->first, i->second.size, + i->second.file, i->second.line); + } + } + + for(std::map<void *, ObjectInfo>::const_iterator + i(sObjectBlocks.begin()); i != sObjectBlocks.end(); ++i) + { + if(is_leak(i->first)) + { + ::fprintf(file, "Object%s %p size %d allocated at " + "%s:%d\n", i->second.array?" []":"", + i->first, i->second.size, i->second.file, + i->second.line); + } + } +} + +void memleakfinder_reportleaks() +{ + InternalAllocGuard guard; + + // report to stdout + memleakfinder_reportleaks_file(stdout); +} + +void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext) +{ + InternalAllocGuard guard; + + FILE *file = ::fopen(filename, "a"); + if(file != 0) + { + if(memleakfinder_numleaks() > 0) + { +#ifdef HAVE_GETPID + fprintf(file, "MEMORY LEAKS FROM PROCESS %d (%s)\n", getpid(), markertext); +#else + fprintf(file, "MEMORY LEAKS (%s)\n", markertext); +#endif + memleakfinder_reportleaks_file(file); + } + + ::fclose(file); + } + else + { + BOX_WARNING("Couldn't open memory leak results file " << + filename << " for appending"); + } +} + +static char atexit_filename[512]; +static char atexit_markertext[512]; +static bool atexit_registered = false; + +extern "C" void memleakfinder_atexit() +{ + memleakfinder_reportleaks_appendfile(atexit_filename, atexit_markertext); +} + +void memleakfinder_setup_exit_report(const std::string& filename, + const char *markertext) +{ + char buffer[PATH_MAX]; + std::string abs_filename = std::string(getcwd(buffer, sizeof(buffer))) + + DIRECTORY_SEPARATOR + filename; + ::strncpy(atexit_filename, abs_filename.c_str(), + sizeof(atexit_filename)-1); + ::strncpy(atexit_markertext, markertext, sizeof(atexit_markertext)-1); + atexit_filename[sizeof(atexit_filename)-1] = 0; + atexit_markertext[sizeof(atexit_markertext)-1] = 0; + if(!atexit_registered) + { + std::atexit(memleakfinder_atexit); + atexit_registered = true; + } +} + +void add_object_block(void *block, size_t size, const char *file, int line, bool array) +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + ASSERT(!sTrackingDataDestroyed); + + if(block != 0) + { + std::map<void *, ObjectInfo>::iterator j(sObjectBlocks.find(block)); + // The same block should not already be tracked! + ASSERT(j == sObjectBlocks.end()); + + ObjectInfo i; + i.size = size; + i.file = file; + i.line = line; + i.array = array; + sObjectBlocks[block] = i; + + if(sTrackObjectsInSection) + { + sSectionObjectBlocks[block] = i; + } + } +} + +void remove_object_block(void *block) +{ + InternalAllocGuard guard; + + if(!memleakfinder_global_enable) return; + if(!memleakfinder_initialised) return; + if(sTrackingDataDestroyed) return; + + std::map<void *, ObjectInfo>::iterator i(sObjectBlocks.find(block)); + if(i != sObjectBlocks.end()) + { + sObjectBlocks.erase(i); + } + + if(sTrackObjectsInSection) + { + std::map<void *, ObjectInfo>::iterator i(sSectionObjectBlocks.find(block)); + if(i != sSectionObjectBlocks.end()) + { + sSectionObjectBlocks.erase(i); + } + } + + // If it's not in the list, just ignore it, as lots of stuff goes this way... +} + +static void *internal_new(size_t size, const char *file, int line) +{ + void *r; + + { + InternalAllocGuard guard; + r = std::malloc(size); + } + + if (sInternalAllocDepth == 0) + { + InternalAllocGuard guard; + add_object_block(r, size, file, line, false); + //TRACE4("new(), %d, %s, %d, %08x\n", size, file, line, r); + } + + return r; +} + +void *operator new(size_t size, const char *file, int line) +{ + return internal_new(size, file, line); +} + +void *operator new[](size_t size, const char *file, int line) +{ + return internal_new(size, file, line); +} + +// where there is no doctor... need to override standard new() too +// http://www.relisoft.com/book/tech/9new.html +// disabled because it causes hangs on FC2 in futex() in test/common +// while reading files. reason unknown. +/* +void *operator new(size_t size) +{ + return internal_new(size, "standard libraries", 0); +} +*/ + +void *operator new[](size_t size) throw (std::bad_alloc) +{ + return internal_new(size, "standard libraries", 0); +} + +void internal_delete(void *ptr) +{ + InternalAllocGuard guard; + + std::free(ptr); + remove_object_block(ptr); + //TRACE1("delete[]() called, %08x\n", ptr); +} + +void operator delete[](void *ptr) throw () +{ + internal_delete(ptr); +} + +void operator delete(void *ptr) throw () +{ + internal_delete(ptr); +} + +#endif // BOX_MEMORY_LEAK_TESTING diff --git a/lib/common/DebugPrintf.cpp b/lib/common/DebugPrintf.cpp new file mode 100644 index 00000000..1335d473 --- /dev/null +++ b/lib/common/DebugPrintf.cpp @@ -0,0 +1,83 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: DebugPrintf.cpp +// Purpose: Implementation of a printf function, to avoid a stdio.h include in Box.h +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- + +#ifndef BOX_RELEASE_BUILD + +#include "Box.h" + +#include <stdio.h> +#include <stdarg.h> + +#ifdef WIN32 + #include "emu.h" +#else + #include <syslog.h> +#endif + +#include "MemLeakFindOn.h" + +// Use this apparently superflous printf function to avoid having to +// include stdio.h in every file in the project. + +int BoxDebug_printf(const char *format, ...) +{ + va_list ap; + va_start(ap, format); + int r = vprintf(format, ap); + va_end(ap); + return r; +} + + +bool BoxDebugTraceOn = true; +bool BoxDebugTraceToStdout = true; +bool BoxDebugTraceToSyslog = false; + +int BoxDebugTrace(const char *format, ...) +{ + char text[512]; + int r = 0; + if(BoxDebugTraceOn || BoxDebugTraceToSyslog) + { + va_list ap; + va_start(ap, format); + r = vsnprintf(text, sizeof(text), format, ap); + va_end(ap); + } + + // Send to stdout if trace is on and std out is enabled + if(BoxDebugTraceOn && BoxDebugTraceToStdout) + { + printf("%s", text); + } + + // But tracing to syslog is independent of tracing being on or not + if(BoxDebugTraceToSyslog) + { +#ifdef WIN32 + // Remove trailing '\n', if it's there + if(r > 0 && text[r-1] == '\n') + { + text[r-1] = '\0'; +#else + if(r > 0 && text[r] == '\n') + { + text[r] = '\0'; +#endif + --r; + } + // Log it + ::syslog(LOG_INFO, "TRACE: %s", text); + } + + return r; +} + + +#endif // BOX_RELEASE_BUILD diff --git a/lib/common/EndStructPackForWire.h b/lib/common/EndStructPackForWire.h new file mode 100644 index 00000000..82637f33 --- /dev/null +++ b/lib/common/EndStructPackForWire.h @@ -0,0 +1,23 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: EndStructPackForWire.h +// Purpose: End structure packing for wire +// Created: 25/11/03 +// +// -------------------------------------------------------------------------- + +// No header guard -- this is intentional + +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS + +#pragma pack() + +#else + + logical error -- check BoxPlatform.h and including file + +#endif + + + diff --git a/lib/common/ExcludeList.cpp b/lib/common/ExcludeList.cpp new file mode 100644 index 00000000..f101782a --- /dev/null +++ b/lib/common/ExcludeList.cpp @@ -0,0 +1,482 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ExcludeList.cpp +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_REGEX_SUPPORT + #ifdef HAVE_PCREPOSIX_H + #include <pcreposix.h> + #else + #include <regex.h> + #endif + #define EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED +#endif + +#include "ExcludeList.h" +#include "Utils.h" +#include "Configuration.h" +#include "Archive.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::ExcludeList() +// Purpose: Constructor. Generates an exclude list which will allow everything +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList::ExcludeList() + : mpAlwaysInclude(0) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::~ExcludeList() +// Purpose: Destructor +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +ExcludeList::~ExcludeList() +{ +#ifdef HAVE_REGEX_SUPPORT + // free regex memory + while(mRegex.size() > 0) + { + regex_t *pregex = mRegex.back(); + mRegex.pop_back(); + // Free regex storage, and the structure itself + ::regfree(pregex); + delete pregex; + } +#endif + + // Clean up exceptions list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } +} + +#ifdef WIN32 +std::string ExcludeList::ReplaceSlashesDefinite(const std::string& input) const +{ + std::string output = input; + + for (std::string::size_type pos = output.find("/"); + pos != std::string::npos; + pos = output.find("/")) + { + output.replace(pos, 1, DIRECTORY_SEPARATOR); + } + + for (std::string::iterator i = output.begin(); i != output.end(); i++) + { + *i = tolower(*i); + } + + return output; +} + +std::string ExcludeList::ReplaceSlashesRegex(const std::string& input) const +{ + std::string output = input; + + for (std::string::size_type pos = output.find("/"); + pos != std::string::npos; + pos = output.find("/")) + { + output.replace(pos, 1, "\\" DIRECTORY_SEPARATOR); + } + + return output; +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::AddDefiniteEntries(const std::string &) +// Purpose: Adds a number of definite entries to the exclude list -- ones which +// will be excluded if and only if the test string matches exactly. +// Uses the Configuration classes' multi-value conventions, with +// multiple entires in one string separated by Configuration::MultiValueSeparator +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::AddDefiniteEntries(const std::string &rEntries) +{ + // Split strings up + std::vector<std::string> ens; + SplitString(rEntries, Configuration::MultiValueSeparator, ens); + + // Add to set of excluded strings + for(std::vector<std::string>::const_iterator i(ens.begin()); i != ens.end(); ++i) + { + if(i->size() > 0) + { + std::string entry = *i; + + // Convert any forward slashes in the string + // to backslashes + + #ifdef WIN32 + entry = ReplaceSlashesDefinite(entry); + #endif + + if (entry.size() > 0 && entry[entry.size() - 1] == + DIRECTORY_SEPARATOR_ASCHAR) + { + BOX_LOG_CATEGORY(Log::WARNING, + ConfigurationVerify::VERIFY_ERROR, + "Exclude entry ends in path separator, " + "will never match: " << entry); + } + + mDefinite.insert(entry); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::AddRegexEntries(const std::string &) +// Purpose: Adds a number of regular expression entries to the exclude list -- +// if the test expression matches any of these regex, it will be excluded. +// Uses the Configuration classes' multi-value conventions, with +// multiple entires in one string separated by Configuration::MultiValueSeparator +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::AddRegexEntries(const std::string &rEntries) +{ +#ifdef HAVE_REGEX_SUPPORT + + // Split strings up + std::vector<std::string> ens; + SplitString(rEntries, Configuration::MultiValueSeparator, ens); + + // Create and add new regular expressions + for(std::vector<std::string>::const_iterator i(ens.begin()); i != ens.end(); ++i) + { + if(i->size() > 0) + { + // Allocate memory + regex_t *pregex = new regex_t; + + try + { + std::string entry = *i; + int flags = REG_EXTENDED | REG_NOSUB; + + // Convert any forward slashes in the string + // to appropriately escaped backslashes + + #ifdef WIN32 + entry = ReplaceSlashesRegex(entry); + flags |= REG_ICASE; // Windows convention + #endif + + // Compile + int errcode = ::regcomp(pregex, entry.c_str(), + flags); + + if (errcode != 0) + { + char buf[1024]; + regerror(errcode, pregex, buf, sizeof(buf)); + BOX_LOG_CATEGORY(Log::ERROR, + ConfigurationVerify::VERIFY_ERROR, + "Invalid regular expression: " << + entry << ": " << buf); + THROW_EXCEPTION(CommonException, BadRegularExpression) + } + + // Store in list of regular expressions + mRegex.push_back(pregex); + // Store in list of regular expression string for Serialize + mRegexStr.push_back(entry.c_str()); + } + catch(...) + { + delete pregex; + throw; + } + } + } + +#else + THROW_EXCEPTION(CommonException, RegexNotSupportedOnThisPlatform) +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::IsExcluded(const std::string &) +// Purpose: Returns true if the entry should be excluded +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +bool ExcludeList::IsExcluded(const std::string &rTest) const +{ + std::string test = rTest; + + #ifdef WIN32 + // converts to lower case as well + test = ReplaceSlashesDefinite(test); + #endif + + // Check against the always include list + if(mpAlwaysInclude != 0) + { + if(mpAlwaysInclude->IsExcluded(test)) + { + // Because the "always include" list says it's 'excluded' + // this means it should actually be included. + return false; + } + } + + // Is it in the set of definite entries? + if(mDefinite.find(test) != mDefinite.end()) + { + return true; + } + + // Check against regular expressions +#ifdef HAVE_REGEX_SUPPORT + for(std::vector<regex_t *>::const_iterator i(mRegex.begin()); i != mRegex.end(); ++i) + { + // Test against this expression + if(regexec(*i, test.c_str(), 0, 0 /* no match information required */, 0 /* no flags */) == 0) + { + // match happened + return true; + } + // In all other cases, including an error, just continue to the next expression + } +#endif + + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::SetAlwaysIncludeList(ExcludeList *) +// Purpose: Takes ownership of the list, deletes any pre-existing list. +// NULL is acceptable to delete the list. +// The AlwaysInclude list is a list of exceptions to the exclusions. +// Created: 19/2/04 +// +// -------------------------------------------------------------------------- +void ExcludeList::SetAlwaysIncludeList(ExcludeList *pAlwaysInclude) +{ + // Delete old list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } + + // Store the pointer + mpAlwaysInclude = pAlwaysInclude; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::Deserialize(Archive & rArchive) +// Purpose: Deserializes this object instance from a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void ExcludeList::Deserialize(Archive & rArchive) +{ + // + // + // + mDefinite.clear(); + +#ifdef HAVE_REGEX_SUPPORT + // free regex memory + while(mRegex.size() > 0) + { + regex_t *pregex = mRegex.back(); + mRegex.pop_back(); + // Free regex storage, and the structure itself + ::regfree(pregex); + delete pregex; + } + + mRegexStr.clear(); +#endif + + // Clean up exceptions list + if(mpAlwaysInclude != 0) + { + delete mpAlwaysInclude; + mpAlwaysInclude = 0; + } + + // + // + // + int64_t iCount = 0; + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + // load each one + std::string strItem; + rArchive.Read(strItem); + mDefinite.insert(strItem); + } + } + + // + // + // +#ifdef HAVE_REGEX_SUPPORT + rArchive.Read(iCount); + + if (iCount > 0) + { + for (int v = 0; v < iCount; v++) + { + std::string strItem; + rArchive.Read(strItem); + + // Allocate memory + regex_t* pregex = new regex_t; + + try + { + // Compile + if(::regcomp(pregex, strItem.c_str(), + REG_EXTENDED | REG_NOSUB) != 0) + { + THROW_EXCEPTION(CommonException, + BadRegularExpression) + } + + // Store in list of regular expressions + mRegex.push_back(pregex); + + // Store in list of regular expression strings + // for Serialize + mRegexStr.push_back(strItem); + } + catch(...) + { + delete pregex; + throw; + } + } + } +#endif // HAVE_REGEX_SUPPORT + + // + // + // + int64_t aMagicMarker = 0; + rArchive.Read(aMagicMarker); + + if (aMagicMarker == ARCHIVE_MAGIC_VALUE_NOOP) + { + // NOOP + } + else if (aMagicMarker == ARCHIVE_MAGIC_VALUE_RECURSE) + { + mpAlwaysInclude = new ExcludeList; + if (!mpAlwaysInclude) + { + throw std::bad_alloc(); + } + + mpAlwaysInclude->Deserialize(rArchive); + } + else + { + // there is something going on here + THROW_EXCEPTION(CommonException, Internal) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ExcludeList::Serialize(Archive & rArchive) +// Purpose: Serializes this object instance into a stream of bytes, using an Archive abstraction. +// +// Created: 2005/04/11 +// +// -------------------------------------------------------------------------- +void ExcludeList::Serialize(Archive & rArchive) const +{ + // + // + // + int64_t iCount = mDefinite.size(); + rArchive.Write(iCount); + + for (std::set<std::string>::const_iterator i = mDefinite.begin(); + i != mDefinite.end(); i++) + { + rArchive.Write(*i); + } + + // + // + // +#ifdef HAVE_REGEX_SUPPORT + // don't even try to save compiled regular expressions, + // use string copies instead. + ASSERT(mRegex.size() == mRegexStr.size()); + + iCount = mRegexStr.size(); + rArchive.Write(iCount); + + for (std::vector<std::string>::const_iterator i = mRegexStr.begin(); + i != mRegexStr.end(); i++) + { + rArchive.Write(*i); + } +#endif // HAVE_REGEX_SUPPORT + + // + // + // + if (!mpAlwaysInclude) + { + 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); + + mpAlwaysInclude->Serialize(rArchive); + } +} diff --git a/lib/common/ExcludeList.h b/lib/common/ExcludeList.h new file mode 100644 index 00000000..3c41bd11 --- /dev/null +++ b/lib/common/ExcludeList.h @@ -0,0 +1,76 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ExcludeList.h +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef EXCLUDELIST__H +#define EXCLUDELIST__H + +#include <string> +#include <set> +#include <vector> + +// avoid including regex.h in lots of places +#ifndef EXCLUDELIST_IMPLEMENTATION_REGEX_T_DEFINED + typedef int regex_t; +#endif + +class Archive; + +// -------------------------------------------------------------------------- +// +// Class +// Name: ExcludeList +// Purpose: General purpose exclusion list +// Created: 28/1/04 +// +// -------------------------------------------------------------------------- +class ExcludeList +{ +public: + ExcludeList(); + ~ExcludeList(); + + void Deserialize(Archive & rArchive); + void Serialize(Archive & rArchive) const; + + void AddDefiniteEntries(const std::string &rEntries); + void AddRegexEntries(const std::string &rEntries); + + // Add exceptions to the exclusions (takes ownership) + void SetAlwaysIncludeList(ExcludeList *pAlwaysInclude); + + // Test function + bool IsExcluded(const std::string &rTest) const; + + // Mainly for tests + unsigned int SizeOfDefiniteList() const {return mDefinite.size();} + unsigned int SizeOfRegexList() const +#ifdef HAVE_REGEX_SUPPORT + {return mRegex.size();} +#else + {return 0;} +#endif + +private: + std::set<std::string> mDefinite; +#ifdef HAVE_REGEX_SUPPORT + std::vector<regex_t *> mRegex; + std::vector<std::string> mRegexStr; // save original regular expression string-based source for Serialize +#endif + +#ifdef WIN32 + std::string ReplaceSlashesDefinite(const std::string& input) const; + std::string ReplaceSlashesRegex (const std::string& input) const; +#endif + + // For exceptions to the excludes + ExcludeList *mpAlwaysInclude; +}; + +#endif // EXCLUDELIST__H + diff --git a/lib/common/FdGetLine.cpp b/lib/common/FdGetLine.cpp new file mode 100644 index 00000000..30409d92 --- /dev/null +++ b/lib/common/FdGetLine.cpp @@ -0,0 +1,142 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FdGetLine.cpp +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "FdGetLine.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::FdGetLine(int) +// Purpose: Constructor, taking file descriptor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +FdGetLine::FdGetLine(int fd) +: mFileHandle(fd) +{ + if(mFileHandle < 0) {THROW_EXCEPTION(CommonException, BadArguments)} + //printf("FdGetLine buffer size = %d\n", sizeof(mBuffer)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::~FdGetLine() +// Purpose: Destructor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +FdGetLine::~FdGetLine() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::GetLine(bool) +// Purpose: Returns a file from the file. If Preprocess is true, leading +// and trailing whitespace is removed, and comments (after #) +// are deleted. +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +std::string FdGetLine::GetLine(bool Preprocess) +{ + if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} + + 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; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FdGetLine::DetachFile() +// Purpose: Detaches the file handle, setting the file pointer correctly. +// Probably not good for sockets... +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +void FdGetLine::DetachFile() +{ + if(mFileHandle == -1) {THROW_EXCEPTION(CommonException, GetLineNoHandle)} + + // Adjust file pointer + int bytesOver = mBufferBegin - mBufferBegin; + ASSERT(bytesOver >= 0); + if(bytesOver > 0) + { + if(::lseek(mFileHandle, 0 - bytesOver, SEEK_CUR) == -1) + { + THROW_EXCEPTION(CommonException, OSFileError) + } + } + + // Unset file pointer + mFileHandle = -1; +} + diff --git a/lib/common/FdGetLine.h b/lib/common/FdGetLine.h new file mode 100644 index 00000000..2b9c268f --- /dev/null +++ b/lib/common/FdGetLine.h @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FdGetLine.h +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#ifndef FDGETLINE__H +#define FDGETLINE__H + +#include <string> + +#include "GetLine.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: FdGetLine +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +class FdGetLine : public GetLine +{ +public: + FdGetLine(int fd); + virtual ~FdGetLine(); +private: + FdGetLine(const FdGetLine &rToCopy); + +public: + 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; +}; + +#endif // FDGETLINE__H + diff --git a/lib/common/FileModificationTime.cpp b/lib/common/FileModificationTime.cpp new file mode 100644 index 00000000..50f1fb62 --- /dev/null +++ b/lib/common/FileModificationTime.cpp @@ -0,0 +1,70 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileModificationTime.cpp +// Purpose: Function for getting file modification time. +// Created: 2010/02/15 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/stat.h> + +#include "BoxTime.h" +#include "FileModificationTime.h" + +#include "MemLeakFindOn.h" + +box_time_t FileModificationTime(const EMU_STRUCT_STAT &st) +{ +#if defined HAVE_STRUCT_STAT_ST_ATIM + box_time_t datamodified = (((int64_t)st.st_mtim.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_mtim.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC + box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#else + box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); +#endif + + return datamodified; +} + +box_time_t FileAttrModificationTime(const EMU_STRUCT_STAT &st) +{ + box_time_t statusmodified = +#if defined HAVE_STRUCT_STAT_ST_ATIM + (((int64_t)st.st_ctim.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctim.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIMESPEC + (((int64_t)st.st_ctimespec.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIM_TV_NSEC + (((int64_t)st.st_ctim.tv_nsec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctim.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#elif defined HAVE_STRUCT_STAT_ST_ATIMENSEC + (((int64_t)st.st_ctimensec) / (NANO_SEC_IN_USEC_LL)) + + (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); +#else // no nanoseconds anywhere + (((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL)); +#endif + + return statusmodified; +} + +box_time_t FileModificationTimeMaxModAndAttr(const EMU_STRUCT_STAT &st) +{ +#ifndef HAVE_STRUCT_STAT_ST_MTIMESPEC + box_time_t datamodified = ((int64_t)st.st_mtime) * (MICRO_SEC_IN_SEC_LL); + box_time_t statusmodified = ((int64_t)st.st_ctime) * (MICRO_SEC_IN_SEC_LL); +#else + box_time_t datamodified = (((int64_t)st.st_mtimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_mtimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); + box_time_t statusmodified = (((int64_t)st.st_ctimespec.tv_nsec) / NANO_SEC_IN_USEC_LL) + + (((int64_t)st.st_ctimespec.tv_sec) * (MICRO_SEC_IN_SEC_LL)); +#endif + + return (datamodified > statusmodified)?datamodified:statusmodified; +} + diff --git a/lib/common/FileModificationTime.h b/lib/common/FileModificationTime.h new file mode 100644 index 00000000..85424842 --- /dev/null +++ b/lib/common/FileModificationTime.h @@ -0,0 +1,22 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileModificationTime.h +// Purpose: Function for getting file modification time. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef FILEMODIFICATIONTIME__H +#define FILEMODIFICATIONTIME__H + +#include <sys/stat.h> + +#include "BoxTime.h" + +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); + +#endif // FILEMODIFICATIONTIME__H + diff --git a/lib/common/FileStream.cpp b/lib/common/FileStream.cpp new file mode 100644 index 00000000..51752f85 --- /dev/null +++ b/lib/common/FileStream.cpp @@ -0,0 +1,465 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileStream.cpp +// Purpose: IOStream interface to files +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "FileStream.h" +#include "CommonException.h" +#include "Logging.h" + +#include <errno.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const char *, int, int) +// Purpose: Constructor, opens file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const std::string& rFilename, int flags, int mode) +#ifdef WIN32 + : mOSFileHandle(::openfile(rFilename.c_str(), flags, mode)), +#else + : mOSFileHandle(::open(rFilename.c_str(), flags, mode)), +#endif + mIsEOF(false), + mFileName(rFilename) +{ + AfterOpen(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const char *, int, int) +// Purpose: Alternative constructor, takes a const char *, +// avoids const strings being interpreted as handles! +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const char *pFilename, int flags, int mode) +#ifdef WIN32 + : mOSFileHandle(::openfile(pFilename, flags, mode)), +#else + : mOSFileHandle(::open(pFilename, flags, mode)), +#endif + mIsEOF(false), + mFileName(pFilename) +{ + AfterOpen(); +} + +void FileStream::AfterOpen() +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + +#ifdef WIN32 + if(errno == EACCES) + { + THROW_WIN_FILE_ERRNO("Failed to open file", mFileName, + winerrno, CommonException, AccessDenied); + } + else + { + THROW_WIN_FILE_ERRNO("Failed to open file", mFileName, + winerrno, CommonException, OSFileOpenError); + } +#else + if(errno == EACCES) + { + THROW_SYS_FILE_ERROR("Failed to open file", mFileName, + CommonException, AccessDenied); + } + else + { + THROW_SYS_FILE_ERROR("Failed to open file", mFileName, + CommonException, OSFileOpenError); + } +#endif + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(tOSFileHandle) +// Purpose: Constructor, using existing file descriptor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(tOSFileHandle FileDescriptor) + : mOSFileHandle(FileDescriptor), + mIsEOF(false), + mFileName("HANDLE") +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + BOX_ERROR("FileStream: called with invalid file handle"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } +} + +#if 0 +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::FileStream(const FileStream &) +// Purpose: Copy constructor, creates a duplicate of the file handle +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::FileStream(const FileStream &rToCopy) + : mOSFileHandle(::dup(rToCopy.mOSFileHandle)), + mIsEOF(rToCopy.mIsEOF) +{ +#ifdef WIN32 + if(mOSFileHandle == INVALID_HANDLE_VALUE) +#else + if(mOSFileHandle < 0) +#endif + { + MEMLEAKFINDER_NOT_A_LEAK(this); + BOX_ERROR("FileStream: copying unopened file"); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } +} +#endif // 0 + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::~FileStream() +// Purpose: Destructor, closes file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +FileStream::~FileStream() +{ + if(mOSFileHandle != INVALID_FILE) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int FileStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + int r; + DWORD numBytesRead = 0; + BOOL valid = ReadFile( + this->mOSFileHandle, + pBuffer, + NBytes, + &numBytesRead, + NULL + ); + + if(valid) + { + r = numBytesRead; + } + else if(GetLastError() == ERROR_BROKEN_PIPE) + { + r = 0; + } + else + { + 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 + + if(r == 0) + { + mIsEOF = true; + } + + return r; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +IOStream::pos_type FileStream::BytesLeftToRead() +{ + EMU_STRUCT_STAT st; + if(EMU_FSTAT(mOSFileHandle, &st) != 0) + { + BOX_LOG_SYS_ERROR(BOX_FILE_MESSAGE("Failed to stat file", mFileName)); + } + + return st.st_size - GetPosition(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Write(void *, int) +// Purpose: Writes bytes to the file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + DWORD numBytesWritten = 0; + BOOL res = WriteFile( + this->mOSFileHandle, + pBuffer, + NBytes, + &numBytesWritten, + NULL + ); + + if ((res == 0) || (numBytesWritten != (DWORD)NBytes)) + { + THROW_WIN_FILE_ERROR("Failed to write to file", mFileName, + CommonException, OSFileWriteError); + } +#else + if(::write(mOSFileHandle, pBuffer, NBytes) != NBytes) + { + THROW_SYS_FILE_ERROR("Failed to write to file", mFileName, + CommonException, OSFileWriteError); + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type FileStream::GetPosition() const +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + LARGE_INTEGER conv; + 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); + } + + return (IOStream::pos_type)conv.QuadPart; +#else // ! WIN32 + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_SYS_FILE_ERROR("Failed to seek in file", mFileName, + CommonException, OSFileError); + } + + return (IOStream::pos_type)p; +#endif // WIN32 +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileClosed) + } + +#ifdef WIN32 + LARGE_INTEGER conv; + conv.QuadPart = Offset; + DWORD retVal = SetFilePointer(this->mOSFileHandle, conv.LowPart, &conv.HighPart, ConvertSeekTypeToOSWhence(SeekType)); + + if(retVal == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) + { + THROW_WIN_FILE_ERROR("Failed to seek in file", mFileName, + CommonException, OSFileError); + } +#else // ! WIN32 + if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_SYS_FILE_ERROR("Failed to seek in file", mFileName, + CommonException, OSFileError); + } +#endif // WIN32 + + // Not end of file any more! + mIsEOF = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::Close() +// Purpose: Closes the underlying file +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void FileStream::Close() +{ + if(mOSFileHandle == INVALID_FILE) + { + THROW_EXCEPTION(CommonException, FileAlreadyClosed) + } + +#ifdef WIN32 + if(::CloseHandle(mOSFileHandle) == 0) + { + 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 + + mOSFileHandle = INVALID_FILE; + mIsEOF = true; +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool FileStream::StreamDataLeft() +{ + return !mIsEOF; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool FileStream::StreamClosed() +{ + return (mOSFileHandle == INVALID_FILE); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileStream::CompareWith(IOStream&, int) +// Purpose: Compare bytes in this file with other stream's data +// Created: 2009/01/03 +// +// -------------------------------------------------------------------------- +bool FileStream::CompareWith(IOStream& rOther, int Timeout) +{ + // Size + IOStream::pos_type mySize = BytesLeftToRead(); + IOStream::pos_type otherSize = 0; + + // Test the contents + char buf1[2048]; + char buf2[2048]; + while(StreamDataLeft() && rOther.StreamDataLeft()) + { + int readSize = rOther.Read(buf1, sizeof(buf1), Timeout); + otherSize += readSize; + + if(Read(buf2, readSize) != readSize || + ::memcmp(buf1, buf2, readSize) != 0) + { + return false; + } + } + + // Check read all the data from the server and file -- can't be + // equal if local and remote aren't the same length. Can't use + // StreamDataLeft() test on local file, because if it's the same + // size, it won't know it's EOF yet. + + if(rOther.StreamDataLeft() || otherSize != mySize) + { + return false; + } + + return true; +} diff --git a/lib/common/FileStream.h b/lib/common/FileStream.h new file mode 100644 index 00000000..1426d8a2 --- /dev/null +++ b/lib/common/FileStream.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: FileStream.h +// Purpose: FileStream interface to files +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef FILESTREAM__H +#define FILESTREAM__H + +#include "IOStream.h" + +#include <fcntl.h> +#include <stdlib.h> +#include <sys/stat.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +class FileStream : public IOStream +{ +public: + FileStream(const std::string& rFilename, + int flags = (O_RDONLY | O_BINARY), + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + + // Ensure that const char * name doesn't end up as a handle + // on Windows! + + FileStream(const char *pFilename, + int flags = (O_RDONLY | O_BINARY), + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + + FileStream(tOSFileHandle FileDescriptor); + + virtual ~FileStream(); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + bool CompareWith(IOStream& rOther, int Timeout = IOStream::TimeOutInfinite); + std::string ToString() const + { + return std::string("local file ") + mFileName; + } + const std::string GetFileName() const { return mFileName; } + +private: + tOSFileHandle mOSFileHandle; + bool mIsEOF; + FileStream(const FileStream &rToCopy) { /* do not call */ } + void AfterOpen(); + + // for debugging.. + std::string mFileName; +}; + + +#endif // FILESTREAM__H + + 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/Guards.h b/lib/common/Guards.h new file mode 100644 index 00000000..46b6d2bd --- /dev/null +++ b/lib/common/Guards.h @@ -0,0 +1,142 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Guards.h +// Purpose: Classes which ensure things are closed/deleted properly when +// going out of scope. Easy exception proof code, etc +// Created: 2003/07/12 +// +// -------------------------------------------------------------------------- + +#ifndef GUARDS__H +#define GUARDS__H + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> + +#include <new> + +#include "CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +template <int flags = O_RDONLY | O_BINARY, int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)> +class FileHandleGuard +{ +public: + FileHandleGuard(const std::string& rFilename) + : mOSFileHandle(::open(rFilename.c_str(), flags, mode)) + { + if(mOSFileHandle < 0) + { + THROW_SYS_FILE_ERROR("Failed to open file", rFilename, + CommonException, OSFileOpenError); + } + } + + ~FileHandleGuard() + { + if(mOSFileHandle >= 0) + { + Close(); + } + } + + void Close() + { + if(mOSFileHandle < 0) + { + THROW_EXCEPTION(CommonException, FileAlreadyClosed) + } + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(CommonException, OSFileCloseError) + } + mOSFileHandle = -1; + } + + operator int() const + { + return mOSFileHandle; + } + +private: + int mOSFileHandle; +}; + +template<typename type> +class MemoryBlockGuard +{ +public: + MemoryBlockGuard(int BlockSize) + : mpBlock(::malloc(BlockSize)), + mBlockSize(BlockSize) + { + if(mpBlock == 0) + { + throw std::bad_alloc(); + } + } + + MemoryBlockGuard(void *pBlock) + : mpBlock(pBlock) + { + if(mpBlock == 0) + { + throw std::bad_alloc(); + } + } + + ~MemoryBlockGuard() + { + free(mpBlock); + } + + operator type() const + { + return (type)mpBlock; + } + + type GetPtr() const + { + return (type)mpBlock; + } + + void Resize(int NewSize) + { + void *ptrn = ::realloc(mpBlock, NewSize); + if(ptrn == 0) + { + throw std::bad_alloc(); + } + mpBlock = ptrn; + } + + void* Release() + { + void* pBlock = mpBlock; + mpBlock = ::malloc(mBlockSize); + if(mpBlock == 0) + { + throw std::bad_alloc(); + } + return pBlock; + } + +private: + void *mpBlock; + int mBlockSize; +}; + +#include "MemLeakFindOff.h" + +#endif // GUARDS__H + diff --git a/lib/common/IOStream.cpp b/lib/common/IOStream.cpp new file mode 100644 index 00000000..3e126d3f --- /dev/null +++ b/lib/common/IOStream.cpp @@ -0,0 +1,274 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStream.cpp +// Purpose: I/O Stream abstraction +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "IOStream.h" +#include "CommonException.h" +#include "Guards.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::IOStream() +// Purpose: Constructor +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +IOStream::IOStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::~IOStream() +// Purpose: Destructor +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +IOStream::~IOStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Close() +// Purpose: Close the stream +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void IOStream::Close() +{ + // Do nothing by default -- let the destructor clear everything up. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Seek(int, int) +// Purpose: Seek in stream (if supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void IOStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + THROW_EXCEPTION(CommonException, NotSupported) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::GetPosition() +// Purpose: Returns current position in stream (if supported) +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type IOStream::GetPosition() const +{ + THROW_EXCEPTION(CommonException, NotSupported) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::ConvertSeekTypeToOSWhence(int) +// Purpose: Return an whence arg for lseek given a IOStream seek type +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +int IOStream::ConvertSeekTypeToOSWhence(int SeekType) +{ + // Should be nicely optimised out as values are choosen in header file to match OS values. + int ostype = SEEK_SET; + switch(SeekType) + { +#ifdef WIN32 + case SeekType_Absolute: + ostype = FILE_BEGIN; + break; + case SeekType_Relative: + ostype = FILE_CURRENT; + break; + case SeekType_End: + ostype = FILE_END; + break; +#else // ! WIN32 + case SeekType_Absolute: + ostype = SEEK_SET; + break; + case SeekType_Relative: + ostype = SEEK_CUR; + break; + case SeekType_End: + ostype = SEEK_END; + break; +#endif // WIN32 + + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + } + + return ostype; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::ReadFullBuffer(void *, int, int) +// Purpose: Reads bytes into buffer, returning whether or not it managed to +// get all the bytes required. Exception and abort use of stream +// if this returns false. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool IOStream::ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout) +{ + int bytesToGo = NBytes; + char *buffer = (char*)pBuffer; + if(pNBytesRead) (*pNBytesRead) = 0; + + while(bytesToGo > 0) + { + int bytesRead = Read(buffer, bytesToGo, Timeout); + if(bytesRead == 0) + { + // Timeout or something + return false; + } + // Increment things + bytesToGo -= bytesRead; + buffer += bytesRead; + if(pNBytesRead) (*pNBytesRead) += bytesRead; + } + + // Got everything + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::WriteAllBuffered() +// Purpose: Ensures that any data which has been buffered is written to the stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void IOStream::WriteAllBuffered(int Timeout) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::BytesLeftToRead() +// Purpose: Numbers of bytes left to read in the stream, or +// IOStream::SizeOfStreamUnknown if this isn't known. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type IOStream::BytesLeftToRead() +{ + return IOStream::SizeOfStreamUnknown; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::CopyStreamTo(IOStream &, int Timeout) +// Purpose: Copies the entire stream to another stream (reading from this, +// writing to rCopyTo). Returns whether the copy completed (ie +// StreamDataLeft() returns false) +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool IOStream::CopyStreamTo(IOStream &rCopyTo, int Timeout, int BufferSize) +{ + // Make sure there's something to do before allocating that buffer + if(!StreamDataLeft()) + { + return true; // complete, even though nothing happened + } + + // Buffer + MemoryBlockGuard<char*> buffer(BufferSize); + + // Get copying! + while(StreamDataLeft()) + { + // Read some data + int bytes = Read(buffer, BufferSize, Timeout); + if(bytes == 0 && StreamDataLeft()) + { + return false; // incomplete, timed out + } + + // Write some data + if(bytes != 0) + { + rCopyTo.Write(buffer, bytes); + } + } + + return true; // completed +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Flush(int Timeout) +// Purpose: Read and discard all remaining data in stream. +// Useful for protocol streams which must be flushed +// to avoid breaking the protocol. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- +void IOStream::Flush(int Timeout) +{ + char buffer[4096]; + + while(StreamDataLeft()) + { + Read(buffer, sizeof(buffer), Timeout); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::Write +// Purpose: Convenience method for writing a C++ string to a +// protocol buffer. +// +// -------------------------------------------------------------------------- +void IOStream::Write(const std::string& rBuffer, int Timeout) +{ + Write(rBuffer.c_str(), rBuffer.size(), Timeout); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::ToString() +// Purpose: Returns a string which describes this stream. Useful +// when reporting exceptions about a stream of unknown +// origin, for example in BackupStoreDirectory(). +// Created: 2014/04/28 +// +// -------------------------------------------------------------------------- +std::string IOStream::ToString() const +{ + return "unknown IOStream"; +} diff --git a/lib/common/IOStream.h b/lib/common/IOStream.h new file mode 100644 index 00000000..df7216c3 --- /dev/null +++ b/lib/common/IOStream.h @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStream.h +// Purpose: I/O Stream abstraction +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef IOSTREAM__H +#define IOSTREAM__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: IOStream +// Purpose: Abstract interface to streams of data +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class IOStream +{ +public: + IOStream(); + virtual ~IOStream(); + +private: + IOStream(const IOStream &rToCopy); /* forbidden */ + IOStream& operator=(const IOStream &rToCopy); /* forbidden */ + +public: + enum + { + TimeOutInfinite = -1, + SizeOfStreamUnknown = -1 + }; + + enum + { + SeekType_Absolute = 0, + SeekType_Relative = 1, + SeekType_End = 2 + }; + + // Timeout in milliseconds + // Read may return 0 -- does not mean end of stream. + typedef int64_t pos_type; + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) = 0; + virtual pos_type BytesLeftToRead(); // may return IOStream::SizeOfStreamUnknown (and will for most stream types) + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) = 0; + virtual void Write(const std::string& rBuffer, + int Timeout = IOStream::TimeOutInfinite); + virtual void WriteAllBuffered(int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual void Close(); + + // Has all data that can be read been read? + virtual bool StreamDataLeft() = 0; + // Has the stream been closed (writing not possible) + virtual bool StreamClosed() = 0; + + // Utility functions + bool ReadFullBuffer(void *pBuffer, int NBytes, int *pNBytesRead, int Timeout = IOStream::TimeOutInfinite); + bool CopyStreamTo(IOStream &rCopyTo, int Timeout = IOStream::TimeOutInfinite, int BufferSize = 1024); + void Flush(int Timeout = IOStream::TimeOutInfinite); + + static int ConvertSeekTypeToOSWhence(int SeekType); + virtual std::string ToString() const; +}; + +#endif // IOSTREAM__H diff --git a/lib/common/IOStreamGetLine.cpp b/lib/common/IOStreamGetLine.cpp new file mode 100644 index 00000000..ef8930b8 --- /dev/null +++ b/lib/common/IOStreamGetLine.cpp @@ -0,0 +1,127 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStreamGetLine.cpp +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "IOStreamGetLine.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::IOStreamGetLine(int) +// Purpose: Constructor, taking file descriptor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +IOStreamGetLine::IOStreamGetLine(IOStream &Stream) +: mrStream(Stream) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::~IOStreamGetLine() +// Purpose: Destructor +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +IOStreamGetLine::~IOStreamGetLine() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::GetLine(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: 2003/07/24 +// +// -------------------------------------------------------------------------- +bool IOStreamGetLine::GetLine(std::string &rOutput, bool Preprocess, int Timeout) +{ + 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; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::DetachFile() +// Purpose: Detaches the file handle, setting the file pointer correctly. +// Probably not good for sockets... +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +void IOStreamGetLine::DetachFile() +{ + // Adjust file pointer + int bytesOver = mBytesInBuffer - mBufferBegin; + ASSERT(bytesOver >= 0); + if(bytesOver > 0) + { + mrStream.Seek(0 - bytesOver, IOStream::SeekType_Relative); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStreamGetLine::IgnoreBufferedData(int) +// Purpose: Ignore buffered bytes (effectively removing them from the +// beginning of the buffered data.) +// Cannot remove more bytes than are currently in the buffer. +// Be careful when this is used! +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void IOStreamGetLine::IgnoreBufferedData(int BytesToIgnore) +{ + int bytesInBuffer = mBytesInBuffer - mBufferBegin; + if(BytesToIgnore < 0 || BytesToIgnore > bytesInBuffer) + { + THROW_EXCEPTION(CommonException, IOStreamGetLineNotEnoughDataToIgnore) + } + mBufferBegin += BytesToIgnore; +} + + + diff --git a/lib/common/IOStreamGetLine.h b/lib/common/IOStreamGetLine.h new file mode 100644 index 00000000..1b537031 --- /dev/null +++ b/lib/common/IOStreamGetLine.h @@ -0,0 +1,67 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: IOStreamGetLine.h +// Purpose: Line based file descriptor reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- + +#ifndef IOSTREAMGETLINE__H +#define IOSTREAMGETLINE__H + +#include <string> + +#include "GetLine.h" +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: IOStreamGetLine +// Purpose: Line based stream reading +// Created: 2003/07/24 +// +// -------------------------------------------------------------------------- +class IOStreamGetLine : public GetLine +{ +public: + IOStreamGetLine(IOStream &Stream); + virtual ~IOStreamGetLine(); +private: + IOStreamGetLine(const IOStreamGetLine &rToCopy); + +public: + bool GetLine(std::string &rOutput, bool Preprocess = false, int Timeout = IOStream::TimeOutInfinite); + std::string GetLine() + { + std::string output; + GetLine(output); + return output; + } + + // Call to detach, setting file pointer correctly to last bit read. + // Only works for lseek-able file descriptors. + void DetachFile(); + + virtual bool IsStreamDataLeft() + { + return mrStream.StreamDataLeft(); + } + + // 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;} + +protected: + int ReadMore(int Timeout = IOStream::TimeOutInfinite); + +private: + IOStream &mrStream; +}; + +#endif // IOSTREAMGETLINE__H + diff --git a/lib/common/InvisibleTempFileStream.cpp b/lib/common/InvisibleTempFileStream.cpp new file mode 100644 index 00000000..1a9d6d5a --- /dev/null +++ b/lib/common/InvisibleTempFileStream.cpp @@ -0,0 +1,40 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: InvisibleTempFileStream.cpp +// Purpose: IOStream interface to temporary files that +// delete themselves +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "InvisibleTempFileStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: InvisibleTempFileStream::InvisibleTempFileStream +// (const char *, int, int) +// Purpose: Constructor, opens invisible file +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- +InvisibleTempFileStream::InvisibleTempFileStream(const std::string& Filename, + int flags, int mode) +#ifdef WIN32 + : FileStream(Filename, flags | O_TEMPORARY, mode) +#else + : FileStream(Filename, flags, mode) +#endif +{ + #ifndef WIN32 + if(unlink(Filename.c_str()) != 0) + { + MEMLEAKFINDER_NOT_A_LEAK(this); + THROW_EXCEPTION(CommonException, OSFileOpenError) + } + #endif +} diff --git a/lib/common/InvisibleTempFileStream.h b/lib/common/InvisibleTempFileStream.h new file mode 100644 index 00000000..bb6c3954 --- /dev/null +++ b/lib/common/InvisibleTempFileStream.h @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: InvisibleTempFileStream.h +// Purpose: FileStream interface to temporary files that +// delete themselves +// Created: 2006/10/13 +// +// -------------------------------------------------------------------------- + +#ifndef INVISIBLETEMPFILESTREAM__H +#define INVISIBLETEMPFILESTREAM__H + +#include "FileStream.h" + +class InvisibleTempFileStream : public FileStream +{ +public: + InvisibleTempFileStream(const std::string& Filename, +#ifdef WIN32 + int flags = (O_RDONLY | O_BINARY), +#else + int flags = O_RDONLY, +#endif + int mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)); + +private: + InvisibleTempFileStream(const InvisibleTempFileStream &rToCopy) + : FileStream(INVALID_FILE) + { /* do not call */ } +}; + +#endif // INVISIBLETEMPFILESTREAM__H + + diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp new file mode 100644 index 00000000..a0d1ec8c --- /dev/null +++ b/lib/common/Logging.cpp @@ -0,0 +1,766 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Logging.cpp +// Purpose: Generic logging core routines implementation +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <time.h> +#include <string.h> // for stderror + +// c.f. http://bugs.debian.org/512510 +#include <cstdio> + +#ifdef HAVE_SYSLOG_H + #include <syslog.h> +#endif +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif +#ifdef WIN32 + #include <process.h> +#endif + +#include <cstring> +#include <iomanip> + +#include "BoxTime.h" +#include "Logging.h" + +bool Logging::sLogToSyslog = false; +bool Logging::sLogToConsole = false; +bool Logging::sContextSet = false; + +bool HideExceptionMessageGuard::sHiddenState = false; + +std::vector<Logger*> Logging::sLoggers; +std::string Logging::sContext; +Console* Logging::spConsole = NULL; +Syslog* Logging::spSyslog = NULL; +Logging Logging::sGlobalLogging; // automatic initialisation +std::string Logging::sProgramName; +const Log::Category Logging::UNCATEGORISED("Uncategorised"); +std::auto_ptr<HideFileGuard> Logging::sapHideFileGuard; + +HideSpecificExceptionGuard::SuppressedExceptions_t + HideSpecificExceptionGuard::sSuppressedExceptions; + +Logging::Logging() +{ + ASSERT(!spConsole); + ASSERT(!spSyslog); + spConsole = new Console(); + spSyslog = new Syslog(); + sLogToConsole = true; + sLogToSyslog = true; +} + +Logging::~Logging() +{ + sLogToConsole = false; + sLogToSyslog = false; + delete spConsole; + delete spSyslog; + spConsole = NULL; + spSyslog = NULL; +} + +void Logging::ToSyslog(bool enabled) +{ + if (!sLogToSyslog && enabled) + { + Add(spSyslog); + } + + if (sLogToSyslog && !enabled) + { + Remove(spSyslog); + } + + sLogToSyslog = enabled; +} + +void Logging::ToConsole(bool enabled) +{ + if (!sLogToConsole && enabled) + { + Add(spConsole); + } + + if (sLogToConsole && !enabled) + { + Remove(spConsole); + } + + sLogToConsole = enabled; +} + +void Logging::FilterConsole(Log::Level level) +{ + spConsole->Filter(level); +} + +void Logging::FilterSyslog(Log::Level level) +{ + spSyslog->Filter(level); +} + +void Logging::Add(Logger* pNewLogger) +{ + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + if (*i == pNewLogger) + { + return; + } + } + + sLoggers.insert(sLoggers.begin(), pNewLogger); +} + +void Logging::Remove(Logger* pOldLogger) +{ + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + if (*i == pOldLogger) + { + sLoggers.erase(i); + return; + } + } +} + +void Logging::Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + std::string newMessage; + + if (sContextSet) + { + newMessage += "[" + sContext + "] "; + } + + newMessage += message; + + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + bool result = (*i)->Log(level, file, line, function, category, + newMessage); + if (!result) + { + return; + } + } +} + +void Logging::LogToSyslog(Log::Level level, const std::string& rFile, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + if (!sLogToSyslog) + { + return; + } + + std::string newMessage; + + if (sContextSet) + { + newMessage += "[" + sContext + "] "; + } + + newMessage += message; + + spSyslog->Log(level, rFile, line, function, category, newMessage); +} + +void Logging::SetContext(std::string context) +{ + sContext = context; + sContextSet = true; +} + +Log::Level Logging::GetNamedLevel(const std::string& rName) +{ + if (rName == "nothing") { return Log::NOTHING; } + else if (rName == "fatal") { return Log::FATAL; } + else if (rName == "error") { return Log::ERROR; } + else if (rName == "warning") { return Log::WARNING; } + else if (rName == "notice") { return Log::NOTICE; } + else if (rName == "info") { return Log::INFO; } + else if (rName == "trace") { return Log::TRACE; } + else if (rName == "everything") { return Log::EVERYTHING; } + else + { + BOX_ERROR("Unknown verbosity level: " << rName); + return Log::INVALID; + } +} + +void Logging::ClearContext() +{ + sContextSet = false; +} + +void Logging::SetProgramName(const std::string& rProgramName) +{ + sProgramName = rProgramName; + + for (std::vector<Logger*>::iterator i = sLoggers.begin(); + i != sLoggers.end(); i++) + { + (*i)->SetProgramName(rProgramName); + } +} + +void Logging::SetFacility(int facility) +{ + spSyslog->SetFacility(facility); +} + +Logger::Logger() +: mCurrentLevel(Log::EVERYTHING) +{ + Logging::Add(this); +} + +Logger::Logger(Log::Level Level) +: mCurrentLevel(Level) +{ + Logging::Add(this); +} + +Logger::~Logger() +{ + Logging::Remove(this); +} + +bool Logger::IsEnabled(Log::Level level) +{ + return (int)mCurrentLevel >= (int)level; +} + +bool Console::sShowTime = false; +bool Console::sShowTimeMicros = false; +bool Console::sShowTag = false; +bool Console::sShowPID = false; +std::string Console::sTag; + +void Console::SetProgramName(const std::string& rProgramName) +{ + sTag = rProgramName; +} + +void Console::SetShowTag(bool enabled) +{ + sShowTag = enabled; +} + +void Console::SetShowTime(bool enabled) +{ + sShowTime = enabled; +} + +void Console::SetShowTimeMicros(bool enabled) +{ + sShowTimeMicros = enabled; +} + +void Console::SetShowPID(bool enabled) +{ + sShowPID = enabled; +} + +bool Console::Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + if (level > GetLevel()) + { + return true; + } + + FILE* target = stdout; + std::ostringstream buf; + + if (sShowTime) + { + buf << FormatTime(GetCurrentBoxTime(), false, sShowTimeMicros); + buf << " "; + } + + if (sShowTag) + { + if (sShowPID) + { + buf << "[" << sTag << " " << getpid() << "] "; + } + else + { + buf << "[" << sTag << "] "; + } + } + else if (sShowPID) + { + buf << "[" << getpid() << "] "; + } + + if (level <= Log::FATAL) + { + buf << "FATAL: "; + } + else if (level <= Log::ERROR) + { + buf << "ERROR: "; + } + else if (level <= Log::WARNING) + { + buf << "WARNING: "; + } + else if (level <= Log::NOTICE) + { + buf << "NOTICE: "; + } + else if (level <= Log::INFO) + { + buf << "INFO: "; + } + else if (level <= Log::TRACE) + { + buf << "TRACE: "; + } + + buf << message; + + #ifdef WIN32 + std::string output = buf.str(); + 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); + + return true; +} + +bool Syslog::Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + if (level > GetLevel()) + { + return true; + } + + int syslogLevel = LOG_ERR; + + switch(level) + { + case Log::NOTHING: /* fall through */ + case Log::INVALID: /* fall through */ + case Log::FATAL: syslogLevel = LOG_CRIT; break; + case Log::ERROR: syslogLevel = LOG_ERR; break; + case Log::WARNING: syslogLevel = LOG_WARNING; break; + case Log::NOTICE: syslogLevel = LOG_NOTICE; break; + case Log::INFO: syslogLevel = LOG_INFO; break; + case Log::TRACE: /* fall through */ + case Log::EVERYTHING: syslogLevel = LOG_DEBUG; break; + } + + std::string msg; + + if (level <= Log::FATAL) + { + msg = "FATAL: "; + } + else if (level <= Log::ERROR) + { + msg = "ERROR: "; + } + else if (level <= Log::WARNING) + { + msg = "WARNING: "; + } + else if (level <= Log::NOTICE) + { + msg = "NOTICE: "; + } + + msg += message; + + syslog(syslogLevel, "%s", msg.c_str()); + + return true; +} + +Syslog::Syslog() : mFacility(LOG_LOCAL6) +{ + ::openlog("Box Backup", LOG_PID, mFacility); +} + +Syslog::~Syslog() +{ + Shutdown(); +} + +void Syslog::Shutdown() +{ + ::closelog(); +} + +void Syslog::SetProgramName(const std::string& rProgramName) +{ + mName = rProgramName; + ::closelog(); + ::openlog(mName.c_str(), LOG_PID, mFacility); +} + +void Syslog::SetFacility(int facility) +{ + mFacility = facility; + ::closelog(); + ::openlog(mName.c_str(), LOG_PID, mFacility); +} + +int Syslog::GetNamedFacility(const std::string& rFacility) +{ + #define CASE_RETURN(x) if (rFacility == #x) { return LOG_ ## x; } + CASE_RETURN(LOCAL0) + CASE_RETURN(LOCAL1) + CASE_RETURN(LOCAL2) + CASE_RETURN(LOCAL3) + CASE_RETURN(LOCAL4) + CASE_RETURN(LOCAL5) + CASE_RETURN(LOCAL6) + CASE_RETURN(DAEMON) + #undef CASE_RETURN + + BOX_ERROR("Unknown log facility '" << rFacility << "', " + "using default LOCAL6"); + return LOG_LOCAL6; +} + +bool FileLogger::Log(Log::Level Level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + if (mLogFile.StreamClosed()) + { + /* skip this logger to allow logging failure to open + the log file, without causing an infinite loop */ + return true; + } + + if (Level > GetLevel()) + { + return true; + } + + /* avoid infinite loop if this throws an exception */ + Log::Level oldLevel = GetLevel(); + Filter(Log::NOTHING); + + std::ostringstream buf; + buf << FormatTime(GetCurrentBoxTime(), true, false); + buf << " "; + + if (Level <= Log::FATAL) + { + buf << "[FATAL] "; + } + else if (Level <= Log::ERROR) + { + buf << "[ERROR] "; + } + else if (Level <= Log::WARNING) + { + buf << "[WARNING] "; + } + else if (Level <= Log::NOTICE) + { + buf << "[NOTICE] "; + } + else if (Level <= Log::INFO) + { + buf << "[INFO] "; + } + else if (Level <= Log::TRACE) + { + buf << "[TRACE] "; + } + + buf << message << "\n"; + std::string output = buf.str(); + + #ifdef WIN32 + ConvertUtf8ToConsole(output.c_str(), output); + #endif + + mLogFile.Write(output.c_str(), output.length()); + + // no infinite loop, reset to saved logging level + Filter(oldLevel); + return true; +} + +std::string PrintEscapedBinaryData(const std::string& rInput) +{ + std::ostringstream output; + + for (size_t i = 0; i < rInput.length(); i++) + { + if (isprint(rInput[i])) + { + output << rInput[i]; + } + else + { + output << "\\x" << std::hex << std::setw(2) << + std::setfill('0') << (int) rInput[i] << + std::dec; + } + } + + return output.str(); +} + +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; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Logging::OptionParser::GetOptionString() +// Purpose: Returns the valid Getopt command-line options +// that Logging::OptionParser::ProcessOption will handle. +// Created: 2014/04/09 +// +// -------------------------------------------------------------------------- +std::string Logging::OptionParser::GetOptionString() +{ + return "L:NPqQt:TUvVW:"; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Logging::OptionParser::ProcessOption(signed int option) +// Purpose: Processes the supplied option (equivalent to the +// return code from getopt()). Return zero if the +// option was handled successfully, or nonzero to +// abort the program with that return value. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +int Logging::OptionParser::ProcessOption(signed int option) +{ + switch(option) + { + case 'L': + { + if(sapHideFileGuard.get()) + { + sapHideFileGuard->Add(optarg); + } + else + { + sapHideFileGuard.reset(new HideFileGuard( + optarg, true)); // HideAllButSelected + } + } + break; + + case 'N': + { + mTruncateLogFile = true; + } + break; + + case 'P': + { + Console::SetShowPID(true); + } + break; + + case 'q': + { + if(mCurrentLevel == Log::NOTHING) + { + BOX_FATAL("Too many '-q': " + "Cannot reduce logging " + "level any more"); + return 2; + } + mCurrentLevel--; + } + break; + + case 'Q': + { + mCurrentLevel = Log::NOTHING; + } + break; + + case 't': + { + Logging::SetProgramName(optarg); + Console::SetShowTag(true); + } + break; + + case 'T': + { + Console::SetShowTime(true); + } + break; + + case 'U': + { + Console::SetShowTime(true); + Console::SetShowTimeMicros(true); + } + break; + + case 'v': + { + if(mCurrentLevel == Log::EVERYTHING) + { + BOX_FATAL("Too many '-v': " + "Cannot increase logging " + "level any more"); + return 2; + } + mCurrentLevel++; + } + break; + + case 'V': + { + mCurrentLevel = Log::EVERYTHING; + } + break; + + case 'W': + { + mCurrentLevel = Logging::GetNamedLevel(optarg); + if (mCurrentLevel == Log::INVALID) + { + BOX_FATAL("Invalid logging level: " << optarg); + return 2; + } + } + break; + + case '?': + { + BOX_FATAL("Unknown option on command line: " + << "'" << (char)optopt << "'"); + return 2; + } + break; + + default: + { + BOX_FATAL("Unknown error in getopt: returned " + << "'" << option << "'"); + return 1; + } + } + + // If we didn't explicitly return an error code above, then the option + // was fine, so return 0 to continue processing. + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Logging::OptionParser::GetUsageString() +// Purpose: Returns a string suitable for displaying as part +// of a program's command-line usage help message, +// describing the logging options. +// Created: 2014/04/09 +// +// -------------------------------------------------------------------------- +std::string Logging::OptionParser::GetUsageString() +{ + return + " -L <file> Filter out log messages except from specified file, can repeat\n" + " (for example, -L " __FILE__ ")\n" + " -N Truncate log file at startup and on backup start\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"; +} + +bool HideCategoryGuard::Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + std::list<Log::Category>::iterator i = std::find(mCategories.begin(), + mCategories.end(), category); + // Return false if category is in our list, to suppress further + // logging (thus, return true if it's not in our list, i.e. we + // found nothing, to allow it). + return (i == mCategories.end()); +} + +bool HideFileGuard::Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) +{ + std::list<std::string>::iterator i = std::find(mFileNames.begin(), + mFileNames.end(), file); + bool allow_log_message; + if(mHideAllButSelected) + { + // Return true if filename is in our list, to allow further + // logging (thus, return false if it's not in our list, i.e. we + // found nothing, to suppress it). + allow_log_message = (i != mFileNames.end()); + } + else + { + // Return false if filename is in our list, to suppress further + // logging (thus, return true if it's not in our list, i.e. we + // found nothing, to allow it). + allow_log_message = (i == mFileNames.end()); + } + return allow_log_message; +} + diff --git a/lib/common/Logging.h b/lib/common/Logging.h new file mode 100644 index 00000000..01358617 --- /dev/null +++ b/lib/common/Logging.h @@ -0,0 +1,672 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Logging.h +// Purpose: Generic logging core routines declarations and macros +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +#ifndef LOGGING__H +#define LOGGING__H + +#include <assert.h> + +#include <algorithm> +#include <cerrno> +#include <cstring> +#include <iomanip> +#include <list> +#include <sstream> +#include <vector> + +#include "FileStream.h" + +#define BOX_LOG(level, stuff) \ +{ \ + std::ostringstream _box_log_line; \ + _box_log_line << stuff; \ + Logging::Log(level, __FILE__, __LINE__, __FUNCTION__, \ + Logging::UNCATEGORISED, _box_log_line.str()); \ +} + +#define BOX_LOG_CATEGORY(level, category, stuff) \ +{ \ + std::ostringstream _box_log_line; \ + _box_log_line << stuff; \ + Logging::Log(level, __FILE__, __LINE__, __FUNCTION__, \ + category, _box_log_line.str()); \ +} + +#define BOX_SYSLOG(level, stuff) \ +{ \ + std::ostringstream _box_log_line; \ + _box_log_line << stuff; \ + Logging::LogToSyslog(level, __FILE__, __LINE__, __FUNCTION__, \ + Logging::UNCATEGORISED, _box_log_line.str()); \ +} + +#define BOX_FATAL(stuff) BOX_LOG(Log::FATAL, stuff) +#define BOX_ERROR(stuff) BOX_LOG(Log::ERROR, stuff) +#define BOX_WARNING(stuff) BOX_LOG(Log::WARNING, stuff) +#define BOX_NOTICE(stuff) BOX_LOG(Log::NOTICE, stuff) +#define BOX_INFO(stuff) BOX_LOG(Log::INFO, stuff) +#define BOX_TRACE(stuff) BOX_LOG(Log::TRACE, stuff) + +#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)) + +#ifdef WIN32 + #define BOX_WIN_ERRNO_MESSAGE(error_number, stuff) \ + stuff << ": " << GetErrorMessage(error_number) + #define BOX_NATIVE_ERRNO_MESSAGE(error_number, stuff) \ + BOX_WIN_ERRNO_MESSAGE(error_number, stuff) + #define BOX_LOG_WIN_ERROR(stuff) \ + BOX_ERROR(BOX_WIN_ERRNO_MESSAGE(GetLastError(), stuff)) + #define BOX_LOG_WIN_WARNING(stuff) \ + BOX_WARNING(BOX_WIN_ERRNO_MESSAGE(GetLastError(), stuff)) + #define BOX_LOG_WIN_ERROR_NUMBER(stuff, number) \ + BOX_ERROR(BOX_WIN_ERRNO_MESSAGE(number, stuff)) + #define BOX_LOG_WIN_WARNING_NUMBER(stuff, number) \ + BOX_WARNING(BOX_WIN_ERRNO_MESSAGE(number, stuff)) + #define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_WIN_ERROR(stuff) + #define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_WIN_WARNING(stuff) + #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) + #define EMU_ERRNO winerrno + #define THROW_EMU_ERROR(message, exception, subtype) \ + THROW_EXCEPTION_MESSAGE(exception, subtype, \ + BOX_NATIVE_ERRNO_MESSAGE(EMU_ERRNO, message)) +#else + #define BOX_NATIVE_ERRNO_MESSAGE(error_number, stuff) \ + BOX_SYS_ERRNO_MESSAGE(error_number, stuff) + #define BOX_LOG_NATIVE_ERROR(stuff) BOX_LOG_SYS_ERROR(stuff) + #define BOX_LOG_NATIVE_WARNING(stuff) BOX_LOG_SYS_WARNING(stuff) + #define EMU_ERRNO errno + #define THROW_EMU_ERROR(message, exception, subtype) \ + THROW_EXCEPTION_MESSAGE(exception, subtype, \ + BOX_SYS_ERRNO_MESSAGE(EMU_ERRNO, message)) +#endif + +#define THROW_EMU_FILE_ERROR(message, filename, exception, subtype) \ + THROW_EMU_ERROR(BOX_FILE_MESSAGE(filename, message), \ + exception, subtype) + +#ifdef WIN32 +# define BOX_SOCKET_ERROR_MESSAGE(_type, _name, _port, stuff) \ + BOX_WIN_ERRNO_MESSAGE(WSAGetLastError(), stuff << " (type " << _type << \ + ", name " << _name << ", port " << _port << ")") +#else +# define BOX_SOCKET_ERROR_MESSAGE(_type, _name, _port, stuff) \ + BOX_SYS_ERROR_MESSAGE(stuff << " (type " << _type << ", name " << _name << \ + ", port " << _port << ")") +#endif + +#define BOX_FORMAT_HEX32(number) \ + std::hex << \ + std::showbase << \ + std::internal << \ + std::setw(10) << \ + std::setfill('0') << \ + (number) << \ + std::dec + +#define BOX_FORMAT_ACCOUNT(accno) \ + BOX_FORMAT_HEX32(accno) + +#define BOX_FORMAT_OBJECTID(objectid) \ + std::hex << \ + std::showbase << \ + (objectid) << \ + std::dec + +#define BOX_FORMAT_TIMESPEC(timespec) \ + timespec.tv_sec << \ + "." << \ + std::setw(6) << \ + std::setfill('0') << \ + timespec.tv_usec + +#define BOX_FORMAT_MICROSECONDS(t) \ + (int)((t) / 1000000) << "." << \ + std::setw(3) << \ + std::setfill('0') << \ + (int)((t % 1000000) / 1000) << " seconds" + +#undef ERROR + +namespace Log +{ + enum Level + { + NOTHING = 1, + FATAL, + ERROR, + WARNING, + NOTICE, + INFO, + TRACE, + EVERYTHING, + INVALID = -1 + }; + + class Category { + private: + std::string mName; + + public: + Category(const std::string& name) + : mName(name) + { } + const std::string& ToString() { return mName; } + bool operator==(const Category& other) { return mName == other.mName; } + }; +} + +// -------------------------------------------------------------------------- +// +// Class +// Name: Logger +// Purpose: Abstract base class for log targets +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Logger +{ + private: + Log::Level mCurrentLevel; + + public: + Logger(); + Logger(Log::Level level); + virtual ~Logger(); + + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) = 0; + + void Filter(Log::Level level) + { + mCurrentLevel = level; + } + + virtual const char* GetType() = 0; + Log::Level GetLevel() { return mCurrentLevel; } + bool IsEnabled(Log::Level level); + + virtual void SetProgramName(const std::string& rProgramName) = 0; + + class LevelGuard + { + private: + Logger& mLogger; + Log::Level mOldLevel; + + public: + LevelGuard(Logger& Logger, Log::Level newLevel = Log::INVALID) + : mLogger(Logger) + { + mOldLevel = Logger.GetLevel(); + if (newLevel != Log::INVALID) + { + Logger.Filter(newLevel); + } + } + ~LevelGuard() + { + mLogger.Filter(mOldLevel); + } + }; +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Console +// Purpose: Console logging target +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Console : public Logger +{ + private: + static bool sShowTag; + static bool sShowTime; + static bool sShowTimeMicros; + static bool sShowPID; + static std::string sTag; + + public: + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + virtual const char* GetType() { return "Console"; } + virtual void SetProgramName(const std::string& rProgramName); + + static void SetShowTag(bool enabled); + static void SetShowTime(bool enabled); + static void SetShowTimeMicros(bool enabled); + static void SetShowPID(bool enabled); + static bool GetShowTag() { return sShowTag; } +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Syslog +// Purpose: Syslog (or Windows Event Viewer) logging target +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Syslog : public Logger +{ + private: + std::string mName; + int mFacility; + + public: + Syslog(); + virtual ~Syslog(); + + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + virtual const char* GetType() { return "Syslog"; } + virtual void SetProgramName(const std::string& rProgramName); + virtual void SetFacility(int facility); + virtual void Shutdown(); + static int GetNamedFacility(const std::string& rFacility); +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Capture +// Purpose: Keeps log messages for analysis in tests. +// Created: 2014/03/08 +// +// -------------------------------------------------------------------------- + +class Capture : public Logger +{ + public: + struct Message + { + Message(const Log::Category& category) + : mCategory(category) { } + Log::Level level; + std::string file; + int line; + std::string function; + Log::Category mCategory; + std::string message; + }; + + private: + std::vector<Message> mMessages; + + public: + virtual ~Capture() { } + + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) + { + Message msg(category); + msg.level = level; + msg.file = file; + msg.line = line; + msg.function = function; + msg.message = message; + mMessages.push_back(msg); + return true; + } + virtual const char* GetType() { return "Capture"; } + virtual void SetProgramName(const std::string& rProgramName) { } + const std::vector<Message>& GetMessages() const { return mMessages; } + std::string GetString() const + { + std::ostringstream oss; + for (std::vector<Message>::const_iterator i = mMessages.begin(); + i != mMessages.end(); i++) + { + oss << i->message << "\n"; + } + return oss.str(); + } +}; + +// Forward declaration +class HideFileGuard; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Logging +// Purpose: Static logging helper, keeps track of enabled loggers +// and distributes log messages to them. +// Created: 2006/12/16 +// +// -------------------------------------------------------------------------- + +class Logging +{ + private: + static std::vector<Logger*> sLoggers; + static bool sLogToSyslog, sLogToConsole; + static std::string sContext; + static bool sContextSet; + static Console* spConsole; + static Syslog* spSyslog; + static Logging sGlobalLogging; + static std::string sProgramName; + static std::auto_ptr<HideFileGuard> sapHideFileGuard; + + public: + Logging (); + ~Logging(); + static void ToSyslog (bool enabled); + static void ToConsole (bool enabled); + static void FilterSyslog (Log::Level level); + static void FilterConsole (Log::Level level); + static void Add (Logger* pNewLogger); + static void Remove (Logger* pOldLogger); + static void Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + static void LogToSyslog(Log::Level level, const std::string& rFile, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + static void SetContext(std::string context); + static void ClearContext(); + static Log::Level GetNamedLevel(const std::string& rName); + static void SetProgramName(const std::string& rProgramName); + static std::string GetProgramName() { return sProgramName; } + static void SetFacility(int facility); + static Console& GetConsole() { return *spConsole; } + static Syslog& GetSyslog() { return *spSyslog; } + + class ShowTagOnConsole + { + private: + bool mOldShowTag; + + public: + ShowTagOnConsole() + : mOldShowTag(Console::GetShowTag()) + { + Console::SetShowTag(true); + } + ~ShowTagOnConsole() + { + Console::SetShowTag(mOldShowTag); + } + }; + + class Tagger + { + private: + std::string mOldTag; + bool mReplace; + + public: + Tagger() + : mOldTag(Logging::GetProgramName()), + mReplace(false) + { + } + Tagger(const std::string& rTempTag, bool replace = false) + : mOldTag(Logging::GetProgramName()), + mReplace(replace) + { + Change(rTempTag); + } + ~Tagger() + { + Logging::SetProgramName(mOldTag); + } + + void Change(const std::string& newTempTag) + { + if(mReplace || mOldTag.empty()) + { + Logging::SetProgramName(newTempTag); + } + else + { + Logging::SetProgramName(mOldTag + " " + newTempTag); + } + } + }; + + class TempLoggerGuard + { + private: + Logger* mpLogger; + + public: + TempLoggerGuard(Logger* pLogger) + : mpLogger(pLogger) + { + Logging::Add(mpLogger); + } + ~TempLoggerGuard() + { + Logging::Remove(mpLogger); + } + }; + + // Process global options + static std::string GetOptionString(); + static int ProcessOption(signed int option); + static std::string GetUsageString(); + + // -------------------------------------------------------------------------- + // + // Class + // Name: Logging::OptionParser + // Purpose: Process command-line options, some global, some local + // Created: 2014/04/09 + // + // -------------------------------------------------------------------------- + class OptionParser + { + public: + OptionParser(Log::Level InitialLevel = + #ifdef BOX_RELEASE_BUILD + Log::NOTICE + #else + Log::INFO + #endif + ) + : mCurrentLevel(InitialLevel), + mTruncateLogFile(false) + { } + + static std::string GetOptionString(); + int ProcessOption(signed int option); + static std::string GetUsageString(); + int mCurrentLevel; // need an int to do math with + bool mTruncateLogFile; + Log::Level GetCurrentLevel() + { + return (Log::Level) mCurrentLevel; + } + }; + + static const Log::Category UNCATEGORISED; +}; + +class FileLogger : public Logger +{ + private: + FileStream mLogFile; + FileLogger(const FileLogger& forbidden) + : mLogFile("") { /* do not call */ } + + public: + FileLogger(const std::string& rFileName, Log::Level Level, bool append) + : Logger(Level), + mLogFile(rFileName, O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC)) + { } + + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + + virtual const char* GetType() { return "FileLogger"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +class HideExceptionMessageGuard +{ + public: + HideExceptionMessageGuard() + { + mOldHiddenState = sHiddenState; + sHiddenState = true; + } + ~HideExceptionMessageGuard() + { + sHiddenState = mOldHiddenState; + } + static bool ExceptionsHidden() { return sHiddenState; } + + private: + static bool sHiddenState; + bool mOldHiddenState; +}; + +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); +}; + +class HideCategoryGuard : public Logger +{ + private: + std::list<Log::Category> mCategories; + HideCategoryGuard(const HideCategoryGuard& other); // no copying + HideCategoryGuard& operator=(const HideCategoryGuard& other); // no assignment + + public: + HideCategoryGuard(const Log::Category& category) + { + mCategories.push_back(category); + Logging::Add(this); + } + ~HideCategoryGuard() + { + Logging::Remove(this); + } + void Add(const Log::Category& category) + { + mCategories.push_back(category); + } + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + virtual const char* GetType() { return "HideCategoryGuard"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +class HideFileGuard : public Logger +{ + private: + std::list<std::string> mFileNames; + HideFileGuard(const HideFileGuard& other); // no copying + HideFileGuard& operator=(const HideFileGuard& other); // no assignment + bool mHideAllButSelected; + + public: + HideFileGuard(const std::string& rFileName, bool HideAllButSelected = false) + : mHideAllButSelected(HideAllButSelected) + { + mFileNames.push_back(rFileName); + Logging::Add(this); + } + ~HideFileGuard() + { + Logging::Remove(this); + } + void Add(const std::string& rFileName) + { + mFileNames.push_back(rFileName); + } + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message); + virtual const char* GetType() { return "HideFileGuard"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +std::string PrintEscapedBinaryData(const std::string& rInput); + +#endif // LOGGING__H diff --git a/lib/common/MainHelper.h b/lib/common/MainHelper.h new file mode 100644 index 00000000..0303090e --- /dev/null +++ b/lib/common/MainHelper.h @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MainHelper.h +// Purpose: Helper stuff for main() programs +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- + +#ifndef MAINHELPER__H +#define MAINHELPER__H + +#include <stdio.h> + +#ifdef NEED_BOX_VERSION_H +# include "BoxVersion.h" +#endif + +#include "BoxException.h" +#include "Logging.h" + +#define MAINHELPER_START \ + if(argc == 2 && ::strcmp(argv[1], "--version") == 0) \ + { printf(BOX_VERSION "\n"); return 0; } \ + MEMLEAKFINDER_INIT \ + MEMLEAKFINDER_START \ + try { + +#define MAINHELPER_END \ + } catch(BoxException &e) { \ + BOX_FATAL(e.what() << ": " << e.GetMessage()); \ + return 1; \ + } catch(std::exception &e) { \ + BOX_FATAL(e.what()); \ + return 1; \ + } catch(...) { \ + BOX_FATAL("UNKNOWN"); \ + return 1; \ + } + +#ifdef BOX_MEMORY_LEAK_TESTING + #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) \ + memleakfinder_setup_exit_report(file, marker); +#else + #define MAINHELPER_SETUP_MEMORY_LEAK_EXIT_REPORT(file, marker) +#endif // BOX_MEMORY_LEAK_TESTING + + +#endif // MAINHELPER__H + diff --git a/lib/common/Makefile.extra b/lib/common/Makefile.extra new file mode 100644 index 00000000..cc3f3a7a --- /dev/null +++ b/lib/common/Makefile.extra @@ -0,0 +1,11 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CommonException.h autogen_CommonException.cpp: $(MAKEEXCEPTION) CommonException.txt + $(_PERL) $(MAKEEXCEPTION) CommonException.txt + +# AUTOGEN SEEDING +autogen_ConversionException.h autogen_ConversionException.cpp: $(MAKEEXCEPTION) ConversionException.txt + $(_PERL) $(MAKEEXCEPTION) ConversionException.txt + diff --git a/lib/common/MemBlockStream.cpp b/lib/common/MemBlockStream.cpp new file mode 100644 index 00000000..f49ac96f --- /dev/null +++ b/lib/common/MemBlockStream.cpp @@ -0,0 +1,268 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemBlockStream.cpp +// Purpose: Stream out data from any memory block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "MemBlockStream.h" +#include "CommonException.h" +#include "StreamableMemBlock.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream() +// Purpose: Constructor with no contents +// Created: 2012/11/07 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream() +: mpBuffer(NULL), + mBytesInBuffer(0), + mReadPosition(0) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream() +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const void *pBuffer, int Size) + : mpBuffer((char*)pBuffer), + mBytesInBuffer(Size), + mReadPosition(0) +{ + ASSERT(pBuffer != 0); + ASSERT(Size >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const std::string& rMessage) +// Purpose: Convenience constructor for sending a simple string. +// Copies the string, so you can pass a temporary in. +// Created: 2014/01/20 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const std::string& rMessage) +: mReadPosition(0) +{ + mTempBuffer.Write(rMessage.c_str(), rMessage.size()); + mTempBuffer.SetForReading(); + mpBuffer = (const char *)(mTempBuffer.GetBuffer()); + mBytesInBuffer = rMessage.size(); + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const StreamableMemBlock &rBlock) + : mpBuffer((char*)rBlock.GetBuffer()), + mBytesInBuffer(rBlock.GetSize()), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const StreamableMemBlock &) +// Purpose: Constructor (doesn't copy block, careful with lifetimes) +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const CollectInBufferStream &rBuffer) + : mpBuffer((char*)rBuffer.GetBuffer()), + mBytesInBuffer(rBuffer.GetSize()), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::MemBlockStream(const MemBlockStream &) +// Purpose: Copy constructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::MemBlockStream(const MemBlockStream &rToCopy) + : mpBuffer(rToCopy.mpBuffer), + mBytesInBuffer(rToCopy.mBytesInBuffer), + mReadPosition(0) +{ + ASSERT(mpBuffer != 0); + ASSERT(mBytesInBuffer >= 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::~MemBlockStream() +// Purpose: Destructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +MemBlockStream::~MemBlockStream() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Read(void *, int, int) +// Purpose: As interface. But only works in read phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +int MemBlockStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Adjust to number of bytes left + if(NBytes > (mBytesInBuffer - mReadPosition)) + { + NBytes = (mBytesInBuffer - mReadPosition); + } + ASSERT(NBytes >= 0); + if(NBytes <= 0) return 0; // careful now + + // Copy in the requested number of bytes and adjust the read pointer + ::memcpy(pBuffer, mpBuffer + mReadPosition, NBytes); + mReadPosition += NBytes; + + return NBytes; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::BytesLeftToRead() +// Purpose: As interface. But only works in read phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type MemBlockStream::BytesLeftToRead() +{ + return (mBytesInBuffer - mReadPosition); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Write(void *, int) +// Purpose: As interface. But only works in write phase +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void MemBlockStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, MemBlockStreamNotSupported) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::GetPosition() +// Purpose: In write phase, returns the number of bytes written, in read +// phase, the number of bytes to go +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type MemBlockStream::GetPosition() const +{ + return mReadPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::Seek(pos_type, int) +// Purpose: As interface. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void MemBlockStream::Seek(pos_type Offset, int SeekType) +{ + int newPos = 0; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newPos = Offset; + break; + case IOStream::SeekType_Relative: + newPos = mReadPosition + Offset; + break; + case IOStream::SeekType_End: + newPos = mBytesInBuffer + Offset; + break; + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + break; + } + + // Make sure it doesn't go over + if(newPos > mBytesInBuffer) + { + newPos = mBytesInBuffer; + } + // or under + if(newPos < 0) + { + newPos = 0; + } + + // Set the new read position + mReadPosition = newPos; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::StreamDataLeft() +// Purpose: As interface +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +bool MemBlockStream::StreamDataLeft() +{ + return mReadPosition < mBytesInBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: MemBlockStream::StreamClosed() +// Purpose: As interface +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +bool MemBlockStream::StreamClosed() +{ + return true; +} + diff --git a/lib/common/MemBlockStream.h b/lib/common/MemBlockStream.h new file mode 100644 index 00000000..1ba4b0a6 --- /dev/null +++ b/lib/common/MemBlockStream.h @@ -0,0 +1,60 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemBlockStream.h +// Purpose: Stream out data from any memory block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#ifndef MEMBLOCKSTREAM__H +#define MEMBLOCKSTREAM__H + +#include "CollectInBufferStream.h" +#include "IOStream.h" + +class StreamableMemBlock; + +// -------------------------------------------------------------------------- +// +// Class +// Name: MemBlockStream +// Purpose: Stream out data from any memory block -- be careful the lifetime +// of the block is greater than the lifetime of this stream. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +class MemBlockStream : public IOStream +{ +public: + MemBlockStream(); + MemBlockStream(const void *pBuffer, int Size); + MemBlockStream(const std::string& rMessage); + MemBlockStream(const StreamableMemBlock &rBlock); + MemBlockStream(const CollectInBufferStream &rBuffer); + MemBlockStream(const MemBlockStream &rToCopy); + ~MemBlockStream(); +public: + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + virtual const void* GetBuffer() const { return mpBuffer; } + virtual int GetSize() const { return mBytesInBuffer; } + +private: + // Use mTempBuffer when we need to hold a copy of the memory block, + // and free it ourselves when done. + CollectInBufferStream mTempBuffer; + const char *mpBuffer; + int mBytesInBuffer; + int mReadPosition; +}; + +#endif // MEMBLOCKSTREAM__H + diff --git a/lib/common/MemLeakFindOff.h b/lib/common/MemLeakFindOff.h new file mode 100644 index 00000000..1cc98bac --- /dev/null +++ b/lib/common/MemLeakFindOff.h @@ -0,0 +1,27 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFindOff.h +// Purpose: Switch memory leak finding off +// Created: 13/1/04 +// +// -------------------------------------------------------------------------- + +// no header guard + +#ifdef BOX_MEMORY_LEAK_TESTING + +#undef new + +#ifndef MEMLEAKFINDER_FULL_MALLOC_MONITORING + #ifdef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #undef malloc + #undef realloc + #undef free + #undef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #endif +#endif + +#undef MEMLEAKFINDER_ENABLED + +#endif diff --git a/lib/common/MemLeakFindOn.h b/lib/common/MemLeakFindOn.h new file mode 100644 index 00000000..f1113184 --- /dev/null +++ b/lib/common/MemLeakFindOn.h @@ -0,0 +1,26 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFindOn.h +// Purpose: Switch memory leak finding on +// Created: 13/1/04 +// +// -------------------------------------------------------------------------- + +// no header guard + +#ifdef BOX_MEMORY_LEAK_TESTING + +#define new DEBUG_NEW + +#ifndef MEMLEAKFINDER_MALLOC_MONITORING_DEFINED + #define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__) + #define calloc(X, Y) memleakfinder_calloc(X, Y, __FILE__, __LINE__) + #define realloc memleakfinder_realloc + #define free memleakfinder_free + #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED +#endif + +#define MEMLEAKFINDER_ENABLED + +#endif diff --git a/lib/common/MemLeakFinder.h b/lib/common/MemLeakFinder.h new file mode 100644 index 00000000..07b52e26 --- /dev/null +++ b/lib/common/MemLeakFinder.h @@ -0,0 +1,68 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MemLeakFinder.h +// Purpose: Memory leak finder +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef MEMLEAKFINDER__H +#define MEMLEAKFINDER__H + +#ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING + // include stdlib now, to avoid problems with having the macros defined already + #include <cstdlib> +#endif + +// global enable flag +extern bool memleakfinder_global_enable; + +class MemLeakSuppressionGuard +{ + public: + MemLeakSuppressionGuard(); + ~MemLeakSuppressionGuard(); +}; + +extern "C" +{ + void *memleakfinder_malloc(size_t size, const char *file, int line); + void *memleakfinder_calloc(size_t blocks, size_t size, const char *file, int line); + void *memleakfinder_realloc(void *ptr, size_t size); + void memleakfinder_free(void *ptr); +} + +void memleakfinder_init(); + +int memleakfinder_numleaks(); + +void memleakfinder_report_usage_summary(); + +void memleakfinder_reportleaks(); + +void memleakfinder_reportleaks_appendfile(const char *filename, const char *markertext); + +void memleakfinder_setup_exit_report(const std::string& filename, const char *markertext); + +void memleakfinder_startsectionmonitor(); + +void memleakfinder_traceblocksinsection(); + +void memleakfinder_notaleak(void *ptr); + +void *operator new (size_t size, const char *file, int line); +void *operator new[](size_t size, const char *file, int line); + +// Define the malloc functions now, if required. These should match the definitions +// in MemLeakFindOn.h. +#ifdef MEMLEAKFINDER_FULL_MALLOC_MONITORING + #define malloc(X) memleakfinder_malloc(X, __FILE__, __LINE__) + #define calloc(X, Y) memleakfinder_calloc(X, Y, __FILE__, __LINE__) + #define realloc memleakfinder_realloc + #define free memleakfinder_free + #define MEMLEAKFINDER_MALLOC_MONITORING_DEFINED +#endif + +#endif // MEMLEAKFINDER__H + diff --git a/lib/common/NamedLock.cpp b/lib/common/NamedLock.cpp new file mode 100644 index 00000000..8e672ff5 --- /dev/null +++ b/lib/common/NamedLock.cpp @@ -0,0 +1,299 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: NamedLock.cpp +// Purpose: A global named lock, implemented as a lock file in +// file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <fcntl.h> +#include <errno.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#ifdef HAVE_FLOCK + #include <sys/file.h> +#endif + +#include "CommonException.h" +#include "NamedLock.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::NamedLock() +// Purpose: Constructor +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +NamedLock::NamedLock() +#ifdef WIN32 +: mFileDescriptor(INVALID_HANDLE_VALUE) +#else +: mFileDescriptor(-1) +#endif +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::~NamedLock() +// Purpose: Destructor (automatically unlocks if locked) +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +NamedLock::~NamedLock() +{ +#ifdef WIN32 + if(mFileDescriptor != INVALID_HANDLE_VALUE) +#else + if(mFileDescriptor != -1) +#endif + { + ReleaseLock(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::TryAndGetLock(const char *, int) +// Purpose: Tries to get a lock on the name in the file system. +// IMPORTANT NOTE: If a file exists with this name, it +// will be deleted. +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +bool NamedLock::TryAndGetLock(const std::string& rFilename, int mode) +{ + // Check +#ifdef WIN32 + if(mFileDescriptor != INVALID_HANDLE_VALUE) +#else + if(mFileDescriptor != -1) +#endif + { + THROW_EXCEPTION(CommonException, NamedLockAlreadyLockingSomething) + } + + mFileName = rFilename; + + // See if the lock can be got + int flags = O_WRONLY | O_CREAT | O_TRUNC; + +#if HAVE_DECL_O_EXLOCK + flags |= O_NONBLOCK | O_EXLOCK; + BOX_TRACE("Trying to create lockfile " << rFilename << " using O_EXLOCK"); +#elif defined BOX_OPEN_LOCK + flags |= BOX_OPEN_LOCK; + BOX_TRACE("Trying to create lockfile " << rFilename << " using BOX_OPEN_LOCK"); +#elif !HAVE_DECL_F_SETLK && !defined HAVE_FLOCK + // We have no other way to get a lock, so all we can do is fail if + // the file already exists, and take the risk of stale locks. + flags |= O_EXCL; + BOX_TRACE("Trying to create lockfile " << rFilename << " using O_EXCL"); +#else + BOX_TRACE("Trying to create lockfile " << rFilename << " without special flags"); +#endif + +#ifdef WIN32 + HANDLE fd = openfile(rFilename.c_str(), flags, mode); + if(fd == INVALID_HANDLE_VALUE) +#else + int fd = ::open(rFilename.c_str(), flags, mode); + if(fd == -1) +#endif +#if HAVE_DECL_O_EXLOCK + { // if() + if(errno == EWOULDBLOCK) + { + // Lockfile already exists, and we tried to open it + // exclusively, which means we failed to lock it. + BOX_NOTICE("Failed to lock lockfile with O_EXLOCK: " << rFilename + << ": already locked by another process?"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to open lockfile with O_EXLOCK", + rFilename, CommonException, OSFileError); + } + } +#else // !HAVE_DECL_O_EXLOCK + { // if() +# if defined BOX_OPEN_LOCK + if(errno == EBUSY) +# else // !BOX_OPEN_LOCK + if(errno == EEXIST && (flags & O_EXCL)) +# endif + { + // Lockfile already exists, and we tried to open it + // exclusively, which means we failed to lock it. + BOX_NOTICE("Failed to lock lockfile with O_EXCL: " << rFilename + << ": already locked by another process?"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to open lockfile with O_EXCL", + rFilename, CommonException, OSFileError); + } + } + + try + { +# ifdef HAVE_FLOCK + BOX_TRACE("Trying to lock lockfile " << rFilename << " using flock()"); + if(::flock(fd, LOCK_EX | LOCK_NB) != 0) + { + if(errno == EWOULDBLOCK) + { + ::close(fd); + BOX_NOTICE("Failed to lock lockfile with flock(): " << rFilename + << ": already locked by another process"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to lock lockfile with flock()", + rFilename, CommonException, OSFileError); + } + } +# elif HAVE_DECL_F_SETLK + struct flock desc; + desc.l_type = F_WRLCK; + desc.l_whence = SEEK_SET; + desc.l_start = 0; + desc.l_len = 0; + BOX_TRACE("Trying to lock lockfile " << rFilename << " using fcntl()"); + if(::fcntl(fd, F_SETLK, &desc) != 0) + { + if(errno == EAGAIN) + { + ::close(fd); + BOX_NOTICE("Failed to lock lockfile with fcntl(): " << rFilename + << ": already locked by another process"); + return false; + } + else + { + THROW_SYS_FILE_ERROR("Failed to lock lockfile with fcntl()", + rFilename, CommonException, OSFileError); + } + } +# endif + } + catch(BoxException &e) + { +# ifdef WIN32 + CloseHandle(fd); +# else + ::close(fd); +# endif + BOX_NOTICE("Failed to lock lockfile " << rFilename << ": " << e.what()); + throw; + } +#endif // HAVE_DECL_O_EXLOCK + + if(!FileExists(rFilename)) + { + BOX_ERROR("Locked lockfile " << rFilename << ", but lockfile no longer " + "exists, bailing out"); +# ifdef WIN32 + CloseHandle(fd); +# else + ::close(fd); +# endif + return false; + } + + // Success + mFileDescriptor = fd; + BOX_TRACE("Successfully locked lockfile " << rFilename); + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: NamedLock::ReleaseLock() +// Purpose: Release the lock. Exceptions if the lock is not held +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +void NamedLock::ReleaseLock() +{ + // Got a lock? +#ifdef WIN32 + if(mFileDescriptor == INVALID_HANDLE_VALUE) +#else + if(mFileDescriptor == -1) +#endif + { + THROW_EXCEPTION(CommonException, NamedLockNotHeld) + } + +#ifndef WIN32 + // Delete the file. We need to do this before closing the filehandle, + // if we used flock() or fcntl() to lock it, otherwise someone could + // acquire the lock, release and delete it between us closing (and + // hence releasing) and deleting it, and we'd fail when it came to + // deleting the file. This happens in tests much more often than + // you'd expect! + // + // This doesn't apply on systems using plain lockfile locking, such as + // Windows, and there we need to close the file before deleting it, + // otherwise the system won't let us delete it. + + if(::unlink(mFileName.c_str()) != 0) + { + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to delete lockfile"), + CommonException, OSFileError); + } +#endif // !WIN32 + + // Close the file +# ifdef WIN32 + if(!CloseHandle(mFileDescriptor)) +# else + if(::close(mFileDescriptor) != 0) +# endif + { + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to close lockfile"), + CommonException, OSFileError); + } + + // Mark as unlocked, so we don't try to close it again if the unlink() fails. +#ifdef WIN32 + mFileDescriptor = INVALID_HANDLE_VALUE; +#else + mFileDescriptor = -1; +#endif + +#ifdef WIN32 + // On Windows we need to close the file before deleting it, otherwise + // the system won't let us delete it. + + if(::unlink(mFileName.c_str()) != 0) + { + THROW_EMU_ERROR( + BOX_FILE_MESSAGE(mFileName, "Failed to delete lockfile"), + CommonException, OSFileError); + } +#endif // WIN32 + + BOX_TRACE("Released lock and deleted lockfile " << mFileName); +} diff --git a/lib/common/NamedLock.h b/lib/common/NamedLock.h new file mode 100644 index 00000000..a7d0d778 --- /dev/null +++ b/lib/common/NamedLock.h @@ -0,0 +1,50 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: NamedLock.h +// Purpose: A global named lock, implemented as a lock file in file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- + +#ifndef NAMEDLOCK__H +#define NAMEDLOCK__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: NamedLock +// Purpose: A global named lock, implemented as a lock file in file system +// Created: 2003/08/28 +// +// -------------------------------------------------------------------------- +class NamedLock +{ +public: + NamedLock(); + ~NamedLock(); +private: + // No copying allowed + NamedLock(const NamedLock &); + +public: + bool TryAndGetLock(const std::string& rFilename, int mode = 0755); +# ifdef WIN32 + bool GotLock() {return mFileDescriptor != INVALID_HANDLE_VALUE;} +# else + bool GotLock() {return mFileDescriptor != -1;} +# endif + void ReleaseLock(); + +private: +# ifdef WIN32 + HANDLE mFileDescriptor; +# else + int mFileDescriptor; +# endif + + std::string mFileName; +}; + +#endif // NAMEDLOCK__H + diff --git a/lib/common/PartialReadStream.cpp b/lib/common/PartialReadStream.cpp new file mode 100644 index 00000000..b5f99bb5 --- /dev/null +++ b/lib/common/PartialReadStream.cpp @@ -0,0 +1,138 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PartialReadStream.h +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "PartialReadStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::PartialReadStream(IOStream &, +// pos_type) +// Purpose: Constructor, taking another stream and the number of +// bytes to be read from it. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +PartialReadStream::PartialReadStream(IOStream &rSource, + pos_type BytesToRead) + : mrSource(rSource), + mBytesLeft(BytesToRead) +{ + ASSERT(BytesToRead > 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::~PartialReadStream() +// Purpose: Destructor. Won't absorb any unread bytes. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +PartialReadStream::~PartialReadStream() +{ + // Warn in debug mode + if(mBytesLeft != 0) + { + BOX_TRACE("PartialReadStream destroyed with " << mBytesLeft << + " bytes remaining"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +int PartialReadStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Finished? + if(mBytesLeft <= 0) + { + return 0; + } + + // Asking for more than is allowed? + if(NBytes > mBytesLeft) + { + // Adjust downwards + NBytes = mBytesLeft; + } + + // Route the request to the source + int read = mrSource.Read(pBuffer, NBytes, Timeout); + ASSERT(read <= mBytesLeft); + + // Adjust the count + mBytesLeft -= read; + + // Return the number read + return read; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::BytesLeftToRead() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type PartialReadStream::BytesLeftToRead() +{ + return mBytesLeft; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::Write(const void *, int) +// Purpose: As interface. But will exception. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void PartialReadStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, CantWriteToPartialReadStream) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::StreamDataLeft() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool PartialReadStream::StreamDataLeft() +{ + return mBytesLeft != 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: PartialReadStream::StreamClosed() +// Purpose: As interface. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +bool PartialReadStream::StreamClosed() +{ + // always closed + return true; +} + diff --git a/lib/common/PartialReadStream.h b/lib/common/PartialReadStream.h new file mode 100644 index 00000000..61bdd7d1 --- /dev/null +++ b/lib/common/PartialReadStream.h @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PartialReadStream.h +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- + +#ifndef PARTIALREADSTREAM__H +#define PARTIALREADSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: PartialReadStream +// Purpose: Read part of another stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +class PartialReadStream : public IOStream +{ +public: + PartialReadStream(IOStream &rSource, pos_type BytesToRead); + ~PartialReadStream(); +private: + // no copying allowed + PartialReadStream(const IOStream &); + PartialReadStream(const PartialReadStream &); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + IOStream &mrSource; + pos_type mBytesLeft; +}; + +#endif // PARTIALREADSTREAM__H + diff --git a/lib/common/PathUtils.cpp b/lib/common/PathUtils.cpp new file mode 100644 index 00000000..924d47d2 --- /dev/null +++ b/lib/common/PathUtils.cpp @@ -0,0 +1,34 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PathUtils.cpp +// Purpose: Platform-independent path manipulation +// Created: 2007/01/17 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeFullPath(const std::string& rDir, const std::string& rFile) +// Purpose: Combine directory and file name +// Created: 2006/08/10 +// +// -------------------------------------------------------------------------- +std::string MakeFullPath(const std::string& rDir, const std::string& rEntry) +{ + std::string result(rDir); + + if (result.size() > 0 && + result[result.size()-1] != DIRECTORY_SEPARATOR_ASCHAR) + { + result += DIRECTORY_SEPARATOR; + } + + result += rEntry; + + return result; +} diff --git a/lib/common/PathUtils.h b/lib/common/PathUtils.h new file mode 100644 index 00000000..1cf2e507 --- /dev/null +++ b/lib/common/PathUtils.h @@ -0,0 +1,26 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: PathUtils.h +// Purpose: Platform-independent path manipulation +// Created: 2007/01/17 +// +// -------------------------------------------------------------------------- + +#ifndef PATHUTILS_H +#define PATHUTILS_H + +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MakeFullPath(const std::string& rDir, const std::string& rFile) +// Purpose: Combine directory and file name +// Created: 2006/08/10 +// +// -------------------------------------------------------------------------- + +std::string MakeFullPath(const std::string& rDir, const std::string& rEntry); + +#endif // !PATHUTILS_H 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..cd9d8271 --- /dev/null +++ b/lib/common/RateLimitingStream.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// 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, + int Timeout = IOStream::TimeOutInfinite) + { + Write(pBuffer, NBytes, Timeout); + } + 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/ReadGatherStream.cpp b/lib/common/ReadGatherStream.cpp new file mode 100644 index 00000000..ae252832 --- /dev/null +++ b/lib/common/ReadGatherStream.cpp @@ -0,0 +1,263 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadGatherStream.cpp +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "ReadGatherStream.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::ReadGatherStream(bool) +// Purpose: Constructor. Args says whether or not all the component streams will be deleted when this +// object is deleted. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +ReadGatherStream::ReadGatherStream(bool DeleteComponentStreamsOnDestruction) + : mDeleteComponentStreamsOnDestruction(DeleteComponentStreamsOnDestruction), + mCurrentPosition(0), + mTotalSize(0), + mCurrentBlock(0), + mPositionInCurrentBlock(0), + mSeekDoneForCurrent(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::~ReadGatherStream() +// Purpose: Destructor. Will delete all the stream objects, if required. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +ReadGatherStream::~ReadGatherStream() +{ + // Delete compoenent streams? + if(mDeleteComponentStreamsOnDestruction) + { + for(unsigned int l = 0; l < mComponents.size(); ++l) + { + delete mComponents[l]; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::AddComponent(IOStream *) +// Purpose: Add a component to this stream, returning the index +// of this component in the internal list. Use this +// with AddBlock() +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +int ReadGatherStream::AddComponent(IOStream *pStream) +{ + ASSERT(pStream != 0); + + // Just add the component to the list, returning it's index. + int index = mComponents.size(); + mComponents.push_back(pStream); + return index; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::AddBlock(int, pos_type, bool, pos_type) +// Purpose: Add a block to the list of blocks being gathered into one stream. +// Length is length of block to read from this component, Seek == true +// if a seek is required, and if true, SeekTo is the position (absolute) +// in the stream to be seeked to when this block is required. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +void ReadGatherStream::AddBlock(int Component, pos_type Length, bool Seek, pos_type SeekTo) +{ + // Check block + if(Component < 0 || Component >= (int)mComponents.size() || Length < 0 || SeekTo < 0) + { + THROW_EXCEPTION(CommonException, ReadGatherStreamAddingBadBlock); + } + + // Add to list + Block b; + b.mLength = Length; + b.mSeekTo = SeekTo; + b.mComponent = Component; + b.mSeek = Seek; + + mBlocks.push_back(b); + + // And update the total size + mTotalSize += Length; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +int ReadGatherStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + int bytesToRead = NBytes; + uint8_t *buffer = (uint8_t*)pBuffer; + + while(bytesToRead > 0) + { + // Done? + if(mCurrentBlock >= mBlocks.size()) + { + // Stop now, as have finished the last block + return NBytes - bytesToRead; + } + + // Seek? + if(mPositionInCurrentBlock == 0 && mBlocks[mCurrentBlock].mSeek && !mSeekDoneForCurrent) + { + // Do seeks in this manner so that seeks are done regardless of whether the block + // has length > 0, and it will only be done once, and at as late a stage as possible. + + mComponents[mBlocks[mCurrentBlock].mComponent]->Seek(mBlocks[mCurrentBlock].mSeekTo, IOStream::SeekType_Absolute); + + mSeekDoneForCurrent = true; + } + + // Anything in the current block? + if(mPositionInCurrentBlock < mBlocks[mCurrentBlock].mLength) + { + // Read! + pos_type s = mBlocks[mCurrentBlock].mLength - mPositionInCurrentBlock; + if(s > bytesToRead) s = bytesToRead; + + pos_type r = mComponents[mBlocks[mCurrentBlock].mComponent]->Read(buffer, s, Timeout); + + // update variables + mPositionInCurrentBlock += r; + buffer += r; + bytesToRead -= r; + mCurrentPosition += r; + + if(r != s) + { + // Stream returned less than requested. To avoid blocking when not necessary, + // return now. + return NBytes - bytesToRead; + } + } + else + { + // Move to next block + ++mCurrentBlock; + mPositionInCurrentBlock = 0; + mSeekDoneForCurrent = false; + } + } + + return NBytes - bytesToRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::GetPosition() +// Purpose: As interface +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadGatherStream::GetPosition() const +{ + return mCurrentPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::BytesLeftToRead() +// Purpose: As interface +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadGatherStream::BytesLeftToRead() +{ + return mTotalSize - mCurrentPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::Write(const void *, int) +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +void ReadGatherStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, CannotWriteToReadGatherStream); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::StreamDataLeft() +// Purpose: As interface. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +bool ReadGatherStream::StreamDataLeft() +{ + if(mCurrentBlock >= mBlocks.size()) + { + // Done all the blocks + return false; + } + + if(mCurrentBlock == (mBlocks.size() - 1) + && mPositionInCurrentBlock >= mBlocks[mCurrentBlock].mLength) + { + // Are on the last block, and have got all the data from it. + return false; + } + + // Otherwise, there's more data to be read + return true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadGatherStream::StreamClosed() +// Purpose: As interface. But the stream is always closed. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +bool ReadGatherStream::StreamClosed() +{ + return true; +} + + diff --git a/lib/common/ReadGatherStream.h b/lib/common/ReadGatherStream.h new file mode 100644 index 00000000..9a44480b --- /dev/null +++ b/lib/common/ReadGatherStream.h @@ -0,0 +1,68 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadGatherStream.h +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef READGATHERSTREAM_H +#define READGATHERSTREAM_H + +#include "IOStream.h" + +#include <vector> + +// -------------------------------------------------------------------------- +// +// Class +// Name: ReadGatherStream +// Purpose: Build a stream (for reading only) out of a number of other streams. +// Created: 10/12/03 +// +// -------------------------------------------------------------------------- +class ReadGatherStream : public IOStream +{ +public: + ReadGatherStream(bool DeleteComponentStreamsOnDestruction); + ~ReadGatherStream(); +private: + ReadGatherStream(const ReadGatherStream &); + ReadGatherStream &operator=(const ReadGatherStream &); +public: + + int AddComponent(IOStream *pStream); + void AddBlock(int Component, pos_type Length, bool Seek = false, pos_type SeekTo = 0); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + virtual pos_type GetPosition() const; + +private: + bool mDeleteComponentStreamsOnDestruction; + std::vector<IOStream *> mComponents; + + typedef struct + { + pos_type mLength; + pos_type mSeekTo; + int mComponent; + bool mSeek; + } Block; + + std::vector<Block> mBlocks; + + pos_type mCurrentPosition; + pos_type mTotalSize; + unsigned int mCurrentBlock; + pos_type mPositionInCurrentBlock; + bool mSeekDoneForCurrent; +}; + + +#endif // READGATHERSTREAM_H diff --git a/lib/common/ReadLoggingStream.cpp b/lib/common/ReadLoggingStream.cpp new file mode 100644 index 00000000..df493344 --- /dev/null +++ b/lib/common/ReadLoggingStream.cpp @@ -0,0 +1,203 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadLoggingStream.cpp +// Purpose: Buffering wrapper around IOStreams +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> + +#include "ReadLoggingStream.h" +#include "CommonException.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::ReadLoggingStream(const char *, int, int) +// Purpose: Constructor, set up buffer +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +ReadLoggingStream::ReadLoggingStream(IOStream& rSource, Logger& rLogger) +: mrSource(rSource), + mOffset(0), + mLength(mrSource.BytesLeftToRead()), + mTotalRead(0), + mStartTime(GetCurrentBoxTime()), + mrLogger(rLogger) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int ReadLoggingStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + int numBytesRead = mrSource.Read(pBuffer, NBytes, Timeout); + + if (numBytesRead > 0) + { + mTotalRead += numBytesRead; + mOffset += numBytesRead; + } + + if (mLength == 0) + { + mrLogger.Log(numBytesRead, mOffset); + } + else if (mTotalRead == 0) + { + mrLogger.Log(numBytesRead, mOffset, mLength); + } + else + { + box_time_t timeNow = GetCurrentBoxTime(); + box_time_t elapsed = timeNow - mStartTime; + box_time_t finish = (elapsed * mLength) / mTotalRead; + // box_time_t remain = finish - elapsed; + mrLogger.Log(numBytesRead, mOffset, mLength, elapsed, finish); + } + + return numBytesRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadLoggingStream::BytesLeftToRead() +{ + return mLength - mOffset; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ReadLoggingStream::GetPosition() const +{ + return mOffset; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + mrSource.Seek(Offset, SeekType); + + switch (SeekType) + { + case SeekType_Absolute: + { + // just go there + mOffset = Offset; + } + break; + + case SeekType_Relative: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to subtract that amount from the seek + // to seek forward that much less, putting the + // real pointer in the right place. + mOffset += Offset; + } + break; + + case SeekType_End: + { + // Actual underlying file position is + // (mBufferSize - mBufferPosition) ahead of us. + // Need to add that amount to the seek + // to seek backwards that much more, putting the + // real pointer in the right place. + mOffset = mLength - Offset; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ReadLoggingStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ReadLoggingStream::StreamDataLeft() +{ + return mrSource.StreamDataLeft(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadLoggingStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ReadLoggingStream::StreamClosed() +{ + return mrSource.StreamClosed(); +} + diff --git a/lib/common/ReadLoggingStream.h b/lib/common/ReadLoggingStream.h new file mode 100644 index 00000000..bee7e1d6 --- /dev/null +++ b/lib/common/ReadLoggingStream.h @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ReadLoggingStream.h +// Purpose: Wrapper around IOStreams that logs read progress +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- + +#ifndef READLOGGINGSTREAM__H +#define READLOGGINGSTREAM__H + +#include "IOStream.h" +#include "BoxTime.h" + +class ReadLoggingStream : public IOStream +{ +public: + class Logger + { + public: + virtual ~Logger() { } + virtual void Log(int64_t readSize, int64_t offset, + int64_t length, box_time_t elapsed, + box_time_t finish) = 0; + virtual void Log(int64_t readSize, int64_t offset, + int64_t length) = 0; + virtual void Log(int64_t readSize, int64_t offset) = 0; + }; + +private: + IOStream& mrSource; + IOStream::pos_type mOffset, mLength, mTotalRead; + box_time_t mStartTime; + Logger& mrLogger; + +public: + ReadLoggingStream(IOStream& rSource, Logger& rLogger); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + ReadLoggingStream(const ReadLoggingStream &rToCopy) + : mrSource(rToCopy.mrSource), mrLogger(rToCopy.mrLogger) + { /* do not call */ } +}; + +#endif // READLOGGINGSTREAM__H + + diff --git a/lib/common/SelfFlushingStream.h b/lib/common/SelfFlushingStream.h new file mode 100644 index 00000000..b4efa294 --- /dev/null +++ b/lib/common/SelfFlushingStream.h @@ -0,0 +1,78 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SelfFlushingStream.h +// Purpose: A stream wrapper that always flushes the underlying +// stream, to ensure protocol safety. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- + +#ifndef SELFFLUSHINGSTREAM__H +#define SELFFLUSHINGSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: SelfFlushingStream +// Purpose: A stream wrapper that always flushes the underlying +// stream, to ensure protocol safety. +// Created: 2008/08/20 +// +// -------------------------------------------------------------------------- +class SelfFlushingStream : public IOStream +{ +public: + SelfFlushingStream(IOStream &rSource) + : mrSource(rSource) { } + + SelfFlushingStream(const SelfFlushingStream &rToCopy) + : mrSource(rToCopy.mrSource) { } + + ~SelfFlushingStream() + { + if(StreamDataLeft()) + { + BOX_WARNING("Not all data was read from stream, " + "discarding the rest"); + } + + Flush(); + } + +private: + // no copying from IOStream allowed + SelfFlushingStream(const IOStream& rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) + { + return mrSource.Read(pBuffer, NBytes, Timeout); + } + virtual pos_type BytesLeftToRead() + { + return mrSource.BytesLeftToRead(); + } + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) + { + mrSource.Write(pBuffer, NBytes, Timeout); + } + virtual bool StreamDataLeft() + { + return mrSource.StreamDataLeft(); + } + virtual bool StreamClosed() + { + return mrSource.StreamClosed(); + } + +private: + IOStream &mrSource; +}; + +#endif // SELFFLUSHINGSTREAM__H + diff --git a/lib/common/StreamableMemBlock.cpp b/lib/common/StreamableMemBlock.cpp new file mode 100644 index 00000000..9abf78d3 --- /dev/null +++ b/lib/common/StreamableMemBlock.cpp @@ -0,0 +1,371 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StreamableMemBlock.cpp +// Purpose: Memory blocks which can be loaded and saved from streams +// with a header indicating the size of the block. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <new> +#include <cstdlib> +#include <string.h> + +#include "StreamableMemBlock.h" +#include "IOStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock() +// Purpose: Constructor, making empty block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock() + : mpBuffer(0), + mSize(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(void *, int) +// Purpose: Create block, copying data from another bit of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(void *pBuffer, int Size) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(Size); + ::memcpy(mpBuffer, pBuffer, Size); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(int) +// Purpose: Create block, initialising it to all zeros +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(int Size) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(Size); + ::memset(mpBuffer, 0, Size); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &) +// Purpose: Copy constructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::StreamableMemBlock(const StreamableMemBlock &rToCopy) + : mpBuffer(0), + mSize(0) +{ + AllocateBlock(rToCopy.mSize); + ::memcpy(mpBuffer, rToCopy.mpBuffer, mSize); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(void *, int) +// Purpose: Set the contents of the block +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(void *pBuffer, int Size) +{ + FreeBlock(); + AllocateBlock(Size); + ::memcpy(mpBuffer, pBuffer, Size); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(IOStream &) +// Purpose: Set from stream. Stream must support BytesLeftToRead() +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(IOStream &rStream, int Timeout) +{ + // Get size + IOStream::pos_type size = rStream.BytesLeftToRead(); + if(size == IOStream::SizeOfStreamUnknown) + { + THROW_EXCEPTION(CommonException, StreamDoesntHaveRequiredProperty) + } + + // Allocate a new block (this way to be exception safe) + char *pblock = (char*)malloc(size); + if(pblock == 0) + { + throw std::bad_alloc(); + } + + try + { + // Read in + if(!rStream.ReadFullBuffer(pblock, size, + 0 /* not interested in bytes read if this fails */, + Timeout)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + // Free the block ready for replacement + FreeBlock(); + } + catch(...) + { + ::free(pblock); + throw; + } + + // store... + ASSERT(mpBuffer == 0); + mpBuffer = pblock; + mSize = size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::Set(const StreamableMemBlock &) +// Purpose: Set from other block. +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::Set(const StreamableMemBlock &rBlock) +{ + Set(rBlock.mpBuffer, rBlock.mSize); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::~StreamableMemBlock() +// Purpose: Destructor +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +StreamableMemBlock::~StreamableMemBlock() +{ + FreeBlock(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::FreeBlock() +// Purpose: Protected. Frees block of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::FreeBlock() +{ + if(mpBuffer != 0) + { + ::free(mpBuffer); + } + mpBuffer = 0; + mSize = 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::AllocateBlock(int) +// Purpose: Protected. Allocate the block of memory +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::AllocateBlock(int Size) +{ + ASSERT(mpBuffer == 0); + if(Size > 0) + { + mpBuffer = ::malloc(Size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } + } + mSize = Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::ResizeBlock(int) +// Purpose: Protected. Resizes the allocated block. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::ResizeBlock(int Size) +{ + ASSERT(Size > 0); + if(Size > 0) + { + void *pnewBuffer = ::realloc(mpBuffer, Size); + if(pnewBuffer == 0) + { + throw std::bad_alloc(); + } + mpBuffer = pnewBuffer; + } + mSize = Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::ReadFromStream(IOStream &, int) +// Purpose: Read the block in from a stream +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::ReadFromStream(IOStream &rStream, int Timeout) +{ + // Get the size of the block + int32_t size_s; + if(!rStream.ReadFullBuffer(&size_s, sizeof(size_s), + 0, /* not interested in bytes read if this fails */ + Timeout)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + int size = ntohl(size_s); + + + // Allocate a new block (this way to be exception safe) + char *pblock = (char*)malloc(size); + if(pblock == 0) + { + throw std::bad_alloc(); + } + + try + { + // Read in + if(!rStream.ReadFullBuffer(pblock, size, + 0, /* not interested in bytes read if this fails */ + Timeout)) + { + THROW_EXCEPTION(CommonException, StreamableMemBlockIncompleteRead) + } + + // Free the block ready for replacement + FreeBlock(); + } + catch(...) + { + ::free(pblock); + throw; + } + + // store... + ASSERT(mpBuffer == 0); + mpBuffer = pblock; + mSize = size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::WriteToStream(IOStream &) +// Purpose: Write the block to a stream +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::WriteToStream(IOStream &rStream) const +{ + int32_t sizenbo = htonl(mSize); + // Size + rStream.Write(&sizenbo, sizeof(sizenbo)); + // Buffer + if(mSize > 0) + { + rStream.Write(mpBuffer, mSize); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::WriteEmptyBlockToStream(IOStream &) +// Purpose: Writes an empty block to a stream. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void StreamableMemBlock::WriteEmptyBlockToStream(IOStream &rStream) +{ + int32_t sizenbo = htonl(0); + rStream.Write(&sizenbo, sizeof(sizenbo)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::GetBuffer() +// Purpose: Get pointer to buffer +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +void *StreamableMemBlock::GetBuffer() const +{ + if(mSize == 0) + { + // Return something which isn't a null pointer + static const int validptr = 0; + return (void*)&validptr; + } + + // return the buffer + ASSERT(mpBuffer != 0); + return mpBuffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: StreamableMemBlock::operator==(const StreamableMemBlock &) +// Purpose: Test for equality of memory blocks +// Created: 2003/09/06 +// +// -------------------------------------------------------------------------- +bool StreamableMemBlock::operator==(const StreamableMemBlock &rCompare) const +{ + if(mSize != rCompare.mSize) return false; + if(mSize == 0 && rCompare.mSize == 0) return true; // without memory comparison! + return ::memcmp(mpBuffer, rCompare.mpBuffer, mSize) == 0; +} + + diff --git a/lib/common/StreamableMemBlock.h b/lib/common/StreamableMemBlock.h new file mode 100644 index 00000000..6c1c5ab8 --- /dev/null +++ b/lib/common/StreamableMemBlock.h @@ -0,0 +1,72 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: StreamableMemBlock.h +// Purpose: Memory blocks which can be loaded and saved from streams, +// with a header indicating the size of the block. +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- + +#ifndef STREAMABLEMEMBLOCK__H +#define STREAMABLEMEMBLOCK__H + +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: StreamableMemBlock +// Purpose: Memory blocks which can be loaded and saved from streams +// Created: 2003/09/05 +// +// -------------------------------------------------------------------------- +class StreamableMemBlock +{ +public: + StreamableMemBlock(); + StreamableMemBlock(int Size); + StreamableMemBlock(void *pBuffer, int Size); + StreamableMemBlock(const StreamableMemBlock &rToCopy); + ~StreamableMemBlock(); + + void Set(const StreamableMemBlock &rBlock); + void Set(void *pBuffer, int Size); + void Set(IOStream &rStream, int Timeout); + StreamableMemBlock &operator=(const StreamableMemBlock &rBlock) + { + Set(rBlock); + return *this; + } + + void ReadFromStream(IOStream &rStream, int Timeout); + void WriteToStream(IOStream &rStream) const; + + static void WriteEmptyBlockToStream(IOStream &rStream); + + void *GetBuffer() const; + + // Size of block + int GetSize() const {return mSize;} + + // Buffer empty? + bool IsEmpty() const {return mSize == 0;} + + // Clear the contents of the block + void Clear() {FreeBlock();} + + bool operator==(const StreamableMemBlock &rCompare) const; + + void ResizeBlock(int Size); + +protected: // be careful with these! + void AllocateBlock(int Size); + void FreeBlock(); + +private: + void *mpBuffer; + int mSize; +}; + +#endif // STREAMABLEMEMBLOCK__H + diff --git a/lib/common/Test.cpp b/lib/common/Test.cpp new file mode 100644 index 00000000..2c51cd61 --- /dev/null +++ b/lib/common/Test.cpp @@ -0,0 +1,640 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Test.cpp +// Purpose: Useful stuff for tests +// Created: 2008/04/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <errno.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> + +#include <sys/stat.h> +#include <sys/types.h> + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "BoxTime.h" +#include "FileStream.h" +#include "Test.h" +#include "Utils.h" + +int num_tests_selected = 0; +int num_failures = 0; +int old_failure_count = 0; +int first_fail_line; +std::string original_working_dir; +std::string first_fail_file; +std::string current_test_name; +std::list<std::string> run_only_named_tests; +std::map<std::string, std::string> s_test_status; + +bool setUp(const char* function_name) +{ + current_test_name = function_name; + + if (!run_only_named_tests.empty()) + { + bool run_this_test = false; + + for (std::list<std::string>::iterator + i = run_only_named_tests.begin(); + i != run_only_named_tests.end(); i++) + { + if (*i == current_test_name) + { + run_this_test = true; + break; + } + } + + if (!run_this_test) + { + // not in the list, so don't run it. + return false; + } + } + + printf("\n\n== %s ==\n", function_name); + num_tests_selected++; + old_failure_count = num_failures; + + if (original_working_dir == "") + { + char buf[1024]; + if (getcwd(buf, sizeof(buf)) == NULL) + { + BOX_LOG_SYS_ERROR("getcwd"); + } + original_working_dir = buf; + } + else + { + if (chdir(original_working_dir.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("chdir"); + } + } + +#ifdef _MSC_VER + DIR* pDir = opendir("testfiles"); + if(!pDir) + { + THROW_SYS_FILE_ERROR("Failed to open test temporary directory", + "testfiles", CommonException, Internal); + } + struct dirent* pEntry; + for(pEntry = readdir(pDir); pEntry; pEntry = readdir(pDir)) + { + std::string filename = pEntry->d_name; + if(StartsWith("TestDir", filename) || + StartsWith("0_", filename) || + filename == "accounts.txt" || + StartsWith("file", filename) || + StartsWith("notifyran", filename) || + StartsWith("notifyscript.tag", filename) || + StartsWith("restore", filename) || + filename == "bbackupd-data" || + filename == "syncallowscript.control" || + StartsWith("syncallowscript.notifyran.", filename) || + filename == "test2.downloaded" || + EndsWith("testfile", filename)) + { + std::string filepath = std::string("testfiles\\") + filename; + + int filetype = ObjectExists(filepath); + if(filetype == ObjectExists_File) + { + if(::unlink(filepath.c_str()) != 0) + { + TEST_FAIL_WITH_MESSAGE(BOX_SYS_ERROR_MESSAGE("Failed to delete " + "test fixture file: unlink(\"" << filepath << "\")")); + } + } + else if(filetype == ObjectExists_Dir) + { + std::string cmd = "cmd /c rd /s /q " + filepath; + WCHAR* wide_cmd = ConvertUtf8ToWideString(cmd.c_str()); + if(wide_cmd == NULL) + { + TEST_FAIL_WITH_MESSAGE("Failed to convert string " + "to wide string: " << cmd); + continue; + } + + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + BOOL result = CreateProcessW( + NULL, // lpApplicationName + wide_cmd, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + TRUE, // bInheritHandles + 0, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &si, // lpStartupInfo + &pi // lpProcessInformation + ); + delete [] wide_cmd; + + if(result == FALSE) + { + TEST_FAIL_WITH_MESSAGE("Failed to delete test " + "fixture file: failed to execute command " + "'" << cmd << "': " << + GetErrorMessage(GetLastError())); + continue; + } + + // Wait until child process exits. + WaitForSingleObject(pi.hProcess, INFINITE); + DWORD exit_code; + result = GetExitCodeProcess(pi.hProcess, &exit_code); + + if(result == FALSE) + { + TEST_FAIL_WITH_MESSAGE("Failed to delete " + "test fixture file: failed to get " + "command exit status: '" << + cmd << "': " << + GetErrorMessage(GetLastError())); + } + else if(exit_code != 0) + { + TEST_FAIL_WITH_MESSAGE("Failed to delete test " + "fixture file: command '" << cmd << "' " + "exited with status " << exit_code); + } + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + } + else + { + TEST_FAIL_WITH_MESSAGE("Don't know how to delete file " << filepath << + " of type " << filetype); + } + } + } + closedir(pDir); + FileStream touch("testfiles/accounts.txt", O_WRONLY | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); +#else + TEST_THAT_THROWONFAIL(system( + "rm -rf testfiles/TestDir* testfiles/0_0 testfiles/0_1 " + "testfiles/0_2 testfiles/accounts.txt " // testfiles/test* .tgz! + "testfiles/file* testfiles/notifyran testfiles/notifyran.* " + "testfiles/notifyscript.tag* " + "testfiles/restore* testfiles/bbackupd-data " + "testfiles/syncallowscript.control " + "testfiles/syncallowscript.notifyran.* " + "testfiles/test2.downloaded" + ) == 0); + TEST_THAT_THROWONFAIL(system("touch testfiles/accounts.txt") == 0); +#endif + TEST_THAT_THROWONFAIL(mkdir("testfiles/0_0", 0755) == 0); + TEST_THAT_THROWONFAIL(mkdir("testfiles/0_1", 0755) == 0); + TEST_THAT_THROWONFAIL(mkdir("testfiles/0_2", 0755) == 0); + TEST_THAT_THROWONFAIL(mkdir("testfiles/bbackupd-data", 0755) == 0); + + return true; +} + +bool tearDown() +{ + if (num_failures == old_failure_count) + { + BOX_NOTICE(current_test_name << " passed"); + s_test_status[current_test_name] = "passed"; + return true; + } + else + { + BOX_NOTICE(current_test_name << " failed"); \ + s_test_status[current_test_name] = "FAILED"; + return false; + } +} + +bool fail() +{ + num_failures++; + return tearDown(); +} + +int finish_test_suite() +{ + printf("\n"); + printf("Test results:\n"); + + typedef std::map<std::string, std::string>::iterator s_test_status_iterator; + for(s_test_status_iterator i = s_test_status.begin(); + i != s_test_status.end(); i++) + { + BOX_NOTICE("test result: " << i->second << ": " << i->first); + } + + TEST_LINE(num_tests_selected > 0, "No tests matched the patterns " + "specified on the command line"); + + return (num_failures == 0 && num_tests_selected > 0) ? 0 : 1; +} + +bool TestFileExists(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0; +} + +bool TestFileNotEmpty(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == 0 && + st.st_size > 0; +} + +bool TestDirExists(const char *Filename) +{ + EMU_STRUCT_STAT st; + return EMU_STAT(Filename, &st) == 0 && (st.st_mode & S_IFDIR) == S_IFDIR; +} + +// -1 if doesn't exist +int TestGetFileSize(const std::string& Filename) +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(Filename.c_str(), &st) == 0) + { + return st.st_size; + } + return -1; +} + +std::string ConvertPaths(const std::string& rOriginal) +{ +#ifdef WIN32 + // convert UNIX paths to native + + std::string converted; + for (size_t i = 0; i < rOriginal.size(); i++) + { + if (rOriginal[i] == '/') + { + converted += '\\'; + } + else + { + converted += rOriginal[i]; + } + } + return converted; + +#else // !WIN32 + return rOriginal; +#endif +} + +int RunCommand(const std::string& rCommandLine) +{ + return ::system(ConvertPaths(rCommandLine).c_str()); +} + +#ifdef WIN32 +#include <windows.h> +#endif + +bool ServerIsAlive(int pid) +{ + #ifdef WIN32 + + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, + false, pid); + if (hProcess == NULL) + { + if (GetLastError() != ERROR_INVALID_PARAMETER) + { + BOX_ERROR("Failed to open process " << pid << + ": " << + GetErrorMessage(GetLastError())); + } + return false; + } + + DWORD exitCode; + BOOL result = GetExitCodeProcess(hProcess, &exitCode); + CloseHandle(hProcess); + + if (result == 0) + { + BOX_ERROR("Failed to get exit code for process " << + pid << ": " << + GetErrorMessage(GetLastError())) + return false; + } + + if (exitCode == STILL_ACTIVE) + { + return true; + } + + return false; + + #else // !WIN32 + + if(pid == 0) return false; + return ::kill(pid, 0) != -1; + + #endif // WIN32 +} + +int ReadPidFile(const char *pidFile) +{ + if(!TestFileNotEmpty(pidFile)) + { + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file " + "(perhaps one was already running?)"); + return -1; + } + + int pid = -1; + + FILE *f = fopen(pidFile, "r"); + if(f == NULL || fscanf(f, "%d", &pid) != 1) + { + TEST_FAIL_WITH_MESSAGE("Couldn't read PID file"); + return -1; + } + fclose(f); + + return pid; +} + +int LaunchServer(const std::string& rCommandLine, const char *pidFile) +{ + BOX_INFO("Starting server: " << rCommandLine); + +#ifdef WIN32 + + PROCESS_INFORMATION procInfo; + + STARTUPINFO startInfo; + startInfo.cb = sizeof(startInfo); + startInfo.lpReserved = NULL; + startInfo.lpDesktop = NULL; + startInfo.lpTitle = NULL; + startInfo.dwFlags = 0; + startInfo.cbReserved2 = 0; + startInfo.lpReserved2 = NULL; + + std::string cmd = ConvertPaths(rCommandLine); + CHAR* tempCmd = strdup(cmd.c_str()); + + DWORD result = CreateProcess + ( + NULL, // lpApplicationName, naughty! + tempCmd, // lpCommandLine + NULL, // lpProcessAttributes + NULL, // lpThreadAttributes + false, // bInheritHandles + 0, // dwCreationFlags + NULL, // lpEnvironment + NULL, // lpCurrentDirectory + &startInfo, // lpStartupInfo + &procInfo // lpProcessInformation + ); + + free(tempCmd); + + TEST_THAT_OR(result != 0, + BOX_LOG_WIN_ERROR("Launch failed: " << rCommandLine); + return -1; + ); + + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + + return WaitForServerStartup(pidFile, (int)procInfo.dwProcessId); + +#else // !WIN32 + + TEST_THAT_OR(RunCommand(rCommandLine) == 0, + TEST_FAIL_WITH_MESSAGE("Failed to start server: " << rCommandLine); + return -1; + ) + + return WaitForServerStartup(pidFile, 0); + +#endif // WIN32 +} + +int WaitForServerStartup(const char *pidFile, int pidIfKnown) +{ + #ifdef WIN32 + if (pidFile == NULL) + { + return pidIfKnown; + } + #else + // on other platforms there is no other way to get + // the PID, so a NULL pidFile doesn't make sense. + ASSERT(pidFile != NULL); + #endif + + // time for it to start up + BOX_TRACE("Waiting for server to start"); + + for (int i = 0; i < 15; i++) + { + if (TestFileNotEmpty(pidFile)) + { + break; + } + + if (pidIfKnown && !ServerIsAlive(pidIfKnown)) + { + break; + } + + ::sleep(1); + } + + // on Win32 we can check whether the process is alive + // without even checking the PID file + + if (pidIfKnown && !ServerIsAlive(pidIfKnown)) + { + TEST_FAIL_WITH_MESSAGE("Server died!"); + return -1; + } + + if (!TestFileNotEmpty(pidFile)) + { + TEST_FAIL_WITH_MESSAGE("Server didn't save PID file"); + return -1; + } + + BOX_TRACE("Server started"); + + // wait a second for the pid to be written to the file + ::sleep(1); + + // read pid file + int pid = ReadPidFile(pidFile); + + // On Win32 we can check whether the PID in the pidFile matches + // the one returned by the system, which it always should. + + if (pidIfKnown && pid != pidIfKnown) + { + BOX_ERROR("Server wrote wrong pid to file (" << pidFile << + "): expected " << pidIfKnown << " but found " << + pid); + TEST_FAIL_WITH_MESSAGE("Server wrote wrong pid to file"); + return -1; + } + + return pid; +} + +void TestRemoteProcessMemLeaksFunc(const char *filename, + const char* file, int line) +{ +#ifdef BOX_MEMORY_LEAK_TESTING + // Does the file exist? + if(!TestFileExists(filename)) + { + if (num_failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } + ++num_failures; + printf("FAILURE: MemLeak report not available (file %s) " + "at %s:%d\n", filename, file, line); + } + else + { + // Is it empty? + if(TestGetFileSize(filename) > 0) + { + if (num_failures == 0) + { + first_fail_file = file; + first_fail_line = line; + } + ++num_failures; + printf("FAILURE: Memory leaks found in other process " + "(file %s) at %s:%d\n==========\n", + filename, file, line); + FILE *f = fopen(filename, "r"); + char linebuf[512]; + while(::fgets(linebuf, sizeof(linebuf), f) != 0) + { + printf("%s", linebuf); + } + fclose(f); + printf("==========\n"); + } + + // Delete it + ::unlink(filename); + } +#endif +} + +void force_sync() +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "force-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); +} + +void wait_for_sync_start() +{ + BOX_TRACE("Waiting for sync to start..."); + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-sync") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + BOX_TRACE("Backup daemon reported that sync has started."); +} + +void wait_for_sync_end() +{ + BOX_TRACE("Waiting for sync to finish..."); + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "wait-for-end") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + BOX_TRACE("Backup daemon reported that sync has finished."); +} + +void sync_and_wait() +{ + BOX_TRACE("Starting a sync and waiting for it to finish..."); + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "sync-and-wait") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + BOX_TRACE("Backup daemon reported that sync has finished."); +} + +void terminate_bbackupd(int pid) +{ + TEST_THAT(::system(BBACKUPCTL " -q -c testfiles/bbackupd.conf " + "terminate") == 0); + TestRemoteProcessMemLeaks("bbackupctl.memleaks"); + + for (int i = 0; i < 20; i++) + { + if (!ServerIsAlive(pid)) break; + fprintf(stdout, "."); + fflush(stdout); + sleep(1); + } + + TEST_THAT(!ServerIsAlive(pid)); + TestRemoteProcessMemLeaks("bbackupd.memleaks"); +} + + +// Wait a given number of seconds for something to complete +void wait_for_operation(int seconds, const char* message) +{ + BOX_INFO("Waiting " << seconds << " seconds for " << message); + + for(int l = 0; l < seconds; ++l) + { + sleep(1); + } + + BOX_TRACE("Finished waiting for " << message); +} + +void safe_sleep(int seconds) +{ + ShortSleep(SecondsToBoxTime(seconds), true); +} + +std::auto_ptr<Configuration> load_config_file(const std::string& config_file, + const ConfigurationVerify& verify) +{ + std::string errs; + std::auto_ptr<Configuration> config( + Configuration::LoadAndVerify(config_file, &verify, errs)); + TEST_EQUAL_LINE(0, errs.size(), "Failed to load configuration file: " + config_file + + ": " + errs); + TEST_EQUAL_OR(0, errs.size(), config.reset()); + return config; +} + diff --git a/lib/common/Test.h b/lib/common/Test.h new file mode 100644 index 00000000..36cd6a59 --- /dev/null +++ b/lib/common/Test.h @@ -0,0 +1,254 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Test.h +// Purpose: Useful stuff for tests +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#ifndef TEST__H +#define TEST__H + +#include <cstring> +#include <list> +#include <map> + +#include "Configuration.h" + +#ifdef WIN32 +#define BBACKUPCTL "..\\..\\bin\\bbackupctl\\bbackupctl.exe" +#define BBACKUPD "..\\..\\bin\\bbackupd\\bbackupd.exe" +#define BBSTORED "..\\..\\bin\\bbstored\\bbstored.exe" +#define BBACKUPQUERY "..\\..\\bin\\bbackupquery\\bbackupquery.exe" +#define BBSTOREACCOUNTS "..\\..\\bin\\bbstoreaccounts\\bbstoreaccounts.exe" +#define TEST_RETURN(actual, expected) TEST_EQUAL(expected, actual); +#else +#define BBACKUPCTL "../../bin/bbackupctl/bbackupctl" +#define BBACKUPD "../../bin/bbackupd/bbackupd" +#define BBSTORED "../../bin/bbstored/bbstored" +#define BBACKUPQUERY "../../bin/bbackupquery/bbackupquery" +#define BBSTOREACCOUNTS "../../bin/bbstoreaccounts/bbstoreaccounts" +#define TEST_RETURN(actual, expected) TEST_EQUAL((expected << 8), actual); +#endif + +extern int num_failures; +extern int first_fail_line; +extern int num_tests_selected; +extern int old_failure_count; +extern std::string first_fail_file; +extern std::string bbackupd_args, bbstored_args, bbackupquery_args, test_args; +extern std::list<std::string> run_only_named_tests; +extern std::string current_test_name; +extern std::map<std::string, std::string> s_test_status; + +//! Simplifies calling setUp() with the current function name in each test. +#define SETUP() \ + if (!setUp(__FUNCTION__)) return true; \ + try \ + { // left open for TEARDOWN() + +#define TEARDOWN() \ + return tearDown(); \ + } \ + catch (BoxException &e) \ + { \ + BOX_NOTICE(__FUNCTION__ << " errored: " << e.what()); \ + num_failures++; \ + tearDown(); \ + s_test_status[__FUNCTION__] = "ERRORED"; \ + return false; \ + } + +//! End the current test. Only use within a test function, because it just returns false! +#define FAIL { \ + std::ostringstream os; \ + os << "failed at " << __FUNCTION__ << ":" << __LINE__; \ + s_test_status[current_test_name] = os.str(); \ + return fail(); \ +} + +#define TEST_FAIL_WITH_MESSAGE(msg) \ +{ \ + if (num_failures == 0) \ + { \ + first_fail_file = __FILE__; \ + first_fail_line = __LINE__; \ + } \ + num_failures++; \ + BOX_ERROR("**** TEST FAILURE: " << msg << " at " << __FILE__ << \ + ":" << __LINE__); \ +} + +#define TEST_ABORT_WITH_MESSAGE(msg) {TEST_FAIL_WITH_MESSAGE(msg); return 1;} + +#define TEST_THAT_OR(condition, or_command) \ + if(!(condition)) \ + { \ + TEST_FAIL_WITH_MESSAGE("Condition [" #condition "] failed"); \ + or_command; \ + } +#define TEST_THAT(condition) TEST_THAT_OR(condition,) +#define TEST_THAT_ABORTONFAIL(condition) {if(!(condition)) TEST_ABORT_WITH_MESSAGE("Condition [" #condition "] failed")} +#define TEST_THAT_THROWONFAIL(condition) \ + TEST_THAT_OR(condition, THROW_EXCEPTION_MESSAGE(CommonException, \ + AssertFailed, "Condition [" #condition "] failed")); + +// NOTE: The 0- bit is to allow this to work with stuff which has negative constants for flags (eg ConnectionException) +#define TEST_CHECK_THROWS_AND_OR(statement, excepttype, subtype, and_command, or_command) \ + { \ + 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; \ + and_command; \ + } \ + catch(...) \ + { \ + throw; \ + } \ + if(!didthrow) \ + { \ + TEST_FAIL_WITH_MESSAGE("Didn't throw exception " #excepttype "(" #subtype ")"); \ + or_command; \ + } \ + } +#define TEST_CHECK_THROWS(statement, excepttype, subtype) \ + TEST_CHECK_THROWS_AND_OR(statement, excepttype, subtype,,) + +// utility macro for comparing two strings in a line +#define TEST_EQUAL_OR(_expected, _found, or_command) \ +{ \ + std::ostringstream _oss1; \ + _oss1 << _expected; \ + std::string _exp_str = _oss1.str(); \ + \ + std::ostringstream _oss2; \ + _oss2 << _found; \ + std::string _found_str = _oss2.str(); \ + \ + if(_exp_str != _found_str) \ + { \ + BOX_ERROR("Expected <" << _exp_str << "> but found <" << \ + _found_str << ">"); \ + \ + std::ostringstream _oss3; \ + _oss3 << #_found << " != " << #_expected; \ + \ + TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ + or_command; \ + } \ +} +#define TEST_EQUAL(_expected, _found) \ + TEST_EQUAL_OR(_expected, _found,) + +// utility macro for comparing two strings in a line +#define TEST_EQUAL_LINE(_expected, _found, _line) \ +{ \ + std::ostringstream _oss1; \ + _oss1 << _expected; \ + std::string _exp_str = _oss1.str(); \ + \ + std::ostringstream _oss2; \ + _oss2 << _found; \ + std::string _found_str = _oss2.str(); \ + \ + if(_exp_str != _found_str) \ + { \ + std::ostringstream _ossl; \ + _ossl << _line; \ + std::string _line_str = _ossl.str(); \ + printf("Expected <%s> but found <%s> in <%s>\n", \ + _exp_str.c_str(), _found_str.c_str(), _line_str.c_str()); \ + \ + std::ostringstream _oss3; \ + _oss3 << #_found << " != " << #_expected << " in " << _line; \ + \ + TEST_FAIL_WITH_MESSAGE(_oss3.str().c_str()); \ + } \ +} + +// utility macros for testing a string/output line +#define TEST_LINE(_condition, _line) \ + TEST_THAT(_condition); \ + if (!(_condition)) \ + { \ + std::ostringstream _ossl; \ + _ossl << _line; \ + std::string _line_str = _ossl.str(); \ + printf("Test failed on <%s>\n", _line_str.c_str()); \ + } + +#define TEST_LINE_OR(_condition, _line, _or_command) \ + TEST_LINE(_condition, _line); \ + if(!(_condition)) \ + { \ + _or_command; \ + } + +#define TEST_STARTSWITH(expected, actual) \ + TEST_EQUAL_LINE(expected, actual.substr(0, std::string(expected).size()), actual); + +//! Sets up (cleans up) test environment at the start of every test. +bool setUp(const char* function_name); + +//! Checks account for errors and shuts down daemons at end of every test. +bool tearDown(); + +//! Like tearDown() but returns false, because a test failure was detected. +bool fail(); + +//! Report final status of all tests, and return the correct value to test main(). +int finish_test_suite(); + +bool TestFileExists(const char *Filename); +bool TestDirExists(const char *Filename); + +// -1 if doesn't exist +int TestGetFileSize(const std::string& Filename); +std::string ConvertPaths(const std::string& rOriginal); +int RunCommand(const std::string& rCommandLine); +bool ServerIsAlive(int pid); +int ReadPidFile(const char *pidFile); +int LaunchServer(const std::string& rCommandLine, const char *pidFile); +int WaitForServerStartup(const char *pidFile, int pidIfKnown); + +#define TestRemoteProcessMemLeaks(filename) \ + TestRemoteProcessMemLeaksFunc(filename, __FILE__, __LINE__) + +void TestRemoteProcessMemLeaksFunc(const char *filename, + const char* file, int line); + +void force_sync(); +void wait_for_sync_start(); +void wait_for_sync_end(); +void sync_and_wait(); +void terminate_bbackupd(int pid); + +// Wait a given number of seconds for something to complete +void wait_for_operation(int seconds, const char* message); +void safe_sleep(int seconds); +std::auto_ptr<Configuration> load_config_file(const std::string& config_file, + const ConfigurationVerify& verify); + +#ifdef _MSC_VER + // Our CMakeFiles compile tests to different executable filenames, + // e.g. test_common.exe instead of _test.exe. + #define TEST_EXECUTABLE BOX_MODULE ".exe" +#else + #define TEST_EXECUTABLE "./_test" +#endif + +#endif // TEST__H diff --git a/lib/common/Timer.cpp b/lib/common/Timer.cpp new file mode 100644 index 00000000..6ce84b7d --- /dev/null +++ b/lib/common/Timer.cpp @@ -0,0 +1,672 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Timer.cpp +// Purpose: Generic timers which execute arbitrary code when +// they expire. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- + +#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 + +#include <signal.h> +#include <cstring> + +#include "Timer.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +std::vector<Timer*>* Timers::spTimers = NULL; +bool Timers::sRescheduleNeeded = false; + +#define TIMER_ID "timer " << mName << " (" << this << ") " +#define TIMER_ID_OF(t) "timer " << (t).GetName() << " (" << &(t) << ")" + +typedef void (*sighandler_t)(int); + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Init() +// Purpose: Initialise timers, prepare signal handler +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Init() +{ + ASSERT(!spTimers); + + #if defined WIN32 && ! defined PLATFORM_CYGWIN + // no init needed + #else + struct sigaction newact, oldact; + newact.sa_handler = Timers::SignalHandler; + newact.sa_flags = SA_RESTART; + sigemptyset(&newact.sa_mask); + if (::sigaction(SIGALRM, &newact, &oldact) != 0) + { + THROW_SYS_ERROR("Failed to install signal handler", + CommonException, Internal); + } + ASSERT(oldact.sa_handler == 0); + #endif // WIN32 && !PLATFORM_CYGWIN + + spTimers = new std::vector<Timer*>; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Cleanup() +// Purpose: Clean up timers, stop signal handler +// Created: 6/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Cleanup(bool throw_exception_if_not_initialised) +{ + if (throw_exception_if_not_initialised) + { + ASSERT(spTimers); + if (!spTimers) + { + BOX_ERROR("Tried to clean up timers when not initialised!"); + return; + } + } + else + { + if (!spTimers) + { + return; + } + } + + #if defined WIN32 && ! defined PLATFORM_CYGWIN + // no cleanup needed + #else + struct itimerval timeout; + memset(&timeout, 0, sizeof(timeout)); + + if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) + { + THROW_SYS_ERROR("Failed to set interval timer", + CommonException, Internal); + } + + struct sigaction newact, oldact; + newact.sa_handler = SIG_DFL; + newact.sa_flags = SA_RESTART; + sigemptyset(&(newact.sa_mask)); + if (::sigaction(SIGALRM, &newact, &oldact) != 0) + { + THROW_SYS_ERROR("Failed to remove signal handler", + CommonException, Internal); + } + ASSERT(oldact.sa_handler == Timers::SignalHandler); + #endif // WIN32 && !PLATFORM_CYGWIN + + spTimers->clear(); + delete spTimers; + spTimers = NULL; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::AssertInitialised() +// Purpose: Throw an assertion error if timers are not ready +// NOW. It's a common mistake (for me) when writing +// tests to forget to initialise timers first. +// Created: 15/05/2014 +// +// -------------------------------------------------------------------------- + +void Timers::AssertInitialised() +{ + if (!spTimers) + { + THROW_EXCEPTION(CommonException, TimersNotInitialised); + } + ASSERT(spTimers); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Add(Timer&) +// Purpose: Add a new timer to the set, and reschedule next wakeup +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Add(Timer& rTimer) +{ + ASSERT(spTimers); + ASSERT(&rTimer); + BOX_TRACE(TIMER_ID_OF(rTimer) " added to global queue, rescheduling"); + spTimers->push_back(&rTimer); + Reschedule(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Remove(Timer&) +// Purpose: Removes the timer from the set (preventing it from +// being called) and reschedule next wakeup +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Remove(Timer& rTimer) +{ + ASSERT(&rTimer); + + if(!spTimers) + { + BOX_WARNING(TIMER_ID_OF(rTimer) " was still active after " + "timer subsystem was cleaned up, already removed."); + return; + } + + ASSERT(spTimers); + BOX_TRACE(TIMER_ID_OF(rTimer) " removed from global queue, rescheduling"); + + bool restart = true; + while (restart) + { + restart = false; + + for (std::vector<Timer*>::iterator i = spTimers->begin(); + i != spTimers->end(); i++) + { + if (&rTimer == *i) + { + spTimers->erase(i); + restart = true; + break; + } + } + } + + Reschedule(); +} + +void Timers::RequestReschedule() +{ + sRescheduleNeeded = true; +} + +void Timers::RescheduleIfNeeded() +{ + if (sRescheduleNeeded) + { + Reschedule(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::Reschedule() +// Purpose: Recalculate when the next wakeup is due +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::Reschedule() +{ + ASSERT(spTimers); + if (spTimers == NULL) + { + THROW_EXCEPTION(CommonException, TimersNotInitialised); + } + + #ifndef WIN32 + struct sigaction oldact; + if (::sigaction(SIGALRM, NULL, &oldact) != 0) + { + THROW_SYS_ERROR("Failed to check signal handler", + CommonException, Internal); + } + + ASSERT(oldact.sa_handler == Timers::SignalHandler); + + if (oldact.sa_handler != Timers::SignalHandler) + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "Signal handler was " << (void *)oldact.sa_handler << + ", expected " << (void *)Timers::SignalHandler); + } + #endif + + // Clear the reschedule-needed flag to false before we start. + // If a timer event occurs while we are scheduling, then we + // may or may not need to reschedule again, but this way + // we will do it anyway. + sRescheduleNeeded = false; + +#ifdef WIN32 + // win32 timers need no management +#else + box_time_t timeNow = GetCurrentBoxTime(); + int64_t timeToNextEvent; + std::string nameOfNextEvent; + + // scan for, trigger and remove expired timers. Removal requires + // us to restart the scan each time, due to std::vector semantics. + bool restart = true; + while (restart) + { + restart = false; + timeToNextEvent = 0; + + for (std::vector<Timer*>::iterator i = spTimers->begin(); + i != spTimers->end(); i++) + { + Timer& rTimer = **i; + int64_t timeToExpiry = rTimer.GetExpiryTime() - timeNow; + + if (timeToExpiry <= 0) + { + BOX_TRACE(TIMER_ID_OF(**i) " has expired, " + "triggering " << + BOX_FORMAT_MICROSECONDS(-timeToExpiry) << + " late"); + rTimer.OnExpire(); + spTimers->erase(i); + restart = true; + break; + } + else + { + /* + BOX_TRACE(TIMER_ID_OF(**i) " has not expired, " + "triggering in " << + FORMAT_MICROSECONDS(timeToExpiry) << + " seconds"); + */ + } + + if (timeToNextEvent == 0 || timeToNextEvent > timeToExpiry) + { + timeToNextEvent = timeToExpiry; + nameOfNextEvent = rTimer.GetName(); + } + } + } + + if (timeToNextEvent == 0) + { + BOX_TRACE("timer: no more events, going to sleep."); + } + else + { + BOX_TRACE("timer: next event: " << nameOfNextEvent << " at " << + FormatTime(timeNow + timeToNextEvent, false, true)); + } + + struct itimerval timeout; + memset(&timeout, 0, sizeof(timeout)); + + timeout.it_value.tv_sec = BoxTimeToSeconds(timeToNextEvent); + timeout.it_value.tv_usec = (int) + (BoxTimeToMicroSeconds(timeToNextEvent) + % MICRO_SEC_IN_SEC); + + if(::setitimer(ITIMER_REAL, &timeout, NULL) != 0) + { + THROW_SYS_ERROR("Failed to initialise system timer", + CommonException, Internal); + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: static void Timers::SignalHandler(unused) +// Purpose: Called as signal handler. Nothing is safe in a signal +// handler, not even traversing the list of timers, so +// just request a reschedule in future, which will do +// that for us, and trigger any expired timers at that +// time. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- +void Timers::SignalHandler(int unused) +{ + // ASSERT(spTimers); + Timers::RequestReschedule(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(size_t timeoutMillis, +// const std::string& rName) +// Purpose: Standard timer constructor, takes a timeout in +// seconds from now, and an optional name for +// logging purposes. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::Timer(size_t timeoutMillis, const std::string& rName) +: mExpires(0), + mExpired(false), + mName(rName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif +{ + Set(timeoutMillis, true /* isInit */); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Start() +// Purpose: This internal function recalculates the remaining +// time (timeout) from the expiry time, and then calls +// Start(timeoutMillis). +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start() +{ + box_time_t timeNow = GetCurrentBoxTime(); + int64_t timeToExpiry = mExpires - timeNow; + + if (timeToExpiry <= 0) + { + BOX_WARNING(TIMER_ID << "fudging expiry from -" << + BOX_FORMAT_MICROSECONDS(-timeToExpiry)) + timeToExpiry = 1; + } + + Start(timeToExpiry / MICRO_SEC_IN_MILLI_SEC); +} + +// -------------------------------------------------------------------------- +// +// Function +// 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. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Start(int64_t timeoutMillis) +{ + ASSERT(mExpires != 0); + Timers::Add(*this); + +#ifdef WIN32 + // only call me once! + ASSERT(mTimerHandle == INVALID_HANDLE_VALUE); + + // 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. + timeoutMillis -= 20; + + // Set a system timer to call our timer routine + if (CreateTimerQueueTimer(&mTimerHandle, NULL, TimerRoutine, + (PVOID)this, timeoutMillis, 0, WT_EXECUTEINTIMERTHREAD) + == FALSE) + { + BOX_ERROR(TIMER_ID "failed to create timer: " << + GetErrorMessage(GetLastError())); + mTimerHandle = INVALID_HANDLE_VALUE; + } + else + { + BOX_INFO(TIMER_ID << "set for " << timeoutMillis << " ms"); + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Stop() +// 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. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::Stop() +{ + if (mExpires != 0) + { + Timers::Remove(*this); + } + +#ifdef WIN32 + if (mTimerHandle != INVALID_HANDLE_VALUE) + { + if (DeleteTimerQueueTimer(NULL, mTimerHandle, + INVALID_HANDLE_VALUE) == FALSE) + { + BOX_ERROR(TIMER_ID "failed to delete timer: " << + GetErrorMessage(GetLastError())); + } + mTimerHandle = INVALID_HANDLE_VALUE; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::~Timer() +// Purpose: Destructor for Timer objects. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::~Timer() +{ + #ifndef BOX_RELEASE_BUILD + BOX_TRACE(TIMER_ID "destroyed"); + #endif + + 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 +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::Timer(Timer& rToCopy) +// Purpose: Copy constructor for Timer objects. Creates a new +// timer that will trigger at the same time as the +// original. The original will usually be discarded. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer::Timer(const Timer& rToCopy) +: mExpires(rToCopy.mExpires), + mExpired(rToCopy.mExpired), + mName(rToCopy.mName) +#ifdef WIN32 +, mTimerHandle(INVALID_HANDLE_VALUE) +#endif +{ + LogAssignment(rToCopy); + + if (!mExpired && mExpires != 0) + { + Start(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::operator=(const Timer& rToCopy) +// Purpose: Assignment operator for Timer objects. Works +// exactly the same as the copy constructor, except +// that if the receiving timer is already running, +// it is stopped first. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +Timer& Timer::operator=(const Timer& rToCopy) +{ + LogAssignment(rToCopy); + + Stop(); + + mExpires = rToCopy.mExpires; + mExpired = rToCopy.mExpired; + mName = rToCopy.mName; + + if (!mExpired && mExpires != 0) + { + Start(); + } + + return *this; +} + +// -------------------------------------------------------------------------- +// +// Function +// 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 +// Name: Timer::OnExpire() +// Purpose: Method called by Timers::Reschedule (on Unixes) +// on next poll after timer expires, or from +// Timer::TimerRoutine (on Windows) from a separate +// thread managed by the OS. Marks the timer as +// expired for future reference. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +void Timer::OnExpire() +{ + #ifndef BOX_RELEASE_BUILD + BOX_TRACE(TIMER_ID "fired"); + #endif + + mExpired = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Timer::TimerRoutine(PVOID lpParam, +// BOOLEAN TimerOrWaitFired) +// Purpose: Static method called by the Windows OS when a +// TimerQueue timer expires. +// Created: 27/07/2008 +// +// -------------------------------------------------------------------------- + +#ifdef WIN32 +VOID CALLBACK Timer::TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired) +{ + Timer* pTimer = (Timer*)lpParam; + pTimer->OnExpire(); + // is it safe to write to write debug output from a timer? + // e.g. to write to the Event Log? +} +#endif diff --git a/lib/common/Timer.h b/lib/common/Timer.h new file mode 100644 index 00000000..68592aaa --- /dev/null +++ b/lib/common/Timer.h @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Timer.h +// Purpose: Generic timers which execute arbitrary code when +// they expire. +// Created: 5/11/2006 +// +// -------------------------------------------------------------------------- + +#ifndef TIMER__H +#define TIMER__H + +#ifdef HAVE_SYS_TIME_H + #include <sys/time.h> +#endif + +#include <vector> + +#include "BoxTime.h" + +#include "MemLeakFindOn.h" + +class Timer; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Timers +// Purpose: Static class to manage all timers and arrange +// efficient delivery of wakeup signals +// Created: 19/3/04 +// +// -------------------------------------------------------------------------- +class Timers +{ + private: + static std::vector<Timer*>* spTimers; + static void Reschedule(); + + static bool sRescheduleNeeded; + static void SignalHandler(int iUnused); + + public: + static void Init(); + static void Cleanup(bool throw_exception_if_not_initialised = true); + static void AssertInitialised(); + static void Add (Timer& rTimer); + static void Remove(Timer& rTimer); + static void RequestReschedule(); + static void RescheduleIfNeeded(); +}; + +class Timer +{ +public: + Timer(size_t timeoutMillis, const std::string& rName = ""); + virtual ~Timer(); + Timer(const Timer &); + Timer &operator=(const Timer &); + + box_time_t GetExpiryTime() { return mExpires; } + virtual void OnExpire(); + bool HasExpired() + { + Timers::RescheduleIfNeeded(); + return mExpired; + } + + const std::string& GetName() const { return mName; } + virtual void Reset(size_t timeoutMillis); + +private: + box_time_t mExpires; + bool mExpired; + std::string mName; + + void Start(); + void Start(int64_t timeoutMillis); + void Stop(); + void LogAssignment(const Timer &From); + virtual void Set(size_t timeoutMillis, bool isReset); + + #ifdef WIN32 + HANDLE mTimerHandle; + static VOID CALLBACK TimerRoutine(PVOID lpParam, + BOOLEAN TimerOrWaitFired); + #endif +}; + +#include "MemLeakFindOff.h" + +#endif // TIMER__H diff --git a/lib/common/UnixUser.cpp b/lib/common/UnixUser.cpp new file mode 100644 index 00000000..32acacfa --- /dev/null +++ b/lib/common/UnixUser.cpp @@ -0,0 +1,123 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: UnixUser.cpp +// Purpose: Interface for managing the UNIX user of the current process +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_PWD_H + #include <pwd.h> +#endif + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include "UnixUser.h" +#include "CommonException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::UnixUser(const char *) +// Purpose: Constructor, initialises to info of given username +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +UnixUser::UnixUser(const std::string& Username) + : mUID(0), + mGID(0), + mRevertOnDestruction(false) +{ + // Get password info + struct passwd *pwd = ::getpwnam(Username.c_str()); + if(pwd == 0) + { + THROW_EXCEPTION(CommonException, CouldNotLookUpUsername) + } + + // Store UID and GID + mUID = pwd->pw_uid; + mGID = pwd->pw_gid; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::UnixUser(uid_t, gid_t) +// Purpose: Construct from given UNIX user ID and group ID +// Created: 15/3/04 +// +// -------------------------------------------------------------------------- +UnixUser::UnixUser(uid_t UID, gid_t GID) + : mUID(UID), + mGID(GID), + mRevertOnDestruction(false) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::~UnixUser() +// Purpose: Destructor -- reverts to previous user if the change wasn't perminant +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +UnixUser::~UnixUser() +{ + if(mRevertOnDestruction) + { + // Revert to "real" user and group id of the process + if(::setegid(::getgid()) != 0 || ::seteuid(::getuid()) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotRestoreProcessUser) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: UnixUser::ChangeProcessUser(bool) +// Purpose: Change the process user and group ID to the user. If Temporary == true +// the process username will be changed back when the object is destructed. +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +void UnixUser::ChangeProcessUser(bool Temporary) +{ + if(Temporary) + { + // Change temporarily (change effective only) + if(::setegid(mGID) != 0 || ::seteuid(mUID) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) + } + + // Mark for change on destruction + mRevertOnDestruction = true; + } + else + { + // Change permanently (change all UIDs and GIDs) + if(::setgid(mGID) != 0 || ::setuid(mUID) != 0) + { + THROW_EXCEPTION(CommonException, CouldNotChangeProcessUser) + } + } +} + + + + diff --git a/lib/common/UnixUser.h b/lib/common/UnixUser.h new file mode 100644 index 00000000..dafe6aa7 --- /dev/null +++ b/lib/common/UnixUser.h @@ -0,0 +1,37 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: UnixUser.h +// Purpose: Interface for managing the UNIX user of the current process +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- + +#ifndef UNIXUSER__H +#define UNIXUSER__H + +class UnixUser +{ +public: + UnixUser(const std::string& Username); + UnixUser(uid_t UID, gid_t GID); + ~UnixUser(); +private: + // no copying allowed + UnixUser(const UnixUser &); + UnixUser &operator=(const UnixUser &); +public: + + void ChangeProcessUser(bool Temporary = false); + + uid_t GetUID() {return mUID;} + gid_t GetGID() {return mGID;} + +private: + uid_t mUID; + gid_t mGID; + bool mRevertOnDestruction; +}; + +#endif // UNIXUSER__H + diff --git a/lib/common/Utils.cpp b/lib/common/Utils.cpp new file mode 100644 index 00000000..4325dd88 --- /dev/null +++ b/lib/common/Utils.cpp @@ -0,0 +1,410 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Utils.cpp +// Purpose: Utility function +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> + +#include <cstdlib> + +#ifdef HAVE_EXECINFO_H + #include <execinfo.h> + #include <stdlib.h> +#endif + +#ifdef HAVE_CXXABI_H + #include <cxxabi.h> +#endif + +#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" + +#include "MemLeakFindOn.h" + +std::string GetBoxBackupVersion() +{ + return BOX_VERSION; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SplitString(const std::string &, char, std::vector<std::string> &) +// Purpose: Splits a string at a given character +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SplitString(std::string String, char SplitOn, std::vector<std::string> &rOutput) +{ + // Split it up. + std::string::size_type begin = 0, end = 0, pos = 0; + + while(end = String.find_first_of(SplitOn, pos), end != String.npos) + { + // Is it preceded by the escape character? + if(end > 0 && String[end - 1] == '\\') + { + // Ignore this one, don't change begin, let the next + // match/fallback consume it instead. But remove the + // backslash from the string, and set pos to the + // current position, which no longer contains a + // separator character. + String.erase(end - 1, 1); + pos = end; + } + else + { + // Extract the substring and move past it. + unsigned int len = end - begin; + if(len >= 1) + { + rOutput.push_back(String.substr(begin, len)); + } + begin = end + 1; + pos = begin; + } + } + // Last string + if(begin < String.size()) + { + rOutput.push_back(String.substr(begin)); + } +/*#ifndef BOX_RELEASE_BUILD + BOX_TRACE("Splitting string '" << String << " on " << (char)SplitOn); + for(unsigned int l = 0; l < rOutput.size(); ++l) + { + BOX_TRACE(l << " = '" << rOutput[l] << "'"); + } +#endif*/ +} + +bool StartsWith(const std::string& prefix, const std::string& haystack) +{ + return haystack.size() >= prefix.size() && + haystack.substr(0, prefix.size()) == prefix; +} + +bool EndsWith(const std::string& suffix, const std::string& haystack) +{ + return haystack.size() >= suffix.size() && + haystack.substr(haystack.size() - suffix.size()) == suffix; +} + +std::string RemovePrefix(const std::string& prefix, const std::string& haystack) +{ + if(StartsWith(prefix, haystack)) + { + return haystack.substr(prefix.size()); + } + else + { + return ""; + } +} + +std::string RemoveSuffix(const std::string& suffix, const std::string& haystack) +{ + if(EndsWith(suffix, haystack)) + { + return haystack.substr(0, haystack.size() - suffix.size()); + } + else + { + return ""; + } +} + +static std::string demangle(const std::string& mangled_name) +{ + std::string demangled_name = mangled_name; + + #ifdef HAVE_CXXABI_H + char buffer[1024]; + int status; + size_t length = sizeof(buffer); + + char* result = abi::__cxa_demangle(mangled_name.c_str(), + buffer, &length, &status); + + if (status == 0) + { + demangled_name = result; + } + 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); + } + #endif // HAVE_CXXABI_H + + return demangled_name; +} + +void DumpStackBacktrace() +{ +#ifdef HAVE_EXECINFO_H + void *array[20]; + size_t size = backtrace(array, 20); + BOX_TRACE("Obtained " << size << " stack frames."); + + for(size_t i = 0; i < size; i++) + { + 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()); + } +#else // !HAVE_EXECINFO_H + BOX_TRACE("Backtrace support was not compiled in"); +#endif // HAVE_EXECINFO_H +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: FileExists(const std::string& rFilename) +// Purpose: Does a file exist? +// Created: 20/11/03 +// +// -------------------------------------------------------------------------- +bool FileExists(const std::string& rFilename, int64_t *pFileSize, + bool TreatLinksAsNotExisting) +{ + EMU_STRUCT_STAT st; + if(EMU_LSTAT(rFilename.c_str(), &st) != 0) + { + if(errno == ENOENT) + { + return false; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError); + } + } + + // is it a file? + if((st.st_mode & S_IFDIR) == 0) + { + if(TreatLinksAsNotExisting && ((st.st_mode & S_IFLNK) != 0)) + { + return false; + } + + // Yes. Tell caller the size? + if(pFileSize != 0) + { + *pFileSize = st.st_size; + } + + return true; + } + else + { + return false; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ObjectExists(const std::string& rFilename) +// Purpose: Does a object exist, and if so, is it a file or a directory? +// Created: 23/11/03 +// +// -------------------------------------------------------------------------- +int ObjectExists(const std::string& rFilename) +{ + EMU_STRUCT_STAT st; + if(EMU_STAT(rFilename.c_str(), &st) != 0) + { + if(errno == ENOENT) + { + return ObjectExists_NoObject; + } + else + { + THROW_EXCEPTION(CommonException, OSFileError); + } + } + + // is it a file or a dir? + return ((st.st_mode & S_IFDIR) == 0)?ObjectExists_File:ObjectExists_Dir; +} + +std::string HumanReadableSize(int64_t Bytes) +{ + double readableValue = Bytes; + std::string units = " B"; + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "kB"; + } + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "MB"; + } + + if (readableValue > 1024) + { + readableValue /= 1024; + units = "GB"; + } + + std::ostringstream result; + result << std::fixed << std::setprecision(2) << readableValue << + " " << units; + return result.str(); +} + +std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, + bool MachineReadable) +{ + std::ostringstream result; + + + if (MachineReadable) + { + result << (Bytes >> 10) << " kB, " << + std::setprecision(0) << ((Bytes*100)/Max) << "%"; + } + else + { + // Bar graph + char bar[17]; + unsigned int b = (int)((Bytes * (sizeof(bar)-1)) / Max); + if(b > sizeof(bar)-1) {b = sizeof(bar)-1;} + for(unsigned int l = 0; l < b; l++) + { + bar[l] = '*'; + } + for(unsigned int l = b; l < sizeof(bar) - 1; l++) + { + bar[l] = ' '; + } + bar[sizeof(bar)-1] = '\0'; + + result << std::fixed << + std::setw(10) << Blocks << " blocks, " << + std::setw(10) << HumanReadableSize(Bytes) << ", " << + std::setw(3) << std::setprecision(0) << + ((Bytes*100)/Max) << "% |" << bar << "|"; + } + + return result.str(); +} + +std::string FormatUsageLineStart(const std::string& rName, + bool MachineReadable) +{ + std::ostringstream result; + + if (MachineReadable) + { + result << rName << ": "; + } + else + { + result << std::setw(20) << std::right << rName << ": "; + } + + return result.str(); +} + +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 +} + + diff --git a/lib/common/Utils.h b/lib/common/Utils.h new file mode 100644 index 00000000..d306ce1c --- /dev/null +++ b/lib/common/Utils.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Utils.h +// Purpose: Utility function +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef UTILS__H +#define UTILS__H + +#include <string> +#include <vector> + +#include "MemLeakFindOn.h" + +std::string GetBoxBackupVersion(); + +void SplitString(std::string String, char SplitOn, std::vector<std::string> &rOutput); +bool StartsWith(const std::string& prefix, const std::string& haystack); +bool EndsWith(const std::string& prefix, const std::string& haystack); +std::string RemovePrefix(const std::string& prefix, const std::string& haystack); +std::string RemoveSuffix(const std::string& suffix, const std::string& haystack); + +void DumpStackBacktrace(); + +bool FileExists(const std::string& rFilename, int64_t *pFileSize = 0, + bool TreatLinksAsNotExisting = false); + +enum +{ + ObjectExists_NoObject = 0, + ObjectExists_File = 1, + ObjectExists_Dir = 2 +}; +int ObjectExists(const std::string& rFilename); +std::string HumanReadableSize(int64_t Bytes); +std::string FormatUsageBar(int64_t Blocks, int64_t Bytes, int64_t Max, + bool MachineReadable); +std::string FormatUsageLineStart(const std::string& rName, + bool MachineReadable); + +std::string BoxGetTemporaryDirectoryName(); + +#include "MemLeakFindOff.h" + +#endif // UTILS__H diff --git a/lib/common/WaitForEvent.cpp b/lib/common/WaitForEvent.cpp new file mode 100644 index 00000000..5646bfbf --- /dev/null +++ b/lib/common/WaitForEvent.cpp @@ -0,0 +1,197 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WaitForEvent.cpp +// Purpose: Generic waiting for events, using an efficient method (platform dependent) +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include <unistd.h> +#endif + +#include <errno.h> +#include <string.h> + +#include "WaitForEvent.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::WaitForEvent() +// Purpose: Constructor +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +#ifdef HAVE_KQUEUE +WaitForEvent::WaitForEvent(int Timeout) + : mKQueue(::kqueue()), + mpTimeout(0) +{ + if(mKQueue == -1) + { + THROW_EXCEPTION(CommonException, CouldNotCreateKQueue) + } + + // Set the choosen timeout + SetTimeout(Timeout); +} +#else +WaitForEvent::WaitForEvent(int Timeout) + : mTimeout(Timeout), + mpPollInfo(0) +{ +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::~WaitForEvent() +// Purpose: Destructor +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +WaitForEvent::~WaitForEvent() +{ +#ifdef HAVE_KQUEUE + ::close(mKQueue); + mKQueue = -1; +#else + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::SetTimeout +// Purpose: Sets the timeout for future wait calls +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +void WaitForEvent::SetTimeout(int Timeout) +{ +#ifdef HAVE_KQUEUE + // Generate timeout + if(Timeout != TimeoutInfinite) + { + mTimeout.tv_sec = Timeout / 1000; + mTimeout.tv_nsec = (Timeout % 1000) * 1000000; + } + + // Infinite or not? + mpTimeout = (Timeout != TimeoutInfinite)?(&mTimeout):(NULL); +#else + mTimeout = Timeout; +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitForEvent::Wait(int) +// Purpose: Wait for an event to take place. Returns a pointer to the object +// which has been signalled, or returns 0 for the timeout condition. +// Timeout specified in milliseconds. +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- +void *WaitForEvent::Wait() +{ +#ifdef HAVE_KQUEUE + // Event return structure + struct kevent e; + ::memset(&e, 0, sizeof(e)); + + switch(::kevent(mKQueue, NULL, 0, &e, 1, mpTimeout)) + { + case 0: + // Timeout + return 0; + break; + + case 1: + // Event happened! + return e.udata; + break; + + default: + // Interrupted system calls aren't an error, just equivalent to a timeout + if(errno != EINTR) + { + THROW_EXCEPTION(CommonException, KEventErrorWait) + } + return 0; + break; + } +#else + // Use poll() instead. + // Need to build the structures? + if(mpPollInfo == 0) + { + // Yes... + mpPollInfo = (struct pollfd *)::malloc((sizeof(struct pollfd) * mItems.size()) + 4); + if(mpPollInfo == 0) + { + throw std::bad_alloc(); + } + + // Build... + for(unsigned int l = 0; l < mItems.size(); ++l) + { + mpPollInfo[l].fd = mItems[l].fd; + mpPollInfo[l].events = mItems[l].events; + mpPollInfo[l].revents = 0; + } + } + + // Make sure everything is reset (don't really have to do this, but don't trust the OS) + for(unsigned int l = 0; l < mItems.size(); ++l) + { + mpPollInfo[l].revents = 0; + } + + // Poll! + switch(::poll(mpPollInfo, mItems.size(), mTimeout)) + { + case -1: + // Interrupted system calls aren't an error, just equivalent to a timeout + if(errno != EINTR) + { + THROW_EXCEPTION(CommonException, KEventErrorWait) + } + return 0; + break; + case 0: // timed out + return 0; + break; + default: // got some thing... + // control flows on... + break; + } + + // Find the item which was ready + for(unsigned int s = 0; s < mItems.size(); ++s) + { + if(mpPollInfo[s].revents & POLLIN) + { + return mItems[s].item; + break; + } + } +#endif + + return 0; +} + diff --git a/lib/common/WaitForEvent.h b/lib/common/WaitForEvent.h new file mode 100644 index 00000000..a80761ef --- /dev/null +++ b/lib/common/WaitForEvent.h @@ -0,0 +1,152 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WaitForEvent.h +// Purpose: Generic waiting for events, using an efficient method (platform dependent) +// Created: 9/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef WAITFOREVENT__H +#define WAITFOREVENT__H + +#include <cstdlib> + +#ifdef HAVE_KQUEUE + #include <sys/event.h> + #include <sys/time.h> +#else + #include <vector> + #ifndef WIN32 + #include <poll.h> + #endif +#endif + +#include <cstdlib> + +#include "CommonException.h" + +#include "MemLeakFindOn.h" + +class WaitForEvent +{ +public: + WaitForEvent(int Timeout = TimeoutInfinite); + ~WaitForEvent(); +private: + // No copying. + WaitForEvent(const WaitForEvent &); + WaitForEvent &operator=(const WaitForEvent &); +public: + + enum + { + TimeoutInfinite = -1 + }; + + void SetTimeout(int Timeout = TimeoutInfinite); + + void *Wait(); + +#ifndef HAVE_KQUEUE + typedef struct + { + int fd; + short events; + void *item; + } ItemInfo; +#endif + + // -------------------------------------------------------------------------- + // + // Function + // Name: WaitForEvent::Add(const Type &, int) + // Purpose: Adds an event to the list of items to wait on. The flags are passed to the object. + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + template<typename T> + void Add(const T *pItem, int Flags = 0) + { + ASSERT(pItem != 0); +#ifdef HAVE_KQUEUE + struct kevent e; + pItem->FillInKEvent(e, Flags); + // Fill in extra flags to say what to do + e.flags |= EV_ADD; + e.udata = (void*)pItem; + if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) + { + THROW_EXCEPTION(CommonException, KEventErrorAdd) + } +#else + // Add item + ItemInfo i; + pItem->FillInPoll(i.fd, i.events, Flags); + i.item = (void*)pItem; + mItems.push_back(i); + // Delete any pre-prepared poll info, as it's now out of date + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } +#endif + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: WaitForEvent::Remove(const Type &, int) + // Purpose: Removes an event from the list of items to wait on. The flags are passed to the object. + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + template<typename T> + void Remove(const T *pItem, int Flags = 0) + { + ASSERT(pItem != 0); +#ifdef HAVE_KQUEUE + struct kevent e; + pItem->FillInKEvent(e, Flags); + // Fill in extra flags to say what to do + e.flags |= EV_DELETE; + e.udata = (void*)pItem; + if(::kevent(mKQueue, &e, 1, NULL, 0, NULL) == -1) + { + THROW_EXCEPTION(CommonException, KEventErrorRemove) + } +#else + if(mpPollInfo != 0) + { + ::free(mpPollInfo); + mpPollInfo = 0; + } + for(std::vector<ItemInfo>::iterator i(mItems.begin()); i != mItems.end(); ++i) + { + if(i->item == pItem) + { + mItems.erase(i); + return; + } + } +#endif + } + +private: +#ifdef HAVE_KQUEUE + int mKQueue; + struct timespec mTimeout; + struct timespec *mpTimeout; +#else + int mTimeout; + std::vector<ItemInfo> mItems; + struct pollfd *mpPollInfo; +#endif +}; + +#include "MemLeakFindOff.h" + +#endif // WAITFOREVENT__H + + diff --git a/lib/common/ZeroStream.cpp b/lib/common/ZeroStream.cpp new file mode 100644 index 00000000..e1342e6f --- /dev/null +++ b/lib/common/ZeroStream.cpp @@ -0,0 +1,170 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ZeroStream.cpp +// Purpose: An IOStream which returns all zeroes up to a certain size +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "ZeroStream.h" +#include "CommonException.h" + +#include <string.h> + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::ZeroStream(IOStream::pos_type) +// Purpose: Constructor +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- +ZeroStream::ZeroStream(IOStream::pos_type size) +: mSize(size), mPosition(0) +{ } + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Read(void *, int) +// Purpose: Reads bytes from the file +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +int ZeroStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + ASSERT(NBytes > 0); + + int bytesToRead = NBytes; + + if (bytesToRead > mSize - mPosition) + { + bytesToRead = mSize - mPosition; + } + + memset(pBuffer, 0, bytesToRead); + mPosition += bytesToRead; + + return bytesToRead; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::BytesLeftToRead() +// Purpose: Returns number of bytes to read (may not be most efficient function ever) +// Created: 2007/01/16 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ZeroStream::BytesLeftToRead() +{ + return mSize - mPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Write(void *, int) +// Purpose: Writes bytes to the underlying stream (not supported) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::GetPosition() +// Purpose: Get position in stream +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ZeroStream::GetPosition() const +{ + return mPosition; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Seek(pos_type, int) +// Purpose: Seeks within file, as lseek, invalidate buffer +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Seek(IOStream::pos_type Offset, int SeekType) +{ + switch (SeekType) + { + case SeekType_Absolute: + { + mPosition = Offset; + } + break; + + case SeekType_Relative: + { + mPosition += Offset; + } + break; + + case SeekType_End: + { + mPosition = mSize - Offset; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::Close() +// Purpose: Closes the underlying stream (not needed) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void ZeroStream::Close() +{ + THROW_EXCEPTION(CommonException, NotSupported); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::StreamDataLeft() +// Purpose: Any data left to write? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ZeroStream::StreamDataLeft() +{ + return (BytesLeftToRead() > 0); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ZeroStream::StreamClosed() +// Purpose: Is the stream closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool ZeroStream::StreamClosed() +{ + return false; +} + diff --git a/lib/common/ZeroStream.h b/lib/common/ZeroStream.h new file mode 100644 index 00000000..f91221b0 --- /dev/null +++ b/lib/common/ZeroStream.h @@ -0,0 +1,40 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ZeroStream.h +// Purpose: An IOStream which returns all zeroes up to a certain size +// Created: 2007/04/28 +// +// -------------------------------------------------------------------------- + +#ifndef ZEROSTREAM__H +#define ZEROSTREAM__H + +#include "IOStream.h" + +class ZeroStream : public IOStream +{ +private: + IOStream::pos_type mSize, mPosition; + +public: + ZeroStream(IOStream::pos_type mSize); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + ZeroStream(const ZeroStream &rToCopy); +}; + +#endif // ZEROSTREAM__H + + diff --git a/lib/common/makeexception.pl.in b/lib/common/makeexception.pl.in new file mode 100755 index 00000000..bddaa94a --- /dev/null +++ b/lib/common/makeexception.pl.in @@ -0,0 +1,250 @@ +#!@PERL@ + +# global exception list file +my $global_list = '../../ExceptionCodes.txt'; + + +my @exception; +my @exception_desc; +my $class; +my $class_number; + +# read the description! + +open EXCEPTION_DESC,$ARGV[0] or die "Can't open $ARGV[0]"; + +while(<EXCEPTION_DESC>) +{ + chomp; s/\A\s+//; s/#.+\Z//; s/\s+\Z//; s/\s+/ /g; + next unless m/\S/; + + if(m/\AEXCEPTION\s+(.+)\s+(\d+)\Z/) + { + $class = $1; + $class_number = $2; + } + else + { + my ($name,$number,$description) = split /\s+/,$_,3; + if($name eq '' || $number =~ m/\D/) + { + die "Bad line '$_'"; + } + if($exception[$number] ne '') + { + die "Duplicate exception number $number"; + } + $exception[$number] = $name; + $exception_desc[$number] = $description; + } +} + +die "Exception class and number not specified" unless $class ne '' && $class_number ne ''; + +close EXCEPTION_DESC; + +# write the code +print "Generating $class exception...\n"; + +open CPP,">autogen_${class}Exception.cpp" or die "Can't open cpp file for writing"; +open H,">autogen_${class}Exception.h" or die "Can't open h file for writing"; + +# write header file +my $guardname = uc 'AUTOGEN_'.$class.'EXCEPTION_H'; +print H <<__E; + +// Auto-generated file -- do not edit + +#ifndef $guardname +#define $guardname + +#include "BoxException.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ${class}Exception +// Purpose: Exception +// Created: autogen +// +// -------------------------------------------------------------------------- +class ${class}Exception : public BoxException +{ +public: + ${class}Exception(unsigned int SubType, + const std::string& rMessage = "") + : mSubType(SubType), mMessage(rMessage), + mWhat(GetMessage(SubType) + std::string(rMessage.empty() ? "" : ": ") + rMessage) + { + } + + ${class}Exception(const ${class}Exception &rToCopy) + : mSubType(rToCopy.mSubType), mMessage(rToCopy.mMessage), mWhat(rToCopy.mWhat) + { + } + + ~${class}Exception() throw () + { + } + + enum + { + ExceptionType = $class_number + }; + + enum + { +__E + +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + print H "\t\t".$exception[$e].' = '.$e.(($e==$#exception)?'':',')."\n" + } +} + +print H <<__E; + }; + + virtual unsigned int GetType() const throw(); + virtual unsigned int GetSubType() const throw(); + virtual const char *what() const throw(); + virtual const std::string& GetMessage() const + { + return mMessage; + } + static const char* GetMessage(int SubType); +private: + unsigned int mSubType; + std::string mMessage; + std::string mWhat; +}; + +#endif // $guardname +__E + +# ----------------------------------------------------------------------------------------------------------- + +print CPP <<__E; + +// Auto-generated file -- do not edit + +#include "Box.h" +#include "autogen_${class}Exception.h" + +#include "MemLeakFindOn.h" + +unsigned int ${class}Exception::GetType() const throw() +{ + return ${class}Exception::ExceptionType; +} + +unsigned int ${class}Exception::GetSubType() const throw() +{ + return mSubType; +} + +const char * ${class}Exception::what() const throw() +{ + return mWhat.c_str(); +} + +const char * ${class}Exception::GetMessage(int SubType) +{ + switch(SubType) + { +__E + +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + print CPP "\t\tcase ".$exception[$e].': return "'.$exception[$e].'";'."\n"; + } +} + +print CPP <<__E; + default: return "Unknown"; + } +} +__E + +close H; +close CPP; + +# update the global exception list +my $list_before; +my $list_after; +my $is_after = 0; +if(open CURRENT,$global_list) +{ + while(<CURRENT>) + { + next if m/\A#/; + + if(m/\AEXCEPTION TYPE (\w+) (\d+)/) + { + # check that the number isn't being reused + if($2 == $class_number && $1 ne $class) + { + die "Class number $class_number is being used by $class and $1 -- correct this.\n"; + } + if($2 > $class_number) + { + # This class comes after the current one (ensures numerical ordering) + $is_after = 1; + } + if($1 eq $class) + { + # skip this entry + while(<CURRENT>) + { + last if m/\AEND TYPE/; + } + $_ = ''; + } + } + + if($is_after) + { + $list_after .= $_; + } + else + { + $list_before .= $_; + } + } + + close CURRENT; +} + +open GLOBAL,">$global_list" or die "Can't open global exception code listing for writing"; + +print GLOBAL <<__E; +# +# automatically generated file, do not edit. +# +# This file lists all the exception codes used by the system. +# Use to look up more detailed descriptions of meanings of errors. +# +__E + +print GLOBAL $list_before; + +print GLOBAL "EXCEPTION TYPE $class $class_number\n"; +for(my $e = 0; $e <= $#exception; $e++) +{ + if($exception[$e] ne '') + { + my $ext = ($exception_desc[$e] ne '')?" - $exception_desc[$e]":''; + print GLOBAL "($class_number/$e) - ${class} ".$exception[$e].$ext."\n"; + } +} +print GLOBAL "END TYPE\n"; + +print GLOBAL $list_after; + +close GLOBAL; + + diff --git a/lib/compress/Compress.h b/lib/compress/Compress.h new file mode 100644 index 00000000..de38af2c --- /dev/null +++ b/lib/compress/Compress.h @@ -0,0 +1,197 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Compress.h +// Purpose: Interface to zlib compression +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSCONTEXT__H +#define COMPRESSCONTEXT__H + +#include <zlib.h> + +#include "CompressException.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: Compress +// Purpose: Interface to zlib compression, only very slight wrapper. +// (Use CompressStream for a more friendly interface.) +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- +template<bool Compressing> +class Compress +{ +public: + Compress() + : mFinished(false), + mFlush(Z_NO_FLUSH) + { + // initialise stream + mStream.zalloc = Z_NULL; + mStream.zfree = Z_NULL; + mStream.opaque = Z_NULL; + mStream.data_type = Z_BINARY; + + if((Compressing)?(deflateInit(&mStream, Z_DEFAULT_COMPRESSION)) + :(inflateInit(&mStream)) != Z_OK) + { + THROW_EXCEPTION(CompressException, InitFailed) + } + + mStream.avail_in = 0; + } + + ~Compress() + { + int r = 0; + if((r = ((Compressing)?(deflateEnd(&mStream)) + :(inflateEnd(&mStream)))) != Z_OK) + { + BOX_WARNING("zlib error code = " << r); + if(r == Z_DATA_ERROR) + { + BOX_WARNING("End of compress/decompress " + "without all input being consumed, " + "possible corruption?"); + } + else + { + THROW_EXCEPTION(CompressException, EndFailed) + } + } + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::InputRequired() + // Purpose: Input required yet? + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + bool InputRequired() + { + return mStream.avail_in <= 0; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::Input(const void *, int) + // Purpose: Set the input buffer ready for next output call. + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + void Input(const void *pInBuffer, int InLength) + { + // Check usage + if(mStream.avail_in != 0) + { + THROW_EXCEPTION(CompressException, BadUsageInputNotRequired) + } + + // Store info + mStream.next_in = (unsigned char *)pInBuffer; + mStream.avail_in = InLength; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::FinishInput() + // Purpose: When compressing, no more input will be given. + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + void FinishInput() + { + mFlush = Z_FINISH; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::Output(void *, int) + // Purpose: Get some output data + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + int Output(void *pOutBuffer, int OutLength, bool SyncFlush = false) + { + // need more input? + if(mStream.avail_in == 0 && mFlush != Z_FINISH && !SyncFlush) + { + return 0; + } + + // Buffers + mStream.next_out = (unsigned char *)pOutBuffer; + mStream.avail_out = OutLength; + + // Call one of the functions + int flush = mFlush; + if(SyncFlush && mFlush != Z_FINISH) + { + flush = Z_SYNC_FLUSH; + } + int ret = (Compressing)?(deflate(&mStream, flush)):(inflate(&mStream, flush)); + + if(SyncFlush && ret == Z_BUF_ERROR) + { + // No progress possible. Just return 0. + return 0; + } + + // Check errors + if(ret < 0) + { + BOX_WARNING("zlib error code = " << ret); + THROW_EXCEPTION(CompressException, TransformFailed) + } + + // Parse result + if(ret == Z_STREAM_END) + { + mFinished = true; + } + + // Return how much data was output + return OutLength - mStream.avail_out; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: Compress<Function>::OutputHasFinished() + // Purpose: No more output to be recieved + // Created: 5/12/03 + // + // -------------------------------------------------------------------------- + bool OutputHasFinished() + { + return mFinished; + } + + +private: + z_stream mStream; + bool mFinished; + int mFlush; +}; + +template<typename Integer> +Integer Compress_MaxSizeForCompressedData(Integer InLen) +{ + // Conservative rendition of the info found here: http://www.gzip.org/zlib/zlib_tech.html + int blocks = (InLen + 32*1024 - 1) / (32*1024); + return InLen + (blocks * 6) + 8; +} + + +#endif // COMPRESSCONTEXT__H + diff --git a/lib/compress/CompressException.h b/lib/compress/CompressException.h new file mode 100644 index 00000000..7e8c9ca2 --- /dev/null +++ b/lib/compress/CompressException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSEXCEPTION__H +#define COMPRESSEXCEPTION__H + +// Compatibility +#include "autogen_CompressException.h" + +#endif // COMPRESSEXCEPTION__H + diff --git a/lib/compress/CompressException.txt b/lib/compress/CompressException.txt new file mode 100644 index 00000000..278b76d3 --- /dev/null +++ b/lib/compress/CompressException.txt @@ -0,0 +1,12 @@ +EXCEPTION Compress 6 + +Internal 0 +InitFailed 1 +EndFailed 2 +BadUsageInputNotRequired 3 +TransformFailed 4 +CopyCompressStreamNotAllowed 5 +NullPointerPassedToCompressStream 6 +CompressStreamReadSupportNotRequested 7 Specify read in the constructor +CompressStreamWriteSupportNotRequested 8 Specify write in the constructor +CannotWriteToClosedCompressStream 9 diff --git a/lib/compress/CompressStream.cpp b/lib/compress/CompressStream.cpp new file mode 100644 index 00000000..f7728a21 --- /dev/null +++ b/lib/compress/CompressStream.cpp @@ -0,0 +1,425 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CompressStream.h +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> +#include <memory> + +#include "CompressStream.h" +#include "Compress.h" +#include "autogen_CompressException.h" + +#include "MemLeakFindOn.h" + +// How big a buffer to use +#ifndef BOX_RELEASE_BUILD + // debug! + #define BUFFER_SIZE 256 +#else + #define BUFFER_SIZE (32*1024) +#endif + +#define USE_READ_COMPRESSOR \ + CheckRead(); \ + Compress<false> *pDecompress = (Compress<false> *)mpReadCompressor; + +#define USE_WRITE_COMPRESSOR \ + CheckWrite(); \ + Compress<true> *pCompress = (Compress<true> *)mpWriteCompressor; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CompressStream(IOStream *, bool, bool, bool, bool) +// Purpose: Constructor +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed) + : mpStream(pStream), + mHaveOwnership(TakeOwnership), + mDecompressRead(DecompressRead), + mCompressWrite(CompressWrite), + mPassThroughWhenNotCompressed(PassThroughWhenNotCompressed), + mpReadCompressor(0), + mpWriteCompressor(0), + mpBuffer(0), + mIsClosed(false) +{ + if(mpStream == 0) + { + THROW_EXCEPTION(CompressException, NullPointerPassedToCompressStream) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::~CompressStream() +// Purpose: Destructor +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::~CompressStream() +{ + // Clean up compressors + if(mpReadCompressor) + { + delete ((Compress<false>*)mpReadCompressor); + mpReadCompressor = 0; + } + if(mpWriteCompressor) + { + delete ((Compress<true>*)mpWriteCompressor); + mpWriteCompressor = 0; + } + + // Delete the stream, if we have ownership + if(mHaveOwnership) + { + delete mpStream; + mpStream = 0; + } + + // Free any buffer + if(mpBuffer != 0) + { + ::free(mpBuffer); + mpBuffer = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CompressStream(const CompressStream &) +// Purpose: Copy constructor, will exception +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream::CompressStream(const CompressStream &) +{ + THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::operator=(const CompressStream &) +// Purpose: Assignment operator, will exception +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +CompressStream &CompressStream::operator=(const CompressStream &) +{ + THROW_EXCEPTION(CompressException, CopyCompressStreamNotAllowed) +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Read(void *, int, int) +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +int CompressStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + USE_READ_COMPRESSOR + if(pDecompress == 0) + { + return mpStream->Read(pBuffer, NBytes, Timeout); + } + + // Where is the buffer? (note if writing as well, read buffer is second in a block of two buffer sizes) + void *pbuf = (mDecompressRead && mCompressWrite)?(((uint8_t*)mpBuffer) + BUFFER_SIZE):mpBuffer; + + // Any data left to go? + if(!pDecompress->InputRequired()) + { + // Output some data from the existing data read + return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); + } + + // Read data into the buffer -- read as much as possible in one go + int s = mpStream->Read(pbuf, BUFFER_SIZE, Timeout); + if(s == 0) + { + return 0; + } + + // Give input to the compressor + pDecompress->Input(pbuf, s); + + // Output as much as possible + return pDecompress->Output(pBuffer, NBytes, true /* write as much as possible */); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Write(const void *, int) +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + USE_WRITE_COMPRESSOR + if(pCompress == 0) + { + mpStream->Write(pBuffer, NBytes, Timeout); + return; + } + + if(mIsClosed) + { + THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) + } + + // Give the data to the compressor + pCompress->Input(pBuffer, NBytes); + + // Write the data to the stream + WriteCompressedData(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::WriteAllBuffered() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::WriteAllBuffered(int Timeout) +{ + if(mIsClosed) + { + THROW_EXCEPTION(CompressException, CannotWriteToClosedCompressStream) + } + + // Just ask compressed data to be written out, but with the sync flag set + WriteCompressedData(true, Timeout); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::Close() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::Close() +{ + if(mCompressWrite) + { + USE_WRITE_COMPRESSOR + if(pCompress != 0) + { + // Flush anything from the write buffer + pCompress->FinishInput(); + WriteCompressedData(); + + // Mark as definitely closed + mIsClosed = true; + } + } + + // Close + mpStream->Close(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::WriteCompressedData(bool) +// Purpose: Private. Writes the output of the compressor to the stream, +// optionally doing a sync flush. +// Created: 28/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::WriteCompressedData(bool SyncFlush, int Timeout) +{ + USE_WRITE_COMPRESSOR + if(pCompress == 0) {THROW_EXCEPTION(CompressException, Internal)} + + int s = 0; + do + { + s = pCompress->Output(mpBuffer, BUFFER_SIZE, SyncFlush); + if(s > 0) + { + mpStream->Write(mpBuffer, s, Timeout); + } + } while(s > 0); + // Check assumption -- all input has been consumed + if(!pCompress->InputRequired()) {THROW_EXCEPTION(CompressException, Internal)} +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::StreamDataLeft() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +bool CompressStream::StreamDataLeft() +{ + USE_READ_COMPRESSOR + if(pDecompress == 0) + { + return mpStream->StreamDataLeft(); + } + + // Any bytes left in our buffer? + if(!pDecompress->InputRequired()) + { + // Still buffered data to decompress + return true; + } + + // Otherwise ask the stream + return mpStream->StreamDataLeft(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::StreamClosed() +// Purpose: As interface +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +bool CompressStream::StreamClosed() +{ + if(!mIsClosed && mpStream->StreamClosed()) + { + mIsClosed = true; + } + return mIsClosed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckRead() +// Purpose: Checks that everything is set up to read +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckRead() +{ + // Has the read compressor already been created? + if(mpReadCompressor != 0) + { + return; + } + + // Need to create one? + if(mDecompressRead) + { + mpReadCompressor = new Compress<false>(); + // And make sure there's a buffer + CheckBuffer(); + } + else + { + // Not decompressing. Should be passing through data? + if(!mPassThroughWhenNotCompressed) + { + THROW_EXCEPTION(CompressException, CompressStreamReadSupportNotRequested) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckWrite() +// Purpose: Checks that everything is set up to write +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckWrite() +{ + // Has the read compressor already been created? + if(mpWriteCompressor != 0) + { + return; + } + + // Need to create one? + if(mCompressWrite) + { + mpWriteCompressor = new Compress<true>(); + // And make sure there's a buffer + CheckBuffer(); + } + else + { + // Not decompressing. Should be passing through data? + if(!mPassThroughWhenNotCompressed) + { + THROW_EXCEPTION(CompressException, CompressStreamWriteSupportNotRequested) + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CompressStream::CheckBuffer() +// Purpose: Allocates the buffer for (de)compression operations +// Created: 28/5/04 +// +// -------------------------------------------------------------------------- +void CompressStream::CheckBuffer() +{ + // Already done + if(mpBuffer != 0) + { + return; + } + + // Allocate the buffer -- which may actually be two buffers in one + // The temporary use buffer is first (used by write only, so only present if writing) + // and the read buffer follows. + int size = BUFFER_SIZE; + if(mDecompressRead && mCompressWrite) + { + size *= 2; + } + BOX_TRACE("Allocating CompressStream buffer, size " << size); + mpBuffer = ::malloc(size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } +} + + diff --git a/lib/compress/CompressStream.h b/lib/compress/CompressStream.h new file mode 100644 index 00000000..7d6b2501 --- /dev/null +++ b/lib/compress/CompressStream.h @@ -0,0 +1,64 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CompressStream.h +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- + +#ifndef COMPRESSSTREAM__H +#define COMPRESSSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CompressStream +// Purpose: Compressing stream +// Created: 27/5/04 +// +// -------------------------------------------------------------------------- +class CompressStream : public IOStream +{ +public: + CompressStream(IOStream *pStream, bool TakeOwnership, + bool DecompressRead, bool CompressWrite, bool PassThroughWhenNotCompressed = false); + ~CompressStream(); +private: + // No copying (have implementations which exception) + CompressStream(const CompressStream &); + CompressStream &operator=(const CompressStream &); +public: + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void WriteAllBuffered(int Timeout = IOStream::TimeOutInfinite); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +protected: + void CheckRead(); + void CheckWrite(); + void CheckBuffer(); + void WriteCompressedData(bool SyncFlush = false, + int Timeout = IOStream::TimeOutInfinite); + +private: + IOStream *mpStream; + bool mHaveOwnership; + bool mDecompressRead; + bool mCompressWrite; + bool mPassThroughWhenNotCompressed; + // Avoid having to include Compress.h + void *mpReadCompressor; + void *mpWriteCompressor; + void *mpBuffer; + bool mIsClosed; +}; + +#endif // COMPRESSSTREAM__H + diff --git a/lib/compress/Makefile.extra b/lib/compress/Makefile.extra new file mode 100644 index 00000000..a29fcdb4 --- /dev/null +++ b/lib/compress/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CompressException.h autogen_CompressException.cpp: $(MAKEEXCEPTION) CompressException.txt + $(_PERL) $(MAKEEXCEPTION) CompressException.txt + diff --git a/lib/crypto/CipherAES.cpp b/lib/crypto/CipherAES.cpp new file mode 100644 index 00000000..fad8b968 --- /dev/null +++ b/lib/crypto/CipherAES.cpp @@ -0,0 +1,163 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherAES.cpp +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +// Only available in new versions of openssl +#ifndef HAVE_OLD_SSL + +#include <openssl/evp.h> + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherAES.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::CipherAES(CipherDescription::CipherMode, const void *, unsigned int, const void *) +// Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) + : CipherDescription(), + mMode(Mode), + mpKey(pKey), + mKeyLength(KeyLength), + mpInitialisationVector(pInitialisationVector) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::CipherAES(const CipherAES &) +// Purpose: Copy constructor +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::CipherAES(const CipherAES &rToCopy) + : CipherDescription(rToCopy), + mMode(rToCopy.mMode), + mpKey(rToCopy.mpKey), + mKeyLength(rToCopy.mKeyLength), + mpInitialisationVector(rToCopy.mpInitialisationVector) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherAES::CipherAES() +// Purpose: Destructor +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES::~CipherAES() +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::operator=(const CipherAES &) +// Purpose: Assignment operator +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +CipherAES &CipherAES::operator=(const CipherAES &rToCopy) +{ + CipherDescription::operator=(rToCopy); + + mMode = rToCopy.mMode; + mpKey = rToCopy.mpKey; + mKeyLength = rToCopy.mKeyLength; + mpInitialisationVector = rToCopy.mpInitialisationVector; + + return *this; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::GetCipher() +// Purpose: Returns cipher object +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +const EVP_CIPHER *CipherAES::GetCipher() const +{ + switch(mMode) + { + case CipherDescription::Mode_ECB: + switch(mKeyLength) + { + case (128/8): return EVP_aes_128_ecb(); break; + case (192/8): return EVP_aes_192_ecb(); break; + case (256/8): return EVP_aes_256_ecb(); break; + default: + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + break; + } + break; + + case CipherDescription::Mode_CBC: + switch(mKeyLength) + { + case (128/8): return EVP_aes_128_cbc(); break; + case (192/8): return EVP_aes_192_cbc(); break; + case (256/8): return EVP_aes_256_cbc(); break; + default: + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + break; + } + break; + + default: + break; + } + + // Unknown! + THROW_EXCEPTION(CipherException, UnknownCipherMode) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherAES::SetupParameters(EVP_CIPHER_CTX *) +// Purpose: Set up various parameters for cipher +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +void CipherAES::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const +{ + ASSERT(pCipherContext != 0); + + // Set key (key length is implied) + if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +} + + + +#endif // n HAVE_OLD_SSL + diff --git a/lib/crypto/CipherAES.h b/lib/crypto/CipherAES.h new file mode 100644 index 00000000..d2c9ed65 --- /dev/null +++ b/lib/crypto/CipherAES.h @@ -0,0 +1,59 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherAES.h +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERAES__H +#define CIPHERAES__H + +// Only available in new versions of openssl +#ifndef HAVE_OLD_SSL + +#include "CipherDescription.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherAES +// Purpose: AES cipher description +// Created: 27/4/04 +// +// -------------------------------------------------------------------------- +class CipherAES : public CipherDescription +{ +public: + CipherAES(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); + CipherAES(const CipherAES &rToCopy); + virtual ~CipherAES(); + CipherAES &operator=(const CipherAES &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; + + virtual std::string GetCipherName() const + { + std::ostringstream out; + out << "AES"; + out << mKeyLength; + return out.str(); + } + virtual CipherMode GetCipherMode() const { return mMode; } + +private: + CipherDescription::CipherMode mMode; + const void *mpKey; + unsigned int mKeyLength; + const void *mpInitialisationVector; +}; + +#endif // n HAVE_OLD_SSL + +#endif // CIPHERAES__H + diff --git a/lib/crypto/CipherBlowfish.cpp b/lib/crypto/CipherBlowfish.cpp new file mode 100644 index 00000000..e16cc6ed --- /dev/null +++ b/lib/crypto/CipherBlowfish.cpp @@ -0,0 +1,220 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherBlowfish.cpp +// Purpose: Blowfish cipher description +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/evp.h> + +#ifdef HAVE_OLD_SSL + #include <string.h> + #include <strings.h> +#endif + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherBlowfish.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode, const void *, unsigned int, const void *) +// Purpose: Constructor -- note key material and IV are not copied. KeyLength in bytes. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector) + : CipherDescription(), + mMode(Mode) +#ifndef HAVE_OLD_SSL + , mpKey(pKey), + mKeyLength(KeyLength), + mpInitialisationVector(pInitialisationVector) +{ +} +#else +{ + mKey.assign((const char *)pKey, KeyLength); + if(pInitialisationVector == 0) + { + bzero(mInitialisationVector, sizeof(mInitialisationVector)); + } + else + { + ::memcpy(mInitialisationVector, pInitialisationVector, sizeof(mInitialisationVector)); + } +} +#endif + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::CipherBlowfish(const CipherBlowfish &) +// Purpose: Copy constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::CipherBlowfish(const CipherBlowfish &rToCopy) + : CipherDescription(rToCopy), + mMode(rToCopy.mMode), +#ifndef HAVE_OLD_SSL + mpKey(rToCopy.mpKey), + mKeyLength(rToCopy.mKeyLength), + mpInitialisationVector(rToCopy.mpInitialisationVector) +{ +} +#else + mKey(rToCopy.mKey) +{ + ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); +} +#endif + + +#ifdef HAVE_OLD_SSL +// Hack functions to support old OpenSSL API +CipherDescription *CipherBlowfish::Clone() const +{ + return new CipherBlowfish(*this); +} +void CipherBlowfish::SetIV(const void *pIV) +{ + if(pIV == 0) + { + bzero(mInitialisationVector, sizeof(mInitialisationVector)); + } + else + { + ::memcpy(mInitialisationVector, pIV, sizeof(mInitialisationVector)); + } +} +#endif + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherBlowfish::CipherBlowfish() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish::~CipherBlowfish() +{ +#ifdef HAVE_OLD_SSL + // Zero copy of key + for(unsigned int l = 0; l < mKey.size(); ++l) + { + mKey[l] = '\0'; + } +#endif +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::operator=(const CipherBlowfish &) +// Purpose: Assignment operator +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherBlowfish &CipherBlowfish::operator=(const CipherBlowfish &rToCopy) +{ + CipherDescription::operator=(rToCopy); + + mMode = rToCopy.mMode; +#ifndef HAVE_OLD_SSL + mpKey = rToCopy.mpKey; + mKeyLength = rToCopy.mKeyLength; + mpInitialisationVector = rToCopy.mpInitialisationVector; +#else + mKey = rToCopy.mKey; + ::memcpy(mInitialisationVector, rToCopy.mInitialisationVector, sizeof(mInitialisationVector)); +#endif + + return *this; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::GetCipher() +// Purpose: Returns cipher object +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +const EVP_CIPHER *CipherBlowfish::GetCipher() const +{ + switch(mMode) + { + case CipherDescription::Mode_ECB: + return EVP_bf_ecb(); + break; + + case CipherDescription::Mode_CBC: + return EVP_bf_cbc(); + break; + + case CipherDescription::Mode_CFB: + return EVP_bf_cfb(); + break; + + case CipherDescription::Mode_OFB: + return EVP_bf_ofb(); + break; + + default: + break; + } + + // Unknown! + THROW_EXCEPTION(CipherException, UnknownCipherMode) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *) +// Purpose: Set up various parameters for cipher +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherBlowfish::SetupParameters(EVP_CIPHER_CTX *pCipherContext) const +{ + ASSERT(pCipherContext != 0); + + // Set key length +#ifndef HAVE_OLD_SSL + if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKeyLength) != 1) +#else + if(EVP_CIPHER_CTX_set_key_length(pCipherContext, mKey.size()) != 1) +#endif + { + THROW_EXCEPTION(CipherException, EVPBadKeyLength) + } + // Set key +#ifndef HAVE_OLD_SSL + if(EVP_CipherInit_ex(pCipherContext, NULL, NULL, (unsigned char*)mpKey, (unsigned char*)mpInitialisationVector, -1) != 1) +#else + if(EVP_CipherInit(pCipherContext, NULL, (unsigned char*)mKey.c_str(), (unsigned char*)mInitialisationVector, -1) != 1) +#endif + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + +} + + + diff --git a/lib/crypto/CipherBlowfish.h b/lib/crypto/CipherBlowfish.h new file mode 100644 index 00000000..152a265c --- /dev/null +++ b/lib/crypto/CipherBlowfish.h @@ -0,0 +1,69 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherBlowfish.h +// Purpose: Blowfish cipher description +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERBLOWFISH__H +#define CIPHERBLOWFISH__H + +#ifdef HAVE_OLD_SSL + #include <string> +#endif + +#include "CipherDescription.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherBlowfish +// Purpose: Description of Blowfish cipher parameters -- note that copies are not made of key material and IV, careful with object lifetimes. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherBlowfish : public CipherDescription +{ +public: + CipherBlowfish(CipherDescription::CipherMode Mode, const void *pKey, unsigned int KeyLength, const void *pInitialisationVector = 0); + CipherBlowfish(const CipherBlowfish &rToCopy); + virtual ~CipherBlowfish(); + CipherBlowfish &operator=(const CipherBlowfish &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const; + + virtual std::string GetCipherName() const + { + std::ostringstream out; + out << "AES"; + out << mKeyLength; + return out.str(); + } + virtual CipherMode GetCipherMode() const { return mMode; } + +#ifdef HAVE_OLD_SSL + CipherDescription *Clone() const; + void SetIV(const void *pIV); +#endif + +private: + CipherDescription::CipherMode mMode; +#ifndef HAVE_OLD_SSL + const void *mpKey; + unsigned int mKeyLength; + const void *mpInitialisationVector; +#else + std::string mKey; + uint8_t mInitialisationVector[8]; +#endif +}; + + +#endif // CIPHERBLOWFISH__H + diff --git a/lib/crypto/CipherContext.cpp b/lib/crypto/CipherContext.cpp new file mode 100644 index 00000000..fd149395 --- /dev/null +++ b/lib/crypto/CipherContext.cpp @@ -0,0 +1,640 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherContext.cpp +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE +#include "CipherContext.h" +#include "CipherDescription.h" +#include "CipherException.h" +#include "CryptoUtils.h" +#include "Random.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::CipherContext() +// Purpose: Constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherContext::CipherContext() +: mInitialised(false), + mWithinTransform(false), + mPaddingOn(true), + mFunction(None) +#ifdef HAVE_OLD_SSL +, mpDescription(0) +#endif +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::~CipherContext() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherContext::~CipherContext() +{ + if(mInitialised) + { + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + mInitialised = false; + } +#ifdef HAVE_OLD_SSL + if(mpDescription != 0) + { + delete mpDescription; + mpDescription = 0; + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// 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 +// 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 +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription) +{ + // Check for bad usage + if(mInitialised) + { + THROW_EXCEPTION(CipherException, AlreadyInitialised) + } + if(Function != Decrypt && Function != Encrypt) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Store function for later + mFunction = Function; + + // Initialise the cipher +#ifndef HAVE_OLD_SSL + EVP_CIPHER_CTX_init(&ctx); // no error return code, even though the docs says it does + + 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")); + } + + try + { + mCipherName = rDescription.GetFullName(); +#ifndef HAVE_OLD_SSL + // Let the description set up everything else + rDescription.SetupParameters(&ctx); +#else + // With the old version, a copy needs to be taken first. + mpDescription = rDescription.Clone(); + // Mark it as not a leak, otherwise static cipher contexts + // cause spurious memory leaks to be reported + MEMLEAKFINDER_NOT_A_LEAK(mpDescription); + mpDescription->SetupParameters(&ctx); +#endif + } + catch(...) + { + THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure, + "Failed to configure " << mCipherName << " cipher: " << + LogError("configuring cipher")); + EVP_CIPHER_CTX_cleanup(&ctx); + throw; + } + + // mark as initialised + mInitialised = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Reset() +// Purpose: Reset the context, so it can be initialised again with a different key +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Reset() +{ + if(mInitialised) + { + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + mInitialised = false; + } +#ifdef HAVE_OLD_SSL + if(mpDescription != 0) + { + delete mpDescription; + mpDescription = 0; + } +#endif + mWithinTransform = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Begin() +// Purpose: Begin a transformation +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::Begin() +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation (not an error, because a context might not have been finalised if an exception occured) + if(mWithinTransform) + { + BOX_WARNING("CipherContext::Begin called when context " + "flagged as within a transform"); + } + + // Initialise the cipher context again + if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) + { + THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure, + "Failed to reset " << mCipherName << " cipher: " << + LogError("resetting cipher")); + } + + // Mark as being within a transform + mWithinTransform = true; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Transform(void *, int, const void *, int) +// Purpose: Transforms the data in the in buffer to the out buffer. If pInBuffer == 0 && InLength == 0 +// then Final() is called instead. +// Returns the number of bytes placed in the out buffer. +// There must be room in the out buffer for all the data in the in buffer. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + if(!mWithinTransform) + { + THROW_EXCEPTION(CipherException, BeginNotCalled) + } + + // Check parameters + if(pOutBuffer == 0 || OutLength < 0 || (pInBuffer != 0 && InLength <= 0) || (pInBuffer == 0 && InLength != 0)) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Is this the final call? + if(pInBuffer == 0) + { + return Final(pOutBuffer, OutLength); + } + + // Check output buffer size + if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + + // Do the transform + int 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())); + } + + return outLength; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::Final(void *, int) +// Purpose: Transforms the data as per Transform, and returns the final data in the out buffer. +// Returns the number of bytes written in the out buffer. +// Two main causes of exceptions being thrown: 1) Data is corrupt, and so the end isn't +// padded properly. 2) Padding is off, and the data to be encrypted isn't a multiple +// of a block long. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::Final(void *pOutBuffer, int OutLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + if(!mWithinTransform) + { + THROW_EXCEPTION(CipherException, BeginNotCalled) + } + + // Check parameters + if(pOutBuffer == 0 || OutLength < 0) + { + THROW_EXCEPTION(CipherException, BadArguments) + } + + // Check output buffer size + if(OutLength < (2 * EVP_CIPHER_CTX_block_size(&ctx))) + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + + // Do the transform + int outLength = OutLength; +#ifndef HAVE_OLD_SSL + if(EVP_CipherFinal(&ctx, (unsigned char*)pOutBuffer, &outLength) != 1) + { + mWithinTransform = false; + THROW_EXCEPTION_MESSAGE(CipherException, EVPFinalFailure, + "Failed to " << GetFunction() << " (final) " << + mCipherName << " cipher: " << LogError(GetFunction())); + } +#else + OldOpenSSLFinal((unsigned char*)pOutBuffer, outLength); +#endif + + mWithinTransform = false; + + return outLength; +} + + +#ifdef HAVE_OLD_SSL +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::OldOpenSSLFinal(unsigned char *, int &) +// Purpose: The old version of OpenSSL needs more work doing to finalise the cipher, +// and reset it so that it's ready for another go. +// Created: 27/3/04 +// +// -------------------------------------------------------------------------- +void CipherContext::OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut) +{ + // Old version needs to use a different form, and then set up the cipher again for next time around + int outLength = rOutLengthOut; + // Have to emulate padding off... + int blockSize = EVP_CIPHER_CTX_block_size(&ctx); + if(mPaddingOn) + { + // Just use normal final call + if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + } + else + { + // Padding is off. OpenSSL < 0.9.7 doesn't support this, so it has to be + // bodged in there. Which isn't nice. + if(mFunction == Decrypt) + { + // NASTY -- fiddling around with internals like this is bad. + // But only way to get this working on old versions of OpenSSL. + if(!EVP_EncryptUpdate(&ctx,Buffer,&outLength,ctx.buf,0) + || outLength != blockSize) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // Clean up + EVP_CIPHER_CTX_cleanup(&ctx); + } + else + { + // Check that the length is correct + if((ctx.buf_len % blockSize) != 0) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // For encryption, assume that the last block entirely is + // padding, and remove it. + char temp[1024]; + outLength = sizeof(temp); + if(EVP_CipherFinal(&ctx, Buffer, &outLength) != 1) + { + THROW_EXCEPTION(CipherException, EVPFinalFailure) + } + // Remove last block, assuming it's full of padded bytes only. + outLength -= blockSize; + // Copy anything to the main buffer + // (can't just use main buffer, because it might overwrite something important) + if(outLength > 0) + { + ::memcpy(Buffer, temp, outLength); + } + } + } + // Reinitialise the cipher for the next time around + if(EVP_CipherInit(&ctx, mpDescription->GetCipher(), NULL, NULL, + (mFunction == Encrypt) ? 1 : 0) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + mpDescription->SetupParameters(&ctx); + + // Update length for caller + rOutLengthOut = outLength; +} +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::InSizeForOutBufferSize(int) +// Purpose: Returns the maximum amount of data that can be sent in +// given a output buffer size. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::InSizeForOutBufferSize(int OutLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Strictly speaking, the *2 is unnecessary. However... + // Final() is paranoid, and requires two input blocks of space to work. + return OutLength - (EVP_CIPHER_CTX_block_size(&ctx) * 2); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::MaxOutSizeForInBufferSize(int) +// Purpose: Returns the maximum output size for an input of a given length. +// Will tend to over estimate, as it needs to allow space for Final() to be called. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::MaxOutSizeForInBufferSize(int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Final() is paranoid, and requires two input blocks of space to work, and so we need to add + // three blocks on to be absolutely sure. + return InLength + (EVP_CIPHER_CTX_block_size(&ctx) * 3); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::TransformBlock(void *, int, const void *, int) +// Purpose: Transform one block to another all in one go, no Final required. +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::TransformBlock called when " + "context flagged as within a transform"); + } + + // Check output buffer size + if(OutLength < (InLength + EVP_CIPHER_CTX_block_size(&ctx))) + { + // Check if padding is off, in which case the buffer can be smaller + if(!mPaddingOn && OutLength <= InLength) + { + // This is OK. + } + else + { + THROW_EXCEPTION(CipherException, OutputBufferTooSmall); + } + } + + // Initialise the cipher context again + if(EVP_CipherInit(&ctx, NULL, NULL, NULL, -1) != 1) + { + THROW_EXCEPTION(CipherException, EVPInitFailure) + } + + // Do the entire block + int outLength = 0; + + // 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; + + return outLength; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::GetIVLength() +// Purpose: Returns the size of the IV for this context +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +int CipherContext::GetIVLength() +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + return EVP_CIPHER_CTX_iv_length(&ctx); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::SetIV(const void *) +// Purpose: Sets the IV for this context (must be correctly sized, use GetIVLength) +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::SetIV(const void *pIV) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::SetIV called when context " + "flagged as within a transform"); + } + + // Set IV + if(EVP_CipherInit(&ctx, NULL, NULL, (unsigned char *)pIV, -1) != 1) + { + THROW_EXCEPTION_MESSAGE(CipherException, EVPInitFailure, + "Failed to " << GetFunction() << " (set IV) " << + mCipherName << " cipher: " << LogError(GetFunction())); + } + +#ifdef HAVE_OLD_SSL + // Update description + if(mpDescription != 0) + { + mpDescription->SetIV(pIV); + } +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::SetRandomIV(int &) +// Purpose: Set a random IV for the context, and return a pointer to the IV used, +// and the length of this IV in the rLengthOut arg. +// Created: 3/12/03 +// +// -------------------------------------------------------------------------- +const void *CipherContext::SetRandomIV(int &rLengthOut) +{ + if(!mInitialised) + { + THROW_EXCEPTION(CipherException, NotInitialised) + } + + // Warn if in a transformation + if(mWithinTransform) + { + BOX_WARNING("CipherContext::SetRandomIV called when " + "context flagged as within a transform"); + } + + // Get length of IV + unsigned int ivLen = EVP_CIPHER_CTX_iv_length(&ctx); + if(ivLen > sizeof(mGeneratedIV)) + { + THROW_EXCEPTION(CipherException, IVSizeImplementationLimitExceeded) + } + + // Generate some random data + Random::Generate(mGeneratedIV, ivLen); + SetIV(mGeneratedIV); + + // Return the IV and it's length + rLengthOut = ivLen; + return mGeneratedIV; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherContext::UsePadding(bool) +// Purpose: Set whether or not the context uses padding. +// Created: 12/12/03 +// +// -------------------------------------------------------------------------- +void CipherContext::UsePadding(bool Padding) +{ +#ifndef HAVE_OLD_SSL + if(EVP_CIPHER_CTX_set_padding(&ctx, Padding) != 1) + { + THROW_EXCEPTION(CipherException, EVPSetPaddingFailure) + } +#endif + mPaddingOn = Padding; +} + + + diff --git a/lib/crypto/CipherContext.h b/lib/crypto/CipherContext.h new file mode 100644 index 00000000..93c889d6 --- /dev/null +++ b/lib/crypto/CipherContext.h @@ -0,0 +1,91 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherContext.h +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERCONTEXT__H +#define CIPHERCONTEXT__H + +#ifdef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE + always include CipherContext.h first in any .cpp file +#endif +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE +#include <openssl/evp.h> +class CipherDescription; + +#define CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH 32 + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherContext +// Purpose: Context for symmetric encryption / descryption +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherContext +{ +public: + CipherContext(); + ~CipherContext(); +private: + CipherContext(const CipherContext &); // no copying + CipherContext &operator=(const CipherContext &); // no assignment +protected: + std::string LogError(const std::string& operation); +public: + + typedef enum + { + None = 0, + Decrypt, + Encrypt + } CipherFunction; + + void Init(CipherContext::CipherFunction Function, const CipherDescription &rDescription); + void Reset(); + + void Begin(); + int Transform(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); + int Final(void *pOutBuffer, int OutLength); + int InSizeForOutBufferSize(int OutLength); + int MaxOutSizeForInBufferSize(int InLength); + + int TransformBlock(void *pOutBuffer, int OutLength, const void *pInBuffer, int InLength); + + bool IsInitialised() {return mInitialised;} + + int GetIVLength(); + void SetIV(const void *pIV); + const void *SetRandomIV(int &rLengthOut); + + void UsePadding(bool Padding = true); + const char* GetFunction() const + { + return (mFunction == Encrypt) ? "encrypt" : "decrypt"; + } + +#ifdef HAVE_OLD_SSL + void OldOpenSSLFinal(unsigned char *Buffer, int &rOutLengthOut); +#endif + +private: + EVP_CIPHER_CTX ctx; + bool mInitialised; + bool mWithinTransform; + bool mPaddingOn; + uint8_t mGeneratedIV[CIPHERCONTEXT_MAX_GENERATED_IV_LENGTH]; + CipherFunction mFunction; + std::string mCipherName; +#ifdef HAVE_OLD_SSL + CipherDescription *mpDescription; +#endif +}; + + +#endif // CIPHERCONTEXT__H + diff --git a/lib/crypto/CipherDescription.cpp b/lib/crypto/CipherDescription.cpp new file mode 100644 index 00000000..f0ba6ec8 --- /dev/null +++ b/lib/crypto/CipherDescription.cpp @@ -0,0 +1,73 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherDescription.cpp +// Purpose: Pure virtual base class for describing ciphers +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/evp.h> + +#define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + +#include "CipherDescription.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::CipherDescription() +// Purpose: Constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::CipherDescription() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::CipherDescription(const CipherDescription &) +// Purpose: Copy constructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::CipherDescription(const CipherDescription &rToCopy) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: ~CipherDescription::CipherDescription() +// Purpose: Destructor +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription::~CipherDescription() +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: CipherDescription::operator=(const CipherDescription &) +// Purpose: Assignment operator +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +CipherDescription &CipherDescription::operator=(const CipherDescription &rToCopy) +{ + return *this; +} + + diff --git a/lib/crypto/CipherDescription.h b/lib/crypto/CipherDescription.h new file mode 100644 index 00000000..813df2ce --- /dev/null +++ b/lib/crypto/CipherDescription.h @@ -0,0 +1,76 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherDescription.h +// Purpose: Pure virtual base class for describing ciphers +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHERDESCRIPTION__H +#define CIPHERDESCRIPTION__H + +#ifndef BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_TRUE + #define BOX_LIB_CRYPTO_OPENSSL_HEADERS_INCLUDED_FALSE + class EVP_CIPHER; + class EVP_CIPHER_CTX; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: CipherDescription +// Purpose: Describes a cipher +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- +class CipherDescription +{ +public: + CipherDescription(); + CipherDescription(const CipherDescription &rToCopy); + virtual ~CipherDescription(); + CipherDescription &operator=(const CipherDescription &rToCopy); + + // Return OpenSSL cipher object + virtual const EVP_CIPHER *GetCipher() const = 0; + + // Setup any other parameters + virtual void SetupParameters(EVP_CIPHER_CTX *pCipherContext) const = 0; + + // Mode parameter for cipher -- used in derived classes + typedef enum + { + Mode_ECB = 0, + Mode_CBC = 1, + Mode_CFB = 2, + Mode_OFB = 3 + } CipherMode; + + 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(); + } + +#ifdef HAVE_OLD_SSL + // For the old version of OpenSSL, we need to be able to store cipher descriptions. + virtual CipherDescription *Clone() const = 0; + // And to be able to store new IVs + virtual void SetIV(const void *pIV) = 0; +#endif +}; + +#endif // CIPHERDESCRIPTION__H + diff --git a/lib/crypto/CipherException.h b/lib/crypto/CipherException.h new file mode 100644 index 00000000..b0c9f8cd --- /dev/null +++ b/lib/crypto/CipherException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: CipherException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef CIPHEREXCEPTION__H +#define CIPHEREXCEPTION__H + +// Compatibility +#include "autogen_CipherException.h" + +#endif // CIPHEREXCEPTION__H + diff --git a/lib/crypto/CipherException.txt b/lib/crypto/CipherException.txt new file mode 100644 index 00000000..abdbac87 --- /dev/null +++ b/lib/crypto/CipherException.txt @@ -0,0 +1,18 @@ +EXCEPTION Cipher 5 + +Internal 0 +UnknownCipherMode 1 +AlreadyInitialised 2 +BadArguments 3 +EVPInitFailure 4 +EVPUpdateFailure 5 +EVPFinalFailure 6 +NotInitialised 7 +OutputBufferTooSmall 8 +EVPBadKeyLength 9 +BeginNotCalled 10 +IVSizeImplementationLimitExceeded 11 +PseudoRandNotAvailable 12 +EVPSetPaddingFailure 13 +RandomInitFailed 14 Failed to read from random device +LengthRequestedTooLongForRandomHex 15 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/crypto/MD5Digest.cpp b/lib/crypto/MD5Digest.cpp new file mode 100644 index 00000000..58cc90ee --- /dev/null +++ b/lib/crypto/MD5Digest.cpp @@ -0,0 +1,82 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MD5Digest.cpp +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include "MD5Digest.h" + +#include "MemLeakFindOn.h" + + +MD5Digest::MD5Digest() +{ + MD5_Init(&md5); + for(unsigned int l = 0; l < sizeof(mDigest); ++l) + { + mDigest[l] = 0; + } +} + +MD5Digest::~MD5Digest() +{ +} + +void MD5Digest::Add(const std::string &rString) +{ + MD5_Update(&md5, rString.c_str(), rString.size()); +} + +void MD5Digest::Add(const void *pData, int Length) +{ + MD5_Update(&md5, pData, Length); +} + +void MD5Digest::Finish() +{ + MD5_Final(mDigest, &md5); +} + +std::string MD5Digest::DigestAsString() +{ + std::string r; + + static const char *hex = "0123456789abcdef"; + + for(unsigned int l = 0; l < sizeof(mDigest); ++l) + { + r += hex[(mDigest[l] & 0xf0) >> 4]; + r += hex[(mDigest[l] & 0x0f)]; + } + + return r; +} + +int MD5Digest::CopyDigestTo(uint8_t *to) +{ + for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) + { + to[l] = mDigest[l]; + } + + return MD5_DIGEST_LENGTH; +} + + +bool MD5Digest::DigestMatches(uint8_t *pCompareWith) const +{ + for(int l = 0; l < MD5_DIGEST_LENGTH; ++l) + { + if(pCompareWith[l] != mDigest[l]) + return false; + } + + return true; +} + diff --git a/lib/crypto/MD5Digest.h b/lib/crypto/MD5Digest.h new file mode 100644 index 00000000..1be01ea9 --- /dev/null +++ b/lib/crypto/MD5Digest.h @@ -0,0 +1,57 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: MD5Digest.h +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef MD5DIGEST_H +#define MD5DIGEST_H + +#include <openssl/md5.h> +#include <string> + +// -------------------------------------------------------------------------- +// +// Function +// Name: MD5Digest +// Purpose: Simple interface for creating MD5 digests +// Created: 8/12/03 +// +// -------------------------------------------------------------------------- +class MD5Digest +{ +public: + MD5Digest(); + virtual ~MD5Digest(); + + void Add(const std::string &rString); + void Add(const void *pData, int Length); + + void Finish(); + + std::string DigestAsString(); + uint8_t *DigestAsData(int *pLength = 0) + { + if(pLength) *pLength = sizeof(mDigest); + return mDigest; + } + + enum + { + DigestLength = MD5_DIGEST_LENGTH + }; + + int CopyDigestTo(uint8_t *to); + + bool DigestMatches(uint8_t *pCompareWith) const; + +private: + MD5_CTX md5; + uint8_t mDigest[MD5_DIGEST_LENGTH]; +}; + +#endif // MD5DIGEST_H + diff --git a/lib/crypto/Makefile.extra b/lib/crypto/Makefile.extra new file mode 100644 index 00000000..3c94389f --- /dev/null +++ b/lib/crypto/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_CipherException.cpp autogen_CipherException.h: $(MAKEEXCEPTION) CipherException.txt + $(_PERL) $(MAKEEXCEPTION) CipherException.txt + diff --git a/lib/crypto/Random.cpp b/lib/crypto/Random.cpp new file mode 100644 index 00000000..1d6a07f0 --- /dev/null +++ b/lib/crypto/Random.cpp @@ -0,0 +1,128 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Random.cpp +// Purpose: Random numbers +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <openssl/rand.h> +#include <stdio.h> + +#include "Random.h" +#include "CipherException.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::Initialise() +// Purpose: Add additional randomness to the standard library initialisation +// Created: 18/6/04 +// +// -------------------------------------------------------------------------- +void Random::Initialise() +{ +#ifdef HAVE_RANDOM_DEVICE + if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) + { + THROW_EXCEPTION(CipherException, RandomInitFailed) + } +#else + BOX_ERROR("No random device -- additional seeding of random number " + "generator not performed."); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::Generate(void *, int) +// Purpose: Generate Length bytes of random data +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- +void Random::Generate(void *pOutput, int Length) +{ + if(RAND_pseudo_bytes((uint8_t*)pOutput, Length) == -1) + { + THROW_EXCEPTION(CipherException, PseudoRandNotAvailable) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::GenerateHex(int) +// Purpose: Generate Length bytes of hex encoded data. Note that the +// maximum length requested is limited. (Returns a string +// 2 x Length characters long.) +// Created: 1/11/04 +// +// -------------------------------------------------------------------------- +std::string Random::GenerateHex(int Length) +{ + uint8_t r[256]; + if(Length > (int)sizeof(r)) + { + THROW_EXCEPTION(CipherException, LengthRequestedTooLongForRandomHex) + } + Random::Generate(r, Length); + + std::string o; + static const char *h = "0123456789abcdef"; + for(int l = 0; l < Length; ++l) + { + o += h[r[l] >> 4]; + o += h[r[l] & 0xf]; + } + + return o; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Random::RandomInt(int) +// Purpose: Return a random integer between 0 and MaxValue inclusive. +// Created: 21/1/04 +// +// -------------------------------------------------------------------------- +uint32_t Random::RandomInt(uint32_t MaxValue) +{ + uint32_t v = 0; + + // Generate a mask + uint32_t mask = 0; + while(mask < MaxValue) + { + mask = (mask << 1) | 1; + } + + do + { + // Generate a random number + uint32_t r = 0; + Random::Generate(&r, sizeof(r)); + + // Mask off relevant bits + v = r & mask; + + // Check that it's in the right range. + } while(v > MaxValue); + + // NOTE: don't do a mod, because this doesn't give a correct random distribution + + return v; +} + + + diff --git a/lib/crypto/Random.h b/lib/crypto/Random.h new file mode 100644 index 00000000..da3e4335 --- /dev/null +++ b/lib/crypto/Random.h @@ -0,0 +1,25 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Random.h +// Purpose: Random numbers +// Created: 31/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef RANDOM__H +#define RANDOM__H + +#include <string> + +namespace Random +{ + void Initialise(); + void Generate(void *pOutput, int Length); + std::string GenerateHex(int Length); + uint32_t RandomInt(uint32_t MaxValue); +}; + + +#endif // RANDOM__H + diff --git a/lib/crypto/RollingChecksum.cpp b/lib/crypto/RollingChecksum.cpp new file mode 100644 index 00000000..a2954e3a --- /dev/null +++ b/lib/crypto/RollingChecksum.cpp @@ -0,0 +1,62 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RollingChecksum.cpp +// Purpose: A simple rolling checksum over a block of data +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "RollingChecksum.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: RollingChecksum::RollingChecksum(const void *, unsigned int) +// Purpose: Constructor -- does initial computation of the checksum. +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- +RollingChecksum::RollingChecksum(const void * const data, const unsigned int Length) + : a(0), + b(0) +{ + const uint8_t *block = (const uint8_t *)data; + for(unsigned int x = Length; x >= 1; --x) + { + a += (*block); + b += x * (*block); + + ++block; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) +// Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, +// and a pointer just after the last byte of the current block and the length of the block and of the skip. +// Created: 7/14/05 +// +// -------------------------------------------------------------------------- +void RollingChecksum::RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip) +{ + // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. + unsigned int i; + uint16_t sumBegin=0, j,k; + + for(i=0; i < Skip; i++) + { + j = StartOfThisBlock[i]; + k = LastOfNextBlock[i]; + sumBegin += j; + a += (k - j); + b += a; + } + + b -= Length * sumBegin; +} diff --git a/lib/crypto/RollingChecksum.h b/lib/crypto/RollingChecksum.h new file mode 100644 index 00000000..be79c36f --- /dev/null +++ b/lib/crypto/RollingChecksum.h @@ -0,0 +1,107 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RollingChecksum.h +// Purpose: A simple rolling checksum over a block of data +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- + +#ifndef ROLLINGCHECKSUM__H +#define ROLLINGCHECKSUM__H + +// -------------------------------------------------------------------------- +// +// Class +// Name: RollingChecksum +// Purpose: A simple rolling checksum over a block of data -- can move the block +// "forwards" in memory and get the next checksum efficiently. +// +// Implementation of http://rsync.samba.org/tech_report/node3.html +// Created: 6/12/03 +// +// -------------------------------------------------------------------------- +class RollingChecksum +{ +public: + RollingChecksum(const void * const data, const unsigned int Length); + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::RollForward(uint8_t, uint8_t, unsigned int) + // Purpose: Move the checksum forward a block, given the first byte of the current block, + // last byte of the next block (it's rolling forward to) and the length of the block. + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline void RollForward(const uint8_t StartOfThisBlock, const uint8_t LastOfNextBlock, const unsigned int Length) + { + // IMPLEMENTATION NOTE: Everything is implicitly mod 2^16 -- uint16_t's will overflow nicely. + a -= StartOfThisBlock; + a += LastOfNextBlock; + b -= Length * StartOfThisBlock; + b += a; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::RollForwardSeveral(uint8_t*, uint8_t*, unsigned int, unsigned int) + // Purpose: Move the checksum forward a block, given a pointer to the first byte of the current block, + // and a pointer just after the last byte of the current block and the length of the block and of the skip. + // Created: 7/14/05 + // + // -------------------------------------------------------------------------- + void RollForwardSeveral(const uint8_t * const StartOfThisBlock, const uint8_t * const LastOfNextBlock, const unsigned int Length, const unsigned int Skip); + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::GetChecksum() + // Purpose: Returns the checksum + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline uint32_t GetChecksum() const + { + return ((uint32_t)a) | (((uint32_t)b) << 16); + } + + // Components, just in case they're handy + inline uint16_t GetComponent1() const {return a;} + inline uint16_t GetComponent2() const {return b;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::GetComponentForHashing() + // Purpose: Return the 16 bit component used for hashing and/or quick checks + // Created: 6/12/03 + // + // -------------------------------------------------------------------------- + inline uint16_t GetComponentForHashing() const + { + return b; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: RollingChecksum::ExtractHashingComponent(uint32_t) + // Purpose: Static. Given a full checksum, extract the component used in the hashing table. + // Created: 14/1/04 + // + // -------------------------------------------------------------------------- + static inline uint16_t ExtractHashingComponent(const uint32_t Checksum) + { + return Checksum >> 16; + } + +private: + uint16_t a; + uint16_t b; +}; + +#endif // ROLLINGCHECKSUM__H + diff --git a/lib/httpserver/HTTPException.txt b/lib/httpserver/HTTPException.txt new file mode 100644 index 00000000..c9b3f940 --- /dev/null +++ b/lib/httpserver/HTTPException.txt @@ -0,0 +1,17 @@ +EXCEPTION HTTP 10 + +Internal 0 +RequestReadFailed 1 +RequestAlreadyBeenRead 2 +BadRequest 3 +UnknownResponseCodeUsed 4 +NoContentTypeSet 5 +POSTContentTooLong 6 +CannotSetRedirectIfReponseHasData 7 +CannotSetNotFoundIfReponseHasData 8 +NotImplemented 9 +RequestNotInitialised 10 +BadResponse 11 +ResponseReadFailed 12 +NoStreamConfigured 13 +RequestFailedUnexpectedly 14 The request was expected to succeed, but it failed. diff --git a/lib/httpserver/HTTPQueryDecoder.cpp b/lib/httpserver/HTTPQueryDecoder.cpp new file mode 100644 index 00000000..c49ac2ce --- /dev/null +++ b/lib/httpserver/HTTPQueryDecoder.cpp @@ -0,0 +1,159 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPQueryDecoder.cpp +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdlib.h> + +#include "HTTPQueryDecoder.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::HTTPQueryDecoder( +// HTTPRequest::Query_t &) +// Purpose: Constructor. Pass in the query contents you want +// to decode the query string into. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPQueryDecoder::HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto) + : mrDecodeInto(rDecodeInto), + mInKey(true), + mEscapedState(0) +{ + // Insert the terminator for escaped characters + mEscaped[2] = '\0'; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::~HTTPQueryDecoder() +// Purpose: Destructor. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPQueryDecoder::~HTTPQueryDecoder() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::Decode(const char *, int) +// Purpose: Decode a chunk of query string -- call several times with +// the bits as they are received, and then call Finish() +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPQueryDecoder::DecodeChunk(const char *pQueryString, int QueryStringSize) +{ + for(int l = 0; l < QueryStringSize; ++l) + { + char c = pQueryString[l]; + + // BEFORE unescaping, check to see if we need to flip key / value + if(mEscapedState == 0) + { + if(mInKey && c == '=') + { + // Set to store characters in the value + mInKey = false; + continue; + } + else if(!mInKey && c == '&') + { + // Need to store the current key/value pair + mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); + // Blank the strings + mCurrentKey.erase(); + mCurrentValue.erase(); + + // Set to store characters in the key + mInKey = true; + continue; + } + } + + // Decode an escaped value? + if(mEscapedState == 1) + { + // Waiting for char one of the escaped hex value + mEscaped[0] = c; + mEscapedState = 2; + continue; + } + else if(mEscapedState == 2) + { + // Escaped value, decode it + mEscaped[1] = c; // str terminated in constructor + mEscapedState = 0; // stop being in escaped mode + long ch = ::strtol(mEscaped, NULL, 16); + if(ch <= 0 || ch > 255) + { + // Bad character, just ignore + continue; + } + + // Use this instead + c = (char)ch; + } + else if(c == '+') + { + c = ' '; + } + else if(c == '%') + { + mEscapedState = 1; + continue; + } + + // Store decoded value into the appropriate string + if(mInKey) + { + mCurrentKey += c; + } + else + { + mCurrentValue += c; + } + } + + // Don't do anything here with left over values, DecodeChunk might be called + // again. Let Finish() clean up. +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPQueryDecoder::Finish() +// Purpose: Finish the decoding. Necessary to get the last item! +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPQueryDecoder::Finish() +{ + // Insert any remaining value. + if(!mCurrentKey.empty()) + { + mrDecodeInto.insert(HTTPRequest::QueryEn_t(mCurrentKey, mCurrentValue)); + // Blank values, just in case + mCurrentKey.erase(); + mCurrentValue.erase(); + } +} + + diff --git a/lib/httpserver/HTTPQueryDecoder.h b/lib/httpserver/HTTPQueryDecoder.h new file mode 100644 index 00000000..ca5afe7e --- /dev/null +++ b/lib/httpserver/HTTPQueryDecoder.h @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPQueryDecoder.h +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPQUERYDECODER__H +#define HTTPQUERYDECODER__H + +#include "HTTPRequest.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPQueryDecoder +// Purpose: Utility class to decode HTTP query strings +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPQueryDecoder +{ +public: + HTTPQueryDecoder(HTTPRequest::Query_t &rDecodeInto); + ~HTTPQueryDecoder(); +private: + // no copying + HTTPQueryDecoder(const HTTPQueryDecoder &); + HTTPQueryDecoder &operator=(const HTTPQueryDecoder &); +public: + + void DecodeChunk(const char *pQueryString, int QueryStringSize); + void Finish(); + +private: + HTTPRequest::Query_t &mrDecodeInto; + std::string mCurrentKey; + std::string mCurrentValue; + bool mInKey; + char mEscaped[4]; + int mEscapedState; +}; + +#endif // HTTPQUERYDECODER__H + diff --git a/lib/httpserver/HTTPRequest.cpp b/lib/httpserver/HTTPRequest.cpp new file mode 100644 index 00000000..a94d96b0 --- /dev/null +++ b/lib/httpserver/HTTPRequest.cpp @@ -0,0 +1,813 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPRequest.cpp +// Purpose: Request object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <sstream> + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPQueryDecoder.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "IOStreamGetLine.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#define MAX_CONTENT_SIZE (128*1024) + +#define ENSURE_COOKIE_JAR_ALLOCATED \ + if(mpCookies == 0) {mpCookies = new CookieJar_t;} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::HTTPRequest() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPRequest::HTTPRequest() + : mMethod(Method_UNINITIALISED), + mHostPort(80), // default if not specified + mHTTPVersion(0), + mContentLength(-1), + mpCookies(0), + mClientKeepAliveRequested(false), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::HTTPRequest(enum Method, +// const std::string&) +// Purpose: Alternate constructor for hand-crafted requests +// Created: 03/01/09 +// +// -------------------------------------------------------------------------- +HTTPRequest::HTTPRequest(enum Method method, const std::string& rURI) + : mMethod(method), + mRequestURI(rURI), + mHostPort(80), // default if not specified + mHTTPVersion(HTTPVersion_1_1), + mContentLength(-1), + mpCookies(0), + mClientKeepAliveRequested(false), + mExpectContinue(false), + mpStreamToReadFrom(NULL) +{ +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::~HTTPRequest() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPRequest::~HTTPRequest() +{ + // Clean up any cookies + if(mpCookies != 0) + { + delete mpCookies; + mpCookies = 0; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::GetMethodName() +// Purpose: Returns the name of the request's HTTP method verb +// as a string. +// Created: 28/7/15 +// +// -------------------------------------------------------------------------- + +std::string HTTPRequest::GetMethodName() const +{ + switch(mMethod) + { + case Method_UNINITIALISED: return "uninitialised"; + case Method_UNKNOWN: return "unknown"; + case Method_GET: return "GET"; + case Method_HEAD: return "HEAD"; + case Method_POST: return "POST"; + case Method_PUT: return "PUT"; + default: + std::ostringstream oss; + oss << "unknown-" << mMethod; + return oss.str(); + }; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::Receive(IOStreamGetLine &, int) +// Purpose: Read the request from an IOStreamGetLine (and +// attached stream). +// Returns false if there was no valid request, +// probably due to a kept-alive connection closing. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::Receive(IOStreamGetLine &rGetLine, int Timeout) +{ + // Check caller's logic + if(mMethod != Method_UNINITIALISED) + { + THROW_EXCEPTION(HTTPException, RequestAlreadyBeenRead); + } + + // Read the first line, which is of a different format to the rest of the lines + std::string requestLine; + if(!rGetLine.GetLine(requestLine, false /* no preprocessing */, Timeout)) + { + // Didn't get the request line, probably end of connection which had been kept alive + return false; + } + BOX_TRACE("Request line: " << requestLine); + + // Check the method + size_t p = 0; // current position in string + p = requestLine.find(' '); // end of first word + + if(p == std::string::npos) + { + // No terminating space, looks bad + p = requestLine.size(); + } + else + { + mHttpVerb = requestLine.substr(0, p); + if (mHttpVerb == "GET") + { + mMethod = Method_GET; + } + else if (mHttpVerb == "HEAD") + { + mMethod = Method_HEAD; + } + else if (mHttpVerb == "POST") + { + mMethod = Method_POST; + } + else if (mHttpVerb == "PUT") + { + mMethod = Method_PUT; + } + else + { + mMethod = Method_UNKNOWN; + } + } + + // Skip spaces to find URI + const char *requestLinePtr = requestLine.c_str(); + while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') + { + ++p; + } + + // Check there's a URI following... + if(requestLinePtr[p] == '\0') + { + // Didn't get the request line, probably end of connection which had been kept alive + return false; + } + + // Read the URI, unescaping any %XX hex codes + while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') + { + // End of URI, on to query string? + if(requestLinePtr[p] == '?') + { + // Put the rest into the query string, without escaping anything + ++p; + while(requestLinePtr[p] != ' ' && requestLinePtr[p] != '\0') + { + mQueryString += requestLinePtr[p]; + ++p; + } + break; + } + // Needs unescaping? + else if(requestLinePtr[p] == '+') + { + mRequestURI += ' '; + } + else if(requestLinePtr[p] == '%') + { + // Be tolerant about this... bad things are silently accepted, + // rather than throwing an error. + char code[4] = {0,0,0,0}; + code[0] = requestLinePtr[++p]; + if(code[0] != '\0') + { + code[1] = requestLinePtr[++p]; + } + + // Convert into a char code + long c = ::strtol(code, NULL, 16); + + // Accept it? + if(c > 0 && c <= 255) + { + mRequestURI += (char)c; + } + } + else + { + // Simple copy of character + mRequestURI += requestLinePtr[p]; + } + + ++p; + } + + // End of URL? + if(requestLinePtr[p] == '\0') + { + // Assume HTTP 0.9 + mHTTPVersion = HTTPVersion_0_9; + } + else + { + // Skip any more spaces + while(requestLinePtr[p] != '\0' && requestLinePtr[p] == ' ') + { + ++p; + } + + // Check to see if there's the right string next... + if(::strncmp(requestLinePtr + p, "HTTP/", 5) == 0) + { + // Find the version numbers + int major, minor; + if(::sscanf(requestLinePtr + p + 5, "%d.%d", &major, &minor) != 2) + { + THROW_EXCEPTION_MESSAGE(HTTPException, BadRequest, + "Unable to parse HTTP version number: " << + requestLinePtr); + } + + // Store version + mHTTPVersion = (major * HTTPVersion__MajorMultiplier) + minor; + } + else + { + // Not good -- wrong string found + THROW_EXCEPTION_MESSAGE(HTTPException, BadRequest, + "Unable to parse HTTP request line: " << + requestLinePtr); + } + } + + BOX_TRACE("HTTPRequest: method=" << mMethod << ", uri=" << + mRequestURI << ", version=" << mHTTPVersion); + + // If HTTP 1.1 or greater, assume keep-alive + if(mHTTPVersion >= HTTPVersion_1_1) + { + mClientKeepAliveRequested = true; + } + + // Decode query string? + if((mMethod == Method_GET || mMethod == Method_HEAD) && !mQueryString.empty()) + { + HTTPQueryDecoder decoder(mQuery); + decoder.DecodeChunk(mQueryString.c_str(), mQueryString.size()); + decoder.Finish(); + } + + // Now parse the headers + ParseHeaders(rGetLine, Timeout); + + std::string expected; + if(GetHeader("Expect", &expected)) + { + if(expected == "100-continue") + { + mExpectContinue = true; + } + } + + // Parse form data? + if(mMethod == Method_POST && mContentLength >= 0) + { + // Too long? Don't allow people to be nasty by sending lots of data + if(mContentLength > MAX_CONTENT_SIZE) + { + THROW_EXCEPTION(HTTPException, POSTContentTooLong); + } + + // Some data in the request to follow, parsing it bit by bit + HTTPQueryDecoder decoder(mQuery); + // Don't forget any data left in the GetLine object + int fromBuffer = rGetLine.GetSizeOfBufferedData(); + if(fromBuffer > mContentLength) fromBuffer = mContentLength; + if(fromBuffer > 0) + { + BOX_TRACE("Decoding " << fromBuffer << " bytes of " + "data from getline buffer"); + decoder.DecodeChunk((const char *)rGetLine.GetBufferedData(), fromBuffer); + // And tell the getline object to ignore the data we just used + rGetLine.IgnoreBufferedData(fromBuffer); + } + // Then read any more data, as required + int bytesToGo = mContentLength - fromBuffer; + while(bytesToGo > 0) + { + char buf[4096]; + int toRead = sizeof(buf); + if(toRead > bytesToGo) toRead = bytesToGo; + IOStream &rstream(rGetLine.GetUnderlyingStream()); + int r = rstream.Read(buf, toRead, Timeout); + if(r == 0) + { + // Timeout, just error + THROW_EXCEPTION_MESSAGE(HTTPException, RequestReadFailed, + "Failed to read complete request with the timeout"); + } + decoder.DecodeChunk(buf, r); + bytesToGo -= r; + } + // Finish off + decoder.Finish(); + } + else if (mContentLength > 0) + { + IOStream::pos_type bytesToCopy = rGetLine.GetSizeOfBufferedData(); + if (bytesToCopy > mContentLength) + { + bytesToCopy = mContentLength; + } + Write(rGetLine.GetBufferedData(), bytesToCopy); + SetForReading(); + mpStreamToReadFrom = &(rGetLine.GetUnderlyingStream()); + } + + return true; +} + +void HTTPRequest::ReadContent(IOStream& rStreamToWriteTo) +{ + Seek(0, SeekType_Absolute); + + CopyStreamTo(rStreamToWriteTo); + IOStream::pos_type bytesCopied = GetSize(); + + while (bytesCopied < mContentLength) + { + char buffer[1024]; + IOStream::pos_type bytesToCopy = sizeof(buffer); + if (bytesToCopy > mContentLength - bytesCopied) + { + bytesToCopy = mContentLength - bytesCopied; + } + bytesToCopy = mpStreamToReadFrom->Read(buffer, bytesToCopy); + rStreamToWriteTo.Write(buffer, bytesToCopy); + bytesCopied += bytesToCopy; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::Send(IOStream &, int) +// Purpose: Write the request to an IOStream using HTTP. +// Created: 03/01/09 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::Send(IOStream &rStream, int Timeout, bool ExpectContinue) +{ + switch (mMethod) + { + case Method_UNINITIALISED: + THROW_EXCEPTION(HTTPException, RequestNotInitialised); break; + case Method_UNKNOWN: + THROW_EXCEPTION(HTTPException, BadRequest); break; + case Method_GET: + rStream.Write("GET"); break; + case Method_HEAD: + rStream.Write("HEAD"); break; + case Method_POST: + rStream.Write("POST"); break; + case Method_PUT: + rStream.Write("PUT"); break; + } + + rStream.Write(" "); + rStream.Write(mRequestURI.c_str()); + rStream.Write(" "); + + switch (mHTTPVersion) + { + case HTTPVersion_0_9: rStream.Write("HTTP/0.9"); break; + case HTTPVersion_1_0: rStream.Write("HTTP/1.0"); break; + case HTTPVersion_1_1: rStream.Write("HTTP/1.1"); break; + default: + THROW_EXCEPTION_MESSAGE(HTTPException, NotImplemented, + "Unsupported HTTP version: " << mHTTPVersion); + } + + rStream.Write("\n"); + std::ostringstream oss; + + if (mContentLength != -1) + { + oss << "Content-Length: " << mContentLength << "\n"; + } + + if (mContentType != "") + { + oss << "Content-Type: " << mContentType << "\n"; + } + + if (mHostName != "") + { + if (mHostPort != 80) + { + oss << "Host: " << mHostName << ":" << mHostPort << + "\n"; + } + else + { + oss << "Host: " << mHostName << "\n"; + } + } + + if (mpCookies) + { + THROW_EXCEPTION_MESSAGE(HTTPException, NotImplemented, + "Cookie support not implemented yet"); + } + + if (mClientKeepAliveRequested) + { + oss << "Connection: keep-alive\n"; + } + else + { + oss << "Connection: close\n"; + } + + for (std::vector<Header>::iterator i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + oss << i->first << ": " << i->second << "\n"; + } + + if (ExpectContinue) + { + oss << "Expect: 100-continue\n"; + } + + rStream.Write(oss.str().c_str()); + rStream.Write("\n"); + + return true; +} + +void HTTPRequest::SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse) +{ + IOStream::pos_type size = pStreamToSend->BytesLeftToRead(); + if (size != IOStream::SizeOfStreamUnknown) + { + mContentLength = size; + } + + Send(rStreamToSendTo, Timeout, true); + + rResponse.Receive(rStreamToSendTo, Timeout); + if (rResponse.GetResponseCode() != 100) + { + // bad response, abort now + return; + } + + pStreamToSend->CopyStreamTo(rStreamToSendTo, Timeout); + + // receive the final response + rResponse.Receive(rStreamToSendTo, Timeout); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::ParseHeaders(IOStreamGetLine &, int) +// Purpose: Private. Parse the headers of the request +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPRequest::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) +{ + std::string header; + bool haveHeader = false; + while(true) + { + if(rGetLine.IsEOF()) + { + // Header terminates unexpectedly + THROW_EXCEPTION(HTTPException, BadRequest) + } + + std::string currentLine; + if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION(HTTPException, RequestReadFailed) + } + + // Is this a continuation of the previous line? + bool processHeader = haveHeader; + if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) + { + // A continuation, don't process anything yet + processHeader = false; + } + //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); + + // Parse the header -- this will actually process the header + // from the previous run around the loop. + if(processHeader) + { + // Find where the : is in the line + const char *h = header.c_str(); + int p = 0; + while(h[p] != '\0' && h[p] != ':') + { + ++p; + } + // Skip white space + int dataStart = p + 1; + while(h[dataStart] == ' ' || h[dataStart] == '\t') + { + ++dataStart; + } + + std::string header_name(ToLowerCase(std::string(h, + p))); + + if (header_name == "content-length") + { + // Decode number + long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK + if(len < 0) len = 0; + // Store + mContentLength = len; + } + else if (header_name == "content-type") + { + // Store rest of string as content type + mContentType = h + dataStart; + } + else if (header_name == "host") + { + // Store host header + mHostName = h + dataStart; + + // Is there a port number to split off? + std::string::size_type colon = mHostName.find_first_of(':'); + if(colon != std::string::npos) + { + // There's a port in the string... attempt to turn it into an int + mHostPort = ::strtol(mHostName.c_str() + colon + 1, 0, 10); + + // Truncate the string to just the hostname + mHostName = mHostName.substr(0, colon); + + BOX_TRACE("Host: header, hostname = " << + "'" << mHostName << "', host " + "port = " << mHostPort); + } + } + else if (header_name == "cookie") + { + // Parse cookies + ParseCookies(header, dataStart); + } + else if (header_name == "connection") + { + // Connection header, what is required? + const char *v = h + dataStart; + if(::strcasecmp(v, "close") == 0) + { + mClientKeepAliveRequested = false; + } + else if(::strcasecmp(v, "keep-alive") == 0) + { + mClientKeepAliveRequested = true; + } + // else don't understand, just assume default for protocol version + } + else + { + mExtraHeaders.push_back(Header(header_name, + h + dataStart)); + } + + // Unset have header flag, as it's now been processed + haveHeader = false; + } + + // Store the chunk of header the for next time round + if(haveHeader) + { + header += currentLine; + } + else + { + header = currentLine; + haveHeader = true; + } + + // End of headers? + if(currentLine.empty()) + { + // All done! + break; + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::ParseCookies(const std::string &, int) +// Purpose: Parse the cookie header +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +void HTTPRequest::ParseCookies(const std::string &rHeader, int DataStarts) +{ + const char *data = rHeader.c_str() + DataStarts; + const char *pos = data; + const char *itemStart = pos; + std::string name; + + enum + { + s_NAME, s_VALUE, s_VALUE_QUOTED, s_FIND_NEXT_NAME + } state = s_NAME; + + do + { + switch(state) + { + case s_NAME: + { + if(*pos == '=') + { + // Found the name. Store + name.assign(itemStart, pos - itemStart); + // Looking at values now + state = s_VALUE; + if((*(pos + 1)) == '"') + { + // Actually it's a quoted value, skip over that + ++pos; + state = s_VALUE_QUOTED; + } + // Record starting point for this item + itemStart = pos + 1; + } + } + break; + + case s_VALUE: + { + if(*pos == ';' || *pos == ',' || *pos == '\0') + { + // Name ends + ENSURE_COOKIE_JAR_ALLOCATED + std::string value(itemStart, pos - itemStart); + (*mpCookies)[name] = value; + // And move to the waiting stage + state = s_FIND_NEXT_NAME; + } + } + break; + + case s_VALUE_QUOTED: + { + if(*pos == '"') + { + // That'll do nicely, save it + ENSURE_COOKIE_JAR_ALLOCATED + std::string value(itemStart, pos - itemStart); + (*mpCookies)[name] = value; + // And move to the waiting stage + state = s_FIND_NEXT_NAME; + } + } + break; + + case s_FIND_NEXT_NAME: + { + // Skip over terminators and white space to get to the next name + if(*pos != ';' && *pos != ',' && *pos != ' ' && *pos != '\t') + { + // Name starts here + itemStart = pos; + state = s_NAME; + } + } + break; + + default: + // Ooops + THROW_EXCEPTION(HTTPException, Internal) + break; + } + } + while(*(pos++) != 0); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::GetCookie(const char *, std::string &) const +// Purpose: Fetch a cookie's value. If cookie not present, returns false +// and string is unaltered. +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +bool HTTPRequest::GetCookie(const char *CookieName, std::string &rValueOut) const +{ + // Got any cookies? + if(mpCookies == 0) + { + return false; + } + + // See if it's there + CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); + if(v != mpCookies->end()) + { + // Return the value + rValueOut = v->second; + return true; + } + + return false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPRequest::GetCookie(const char *) +// Purpose: Return a string for the given cookie, or the null string if the +// cookie has not been recieved. +// Created: 22/8/04 +// +// -------------------------------------------------------------------------- +const std::string &HTTPRequest::GetCookie(const char *CookieName) const +{ + static const std::string noCookie; + + // Got any cookies? + if(mpCookies == 0) + { + return noCookie; + } + + // See if it's there + CookieJar_t::const_iterator v(mpCookies->find(std::string(CookieName))); + if(v != mpCookies->end()) + { + // Return the value + return v->second; + } + + return noCookie; +} + + + diff --git a/lib/httpserver/HTTPRequest.h b/lib/httpserver/HTTPRequest.h new file mode 100644 index 00000000..16c4d16c --- /dev/null +++ b/lib/httpserver/HTTPRequest.h @@ -0,0 +1,194 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPRequest.h +// Purpose: Request object for HTTP connections +// Created: 26/3/2004 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPREQUEST__H +#define HTTPREQUEST__H + +#include <string> +#include <map> + +#include "CollectInBufferStream.h" + +class HTTPResponse; +class IOStream; +class IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPRequest +// Purpose: Request object for HTTP connections. Although it +// inherits from CollectInBufferStream, not all of the +// request data is held in memory, only the beginning. +// Use ReadContent() to write it all (including the +// buffered beginning) to another stream, e.g. a file. +// Created: 26/3/2004 +// +// -------------------------------------------------------------------------- +class HTTPRequest : public CollectInBufferStream +{ +public: + enum Method + { + Method_UNINITIALISED = -1, + Method_UNKNOWN = 0, + Method_GET = 1, + Method_HEAD = 2, + Method_POST = 3, + Method_PUT = 4 + }; + + HTTPRequest(); + HTTPRequest(enum Method method, const std::string& rURI); + ~HTTPRequest(); +private: + // no copying + HTTPRequest(const HTTPRequest &); + HTTPRequest &operator=(const HTTPRequest &); +public: + typedef std::multimap<std::string, std::string> Query_t; + typedef Query_t::value_type QueryEn_t; + typedef std::pair<std::string, std::string> Header; + + enum + { + HTTPVersion__MajorMultiplier = 1000, + HTTPVersion_0_9 = 9, + HTTPVersion_1_0 = 1000, + HTTPVersion_1_1 = 1001 + }; + + bool Receive(IOStreamGetLine &rGetLine, int Timeout); + bool Send(IOStream &rStream, int Timeout, bool ExpectContinue = false); + void SendWithStream(IOStream &rStreamToSendTo, int Timeout, + IOStream* pStreamToSend, HTTPResponse& rResponse); + void ReadContent(IOStream& rStreamToWriteTo); + + typedef std::map<std::string, std::string> CookieJar_t; + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::Get*() + // Purpose: Various Get accessors + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + enum Method GetMethod() const {return mMethod;} + std::string GetMethodName() const; + const std::string &GetRequestURI() const {return mRequestURI;} + + // Note: the HTTPRequest generates and parses the Host: header + // Do not attempt to set one yourself with AddHeader(). + const std::string &GetHostName() const {return mHostName;} + void SetHostName(const std::string& rHostName) + { + mHostName = rHostName; + } + + const int GetHostPort() const {return mHostPort;} + const std::string &GetQueryString() const {return mQueryString;} + int GetHTTPVersion() const {return mHTTPVersion;} + const Query_t &GetQuery() const {return mQuery;} + int GetContentLength() const {return mContentLength;} + const std::string &GetContentType() const {return mContentType;} + const CookieJar_t *GetCookies() const {return mpCookies;} // WARNING: May return NULL + bool GetCookie(const char *CookieName, std::string &rValueOut) const; + const std::string &GetCookie(const char *CookieName) const; + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + std::string header = ToLowerCase(rName); + + for (std::vector<Header>::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == header) + { + *pValueOut = i->second; + return true; + } + } + + return false; + } + std::vector<Header> GetHeaders() { return mExtraHeaders; } + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPRequest::GetClientKeepAliveRequested() + // Purpose: Returns true if the client requested that the connection + // should be kept open for further requests. + // Created: 22/12/04 + // + // -------------------------------------------------------------------------- + bool GetClientKeepAliveRequested() const {return mClientKeepAliveRequested;} + void SetClientKeepAliveRequested(bool keepAlive) + { + mClientKeepAliveRequested = keepAlive; + } + + void AddHeader(const std::string& rName, const std::string& rValue) + { + mExtraHeaders.push_back(Header(ToLowerCase(rName), rValue)); + } + bool IsExpectingContinue() const { return mExpectContinue; } + const char* GetVerb() const + { + if (!mHttpVerb.empty()) + { + return mHttpVerb.c_str(); + } + switch (mMethod) + { + case Method_UNINITIALISED: return "Uninitialized"; + case Method_UNKNOWN: return "Unknown"; + case Method_GET: return "GET"; + case Method_HEAD: return "HEAD"; + case Method_POST: return "POST"; + case Method_PUT: return "PUT"; + } + return "Bad"; + } + +private: + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); + void ParseCookies(const std::string &rHeader, int DataStarts); + + enum Method mMethod; + std::string mRequestURI; + std::string mHostName; + int mHostPort; + std::string mQueryString; + int mHTTPVersion; + Query_t mQuery; + int mContentLength; + std::string mContentType; + CookieJar_t *mpCookies; + bool mClientKeepAliveRequested; + std::vector<Header> mExtraHeaders; + bool mExpectContinue; + IOStream* mpStreamToReadFrom; + std::string mHttpVerb; + + std::string ToLowerCase(const std::string& rInput) const + { + std::string output = rInput; + for (std::string::iterator c = output.begin(); + c != output.end(); c++) + { + *c = tolower(*c); + } + return output; + } +}; + +#endif // HTTPREQUEST__H + diff --git a/lib/httpserver/HTTPResponse.cpp b/lib/httpserver/HTTPResponse.cpp new file mode 100644 index 00000000..c56f286f --- /dev/null +++ b/lib/httpserver/HTTPResponse.cpp @@ -0,0 +1,649 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPResponse.cpp +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include <stdio.h> +#include <string.h> + +#include "HTTPResponse.h" +#include "IOStreamGetLine.h" +#include "autogen_HTTPException.h" + +#include "MemLeakFindOn.h" + +// Static variables +std::string HTTPResponse::msDefaultURIPrefix; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse(IOStream*) +// Purpose: Constructor for response to be sent to a stream +// Created: 04/01/09 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse(IOStream* pStreamToSendTo) + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(pStreamToSendTo) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::HTTPResponse() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPResponse::HTTPResponse() + : mResponseCode(HTTPResponse::Code_NoContent), + mResponseIsDynamicContent(true), + mKeepAlive(false), + mContentLength(-1), + mpStreamToSendTo(NULL) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::~HTTPResponse() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPResponse::~HTTPResponse() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::ResponseCodeToString(int) +// Purpose: Return string equivalent of the response code, +// suitable for Status: headers +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const char *HTTPResponse::ResponseCodeToString(int ResponseCode) +{ + switch(ResponseCode) + { + case Code_OK: return "200 OK"; break; + case Code_NoContent: return "204 No Content"; break; + case Code_MovedPermanently: return "301 Moved Permanently"; break; + case Code_Found: return "302 Found"; break; + case Code_NotModified: return "304 Not Modified"; break; + case Code_TemporaryRedirect: return "307 Temporary Redirect"; break; + case Code_MethodNotAllowed: return "400 Method Not Allowed"; break; + case Code_Unauthorized: return "401 Unauthorized"; break; + case Code_Forbidden: return "403 Forbidden"; break; + case Code_NotFound: return "404 Not Found"; break; + case Code_InternalServerError: return "500 Internal Server Error"; break; + case Code_NotImplemented: return "501 Not Implemented"; break; + default: + { + THROW_EXCEPTION(HTTPException, UnknownResponseCodeUsed) + } + } + return "500 Internal Server Error"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetResponseCode(int) +// Purpose: Set the response code to be returned +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetResponseCode(int Code) +{ + mResponseCode = Code; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetContentType(const char *) +// Purpose: Set content type +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetContentType(const char *ContentType) +{ + mContentType = ContentType; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::Send(IOStream &, bool) +// Purpose: Build the response, and send via the stream. +// Created: 26/3/2004 +// +// -------------------------------------------------------------------------- +void HTTPResponse::Send(bool OmitContent) +{ + if (!mpStreamToSendTo) + { + THROW_EXCEPTION(HTTPException, NoStreamConfigured); + } + + if (GetSize() != 0 && mContentType.empty()) + { + THROW_EXCEPTION(HTTPException, NoContentTypeSet); + } + + // Build and send header + { + std::string header("HTTP/1.1 "); + header += ResponseCodeToString(mResponseCode); + header += "\r\nContent-Type: "; + header += mContentType; + header += "\r\nContent-Length: "; + { + char len[32]; + ::sprintf(len, "%d", OmitContent?(0):(GetSize())); + header += len; + } + // Extra headers... + for(std::vector<std::pair<std::string, std::string> >::const_iterator i(mExtraHeaders.begin()); i != mExtraHeaders.end(); ++i) + { + header += "\r\n"; + header += i->first + ": " + i->second; + } + // NOTE: a line ending must be included here in all cases + // Control whether the response is cached + if(mResponseIsDynamicContent) + { + // dynamic is private and can't be cached + header += "\r\nCache-Control: no-cache, private"; + } + else + { + // static is allowed to be cached for a day + header += "\r\nCache-Control: max-age=86400"; + } + + if(mKeepAlive) + { + header += "\r\nConnection: keep-alive\r\n\r\n"; + } + else + { + header += "\r\nConnection: close\r\n\r\n"; + } + + // NOTE: header ends with blank line in all cases + + // Write to stream + mpStreamToSendTo->Write(header.c_str(), header.size()); + } + + // Send content + if(!OmitContent) + { + mpStreamToSendTo->Write(GetBuffer(), GetSize()); + } +} + +void HTTPResponse::SendContinue() +{ + mpStreamToSendTo->Write("HTTP/1.1 100 Continue\r\n"); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::ParseHeaders(IOStreamGetLine &, int) +// Purpose: Private. Parse the headers of the response +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::ParseHeaders(IOStreamGetLine &rGetLine, int Timeout) +{ + std::string header; + bool haveHeader = false; + while(true) + { + if(rGetLine.IsEOF()) + { + // Header terminates unexpectedly + THROW_EXCEPTION(HTTPException, BadRequest) + } + + std::string currentLine; + if(!rGetLine.GetLine(currentLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION(HTTPException, RequestReadFailed) + } + + // Is this a continuation of the previous line? + bool processHeader = haveHeader; + if(!currentLine.empty() && (currentLine[0] == ' ' || currentLine[0] == '\t')) + { + // A continuation, don't process anything yet + processHeader = false; + } + //TRACE3("%d:%d:%s\n", processHeader, haveHeader, currentLine.c_str()); + + // Parse the header -- this will actually process the header + // from the previous run around the loop. + if(processHeader) + { + // Find where the : is in the line + const char *h = header.c_str(); + int p = 0; + while(h[p] != '\0' && h[p] != ':') + { + ++p; + } + // Skip white space + int dataStart = p + 1; + while(h[dataStart] == ' ' || h[dataStart] == '\t') + { + ++dataStart; + } + + if(p == sizeof("Content-Length")-1 + && ::strncasecmp(h, "Content-Length", sizeof("Content-Length")-1) == 0) + { + // Decode number + long len = ::strtol(h + dataStart, NULL, 10); // returns zero in error case, this is OK + if(len < 0) len = 0; + // Store + mContentLength = len; + } + else if(p == sizeof("Content-Type")-1 + && ::strncasecmp(h, "Content-Type", sizeof("Content-Type")-1) == 0) + { + // Store rest of string as content type + mContentType = h + dataStart; + } + else if(p == sizeof("Cookie")-1 + && ::strncasecmp(h, "Cookie", sizeof("Cookie")-1) == 0) + { + THROW_EXCEPTION(HTTPException, NotImplemented); + /* + // Parse cookies + ParseCookies(header, dataStart); + */ + } + else if(p == sizeof("Connection")-1 + && ::strncasecmp(h, "Connection", sizeof("Connection")-1) == 0) + { + // Connection header, what is required? + const char *v = h + dataStart; + if(::strcasecmp(v, "close") == 0) + { + mKeepAlive = false; + } + else if(::strcasecmp(v, "keep-alive") == 0) + { + mKeepAlive = true; + } + // else don't understand, just assume default for protocol version + } + else + { + std::string headerName = header.substr(0, p); + AddHeader(headerName, h + dataStart); + } + + // Unset have header flag, as it's now been processed + haveHeader = false; + } + + // Store the chunk of header the for next time round + if(haveHeader) + { + header += currentLine; + } + else + { + header = currentLine; + haveHeader = true; + } + + // End of headers? + if(currentLine.empty()) + { + // All done! + break; + } + } +} + +void HTTPResponse::Receive(IOStream& rStream, int Timeout) +{ + IOStreamGetLine rGetLine(rStream); + + if(rGetLine.IsEOF()) + { + // Connection terminated unexpectedly + THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse, + "HTTP server closed the connection without sending a response"); + } + + std::string statusLine; + if(!rGetLine.GetLine(statusLine, false /* no preprocess */, Timeout)) + { + // Timeout + THROW_EXCEPTION_MESSAGE(HTTPException, ResponseReadFailed, + "Failed to get a response from the HTTP server within the timeout"); + } + + if (statusLine.substr(0, 7) != "HTTP/1." || statusLine[8] != ' ') + { + THROW_EXCEPTION_MESSAGE(HTTPException, BadResponse, + "HTTP server sent an invalid HTTP status line: " << statusLine); + } + + if (statusLine[5] == '1' && statusLine[7] == '1') + { + // HTTP/1.1 default is to keep alive + mKeepAlive = true; + } + + // Decode the status code + long status = ::strtol(statusLine.substr(9, 3).c_str(), NULL, 10); + // returns zero in error case, this is OK + if (status < 0) status = 0; + // Store + mResponseCode = status; + + // 100 Continue responses have no headers, terminating newline, or body + if (status == 100) + { + return; + } + + ParseHeaders(rGetLine, Timeout); + + // push back whatever bytes we have left + // rGetLine.DetachFile(); + if (mContentLength > 0) + { + if (mContentLength < rGetLine.GetSizeOfBufferedData()) + { + // very small response, not good! + THROW_EXCEPTION(HTTPException, NotImplemented); + } + + mContentLength -= rGetLine.GetSizeOfBufferedData(); + + Write(rGetLine.GetBufferedData(), + rGetLine.GetSizeOfBufferedData()); + } + + while (mContentLength != 0) // could be -1 as well + { + char buffer[4096]; + int readSize = sizeof(buffer); + if (mContentLength > 0 && mContentLength < readSize) + { + readSize = mContentLength; + } + readSize = rStream.Read(buffer, readSize, Timeout); + if (readSize == 0) + { + break; + } + mContentLength -= readSize; + Write(buffer, readSize); + } + + SetForReading(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *) +// Purpose: Add header, given entire line +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +/* +void HTTPResponse::AddHeader(const char *EntireHeaderLine) +{ + mExtraHeaders.push_back(std::string(EntireHeaderLine)); +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const std::string &) +// Purpose: Add header, given entire line +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +/* +void HTTPResponse::AddHeader(const std::string &rEntireHeaderLine) +{ + mExtraHeaders.push_back(rEntireHeaderLine); +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *, const char *) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const char *pHeader, const char *pValue) +{ + mExtraHeaders.push_back(Header(pHeader, pValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const char *, const std::string &) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const char *pHeader, const std::string &rValue) +{ + mExtraHeaders.push_back(Header(pHeader, rValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::AddHeader(const std::string &, const std::string &) +// Purpose: Add header, given header name and it's value +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::AddHeader(const std::string &rHeader, const std::string &rValue) +{ + mExtraHeaders.push_back(Header(rHeader, rValue)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetCookie(const char *, const char *, const char *, int) +// Purpose: Sets a cookie, using name, value, path and expiry time. +// Created: 20/8/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetCookie(const char *Name, const char *Value, const char *Path, int ExpiresAt) +{ + if(ExpiresAt != 0) + { + THROW_EXCEPTION(HTTPException, NotImplemented) + } + + // Appears you shouldn't use quotes when you generate set-cookie headers. + // Oh well. It was fun finding that out. +/* std::string h("Set-Cookie: "); + h += Name; + h += "=\""; + h += Value; + h += "\"; Version=\"1\"; Path=\""; + h += Path; + h += "\""; +*/ + std::string h; + h += Name; + h += "="; + h += Value; + h += "; Version=1; Path="; + h += Path; + + mExtraHeaders.push_back(Header("Set-Cookie", h)); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetAsRedirect(const char *, bool) +// Purpose: Sets the response objects to be a redirect to another page. +// If IsLocalURL == true, the default prefix will be added. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetAsRedirect(const char *RedirectTo, bool IsLocalURI) +{ + if(mResponseCode != HTTPResponse::Code_NoContent + || !mContentType.empty() + || GetSize() != 0) + { + THROW_EXCEPTION(HTTPException, CannotSetRedirectIfReponseHasData) + } + + // Set response code + mResponseCode = Code_Found; + + // Set location to redirect to + std::string header; + if(IsLocalURI) header += msDefaultURIPrefix; + header += RedirectTo; + mExtraHeaders.push_back(Header("Location", header)); + + // Set up some default content + mContentType = "text/html"; + #define REDIRECT_HTML_1 "<html><head><title>Redirection\n

    Redirect to content

    \n" + Write(REDIRECT_HTML_1, sizeof(REDIRECT_HTML_1) - 1); + if(IsLocalURI) Write(msDefaultURIPrefix.c_str(), msDefaultURIPrefix.size()); + Write(RedirectTo, ::strlen(RedirectTo)); + Write(REDIRECT_HTML_2, sizeof(REDIRECT_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::SetAsNotFound(const char *) +// Purpose: Set the response object to be a standard page not found 404 response. +// Created: 7/4/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::SetAsNotFound(const char *URI) +{ + if(mResponseCode != HTTPResponse::Code_NoContent + || mExtraHeaders.size() != 0 + || !mContentType.empty() + || GetSize() != 0) + { + THROW_EXCEPTION(HTTPException, CannotSetNotFoundIfReponseHasData) + } + + // Set response code + mResponseCode = Code_NotFound; + + // Set data + mContentType = "text/html"; + #define NOT_FOUND_HTML_1 "404 Not Found\n

    404 Not Found

    \n

    The URI " + #define NOT_FOUND_HTML_2 " was not found on this server.

    \n" + Write(NOT_FOUND_HTML_1, sizeof(NOT_FOUND_HTML_1) - 1); + WriteStringDefang(std::string(URI)); + Write(NOT_FOUND_HTML_2, sizeof(NOT_FOUND_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPResponse::WriteStringDefang(const char *, unsigned int) +// Purpose: Writes a string 'defanged', ie has HTML special characters escaped +// so that people can't output arbitary HTML by playing with +// URLs and form parameters, and it's safe to write strings into +// HTML element attribute values. +// Created: 9/4/04 +// +// -------------------------------------------------------------------------- +void HTTPResponse::WriteStringDefang(const char *String, unsigned int StringLen) +{ + while(StringLen > 0) + { + unsigned int toWrite = 0; + while(toWrite < StringLen + && String[toWrite] != '<' + && String[toWrite] != '>' + && String[toWrite] != '&' + && String[toWrite] != '"') + { + ++toWrite; + } + if(toWrite > 0) + { + Write(String, toWrite); + StringLen -= toWrite; + String += toWrite; + } + + // Is it a bad character next? + while(StringLen > 0) + { + bool notSpecial = false; + switch(*String) + { + case '<': Write("<", 4); break; + case '>': Write(">", 4); break; + case '&': Write("&", 5); break; + case '"': Write(""", 6); break; + default: + // Stop this loop + notSpecial = true; + break; + } + if(notSpecial) break; + ++String; + --StringLen; + } + } +} + + diff --git a/lib/httpserver/HTTPResponse.h b/lib/httpserver/HTTPResponse.h new file mode 100644 index 00000000..f39825d9 --- /dev/null +++ b/lib/httpserver/HTTPResponse.h @@ -0,0 +1,177 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPResponse.h +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPRESPONSE__H +#define HTTPRESPONSE__H + +#include +#include + +#include "CollectInBufferStream.h" + +class IOStreamGetLine; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPResponse +// Purpose: Response object for HTTP connections +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPResponse : public CollectInBufferStream +{ +public: + HTTPResponse(IOStream* pStreamToSendTo); + HTTPResponse(); + ~HTTPResponse(); + + // allow copying, but be very careful with the response stream, + // you can only read it once! (this class doesn't police it). + HTTPResponse(const HTTPResponse& rOther) + : mResponseCode(rOther.mResponseCode), + mResponseIsDynamicContent(rOther.mResponseIsDynamicContent), + mKeepAlive(rOther.mKeepAlive), + mContentType(rOther.mContentType), + mExtraHeaders(rOther.mExtraHeaders), + mContentLength(rOther.mContentLength), + mpStreamToSendTo(rOther.mpStreamToSendTo) + { + Write(rOther.GetBuffer(), rOther.GetSize()); + } + + HTTPResponse &operator=(const HTTPResponse &rOther) + { + Reset(); + Write(rOther.GetBuffer(), rOther.GetSize()); + mResponseCode = rOther.mResponseCode; + mResponseIsDynamicContent = rOther.mResponseIsDynamicContent; + mKeepAlive = rOther.mKeepAlive; + mContentType = rOther.mContentType; + mExtraHeaders = rOther.mExtraHeaders; + mContentLength = rOther.mContentLength; + mpStreamToSendTo = rOther.mpStreamToSendTo; + return *this; + } + + typedef std::pair Header; + + void SetResponseCode(int Code); + int GetResponseCode() const { return mResponseCode; } + void SetContentType(const char *ContentType); + const std::string& GetContentType() { return mContentType; } + int64_t GetContentLength() { return mContentLength; } + + void SetAsRedirect(const char *RedirectTo, bool IsLocalURI = true); + void SetAsNotFound(const char *URI); + + void Send(bool OmitContent = false); + void SendContinue(); + void Receive(IOStream& rStream, int Timeout = IOStream::TimeOutInfinite); + + // void AddHeader(const char *EntireHeaderLine); + // void AddHeader(const std::string &rEntireHeaderLine); + void AddHeader(const char *Header, const char *Value); + void AddHeader(const char *Header, const std::string &rValue); + void AddHeader(const std::string &rHeader, const std::string &rValue); + bool GetHeader(const std::string& rName, std::string* pValueOut) const + { + for (std::vector
    ::const_iterator + i = mExtraHeaders.begin(); + i != mExtraHeaders.end(); i++) + { + if (i->first == rName) + { + *pValueOut = i->second; + return true; + } + } + return false; + } + std::string GetHeaderValue(const std::string& rName) + { + std::string value; + if (!GetHeader(rName, &value)) + { + THROW_EXCEPTION(CommonException, ConfigNoKey); + } + return value; + } + + // Set dynamic content flag, default is content is dynamic + void SetResponseIsDynamicContent(bool IsDynamic) {mResponseIsDynamicContent = IsDynamic;} + // Set keep alive control, default is to mark as to be closed + void SetKeepAlive(bool KeepAlive) {mKeepAlive = KeepAlive;} + bool IsKeepAlive() {return mKeepAlive;} + + void SetCookie(const char *Name, const char *Value, const char *Path = "/", int ExpiresAt = 0); + + enum + { + Code_OK = 200, + Code_NoContent = 204, + Code_MovedPermanently = 301, + Code_Found = 302, // redirection + Code_NotModified = 304, + Code_TemporaryRedirect = 307, + Code_MethodNotAllowed = 400, + Code_Unauthorized = 401, + Code_Forbidden = 403, + Code_NotFound = 404, + Code_InternalServerError = 500, + Code_NotImplemented = 501 + }; + + static const char *ResponseCodeToString(int ResponseCode); + + void WriteStringDefang(const char *String, unsigned int StringLen); + void WriteStringDefang(const std::string &rString) {WriteStringDefang(rString.c_str(), rString.size());} + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::WriteString(const std::string &) + // Purpose: Write a string to the response (simple sugar function) + // Created: 9/4/04 + // + // -------------------------------------------------------------------------- + void WriteString(const std::string &rString) + { + Write(rString.c_str(), rString.size()); + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPResponse::SetDefaultURIPrefix(const std::string &) + // Purpose: Set default prefix used to local redirections + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + static void SetDefaultURIPrefix(const std::string &rPrefix) + { + msDefaultURIPrefix = rPrefix; + } + +private: + int mResponseCode; + bool mResponseIsDynamicContent; + bool mKeepAlive; + std::string mContentType; + std::vector
    mExtraHeaders; + int64_t mContentLength; // only used when reading response from stream + IOStream* mpStreamToSendTo; // nonzero only when constructed with a stream + + static std::string msDefaultURIPrefix; + + void ParseHeaders(IOStreamGetLine &rGetLine, int Timeout); +}; + +#endif // HTTPRESPONSE__H + diff --git a/lib/httpserver/HTTPServer.cpp b/lib/httpserver/HTTPServer.cpp new file mode 100644 index 00000000..a2daed99 --- /dev/null +++ b/lib/httpserver/HTTPServer.cpp @@ -0,0 +1,256 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPServer.cpp +// Purpose: HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "HTTPServer.h" +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "IOStreamGetLine.h" + +#include "MemLeakFindOn.h" + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPServer() +// Purpose: Constructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPServer::HTTPServer(int Timeout) +: mTimeout(Timeout) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::~HTTPServer() +// Purpose: Destructor +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +HTTPServer::~HTTPServer() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::DaemonName() +// Purpose: As interface, generic name for daemon +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const char *HTTPServer::DaemonName() const +{ + return "generic-httpserver"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::GetConfigVerify() +// Purpose: As interface -- return most basic config so it's only necessary to +// provide this if you want to add extra directives. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *HTTPServer::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerifyKey verifyrootkeys[] = + { + HTTPSERVER_VERIFY_ROOT_KEYS + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::Run() +// Purpose: As interface. +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::Run() +{ + // Do some configuration stuff + const Configuration &conf(GetConfiguration()); + HTTPResponse::SetDefaultURIPrefix(conf.GetKeyValue("AddressPrefix")); + + // Let the base class do the work + ServerStream::Run(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::Connection(SocketStream &) +// Purpose: As interface, handle connection +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::Connection(std::auto_ptr apConn) +{ + // Create a get line object to use + IOStreamGetLine getLine(*apConn); + + // Notify dervived claases + HTTPConnectionOpening(); + + bool handleRequests = true; + while(handleRequests) + { + // Parse the request + HTTPRequest request; + if(!request.Receive(getLine, mTimeout)) + { + // Didn't get request, connection probably closed. + break; + } + + // Generate a response + HTTPResponse response(apConn.get()); + + try + { + Handle(request, response); + } + catch(BoxException &e) + { + char exceptionCode[256]; + ::sprintf(exceptionCode, "%s (%d/%d)", e.what(), + e.GetType(), e.GetSubType()); + SendInternalErrorResponse(exceptionCode, response); + } + catch(...) + { + SendInternalErrorResponse("unknown", response); + } + + // Keep alive? response.GetSize() works for CollectInBufferStream, but + // when we make HTTPResponse stream the response instead, we'll need to + // figure out whether we can get the response length from the IOStream + // to be streamed, or not. Also, we don't currently support chunked + // encoding, and http://tools.ietf.org/html/rfc7230#section-3.3.1 says + // that "If any transfer coding other than chunked is applied to a + // response payload body, the sender MUST either apply chunked as the + // final transfer coding or terminate the message by closing the + // connection. So for now, keepalive stays off. + if(false && request.GetClientKeepAliveRequested() && + response.GetSize() >= 0) + { + // Mark the response to the client as supporting keepalive + response.SetKeepAlive(true); + } + else + { + // Stop now + handleRequests = false; + } + + // Send the response (omit any content if this is a HEAD method request) + response.Send(request.GetMethod() == HTTPRequest::Method_HEAD); + } + + // Notify derived classes + HTTPConnectionClosing(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::SendInternalErrorResponse(const char*, +// HTTPResponse&) +// Purpose: Generates an error message in the provided response +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse) +{ + #define ERROR_HTML_1 "Internal Server Error\n" \ + "

    Internal Server Error

    \n" \ + "

    An error, type " + #define ERROR_HTML_2 " occured when processing the request.

    " \ + "

    Please try again later.

    " \ + "\n\n" + + // Generate the error page + // rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/html"); + rResponse.Write(ERROR_HTML_1, sizeof(ERROR_HTML_1) - 1); + rResponse.IOStream::Write(rErrorMsg.c_str()); + rResponse.Write(ERROR_HTML_2, sizeof(ERROR_HTML_2) - 1); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPConnectionOpening() +// Purpose: Override to get notifications of connections opening +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::HTTPConnectionOpening() +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::HTTPConnectionClosing() +// Purpose: Override to get notifications of connections closing +// Created: 22/12/04 +// +// -------------------------------------------------------------------------- +void HTTPServer::HTTPConnectionClosing() +{ +} + + diff --git a/lib/httpserver/HTTPServer.h b/lib/httpserver/HTTPServer.h new file mode 100644 index 00000000..8ac1ff83 --- /dev/null +++ b/lib/httpserver/HTTPServer.h @@ -0,0 +1,82 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: HTTPServer.h +// Purpose: HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef HTTPSERVER__H +#define HTTPSERVER__H + +#include "ServerStream.h" +#include "SocketStream.h" + +class HTTPRequest; +class HTTPResponse; + +// -------------------------------------------------------------------------- +// +// Class +// Name: HTTPServer +// Purpose: HTTP server +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- +class HTTPServer : public ServerStream +{ +public: + HTTPServer(int Timeout = 60000); + // default timeout leaves 1 minute for clients to get a second request in. + ~HTTPServer(); +private: + // no copying + HTTPServer(const HTTPServer &); + HTTPServer &operator=(const HTTPServer &); +public: + + int GetTimeout() const {return mTimeout;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: HTTPServer::Handle(const HTTPRequest &, HTTPResponse &) + // Purpose: Response to a request, filling in the response object for sending + // at some point in the future. + // Created: 26/3/04 + // + // -------------------------------------------------------------------------- + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) = 0; + + // For notifications to derived classes + virtual void HTTPConnectionOpening(); + virtual void HTTPConnectionClosing(); + +protected: + void SendInternalErrorResponse(const std::string& rErrorMsg, + HTTPResponse& rResponse); + int GetTimeout() { return mTimeout; } + +private: + int mTimeout; // Timeout for read operations + const char *DaemonName() const; + const ConfigurationVerify *GetConfigVerify() const; + void Run(); + void Connection(std::auto_ptr apStream); +}; + +// Root level +#define HTTPSERVER_VERIFY_ROOT_KEYS \ + ConfigurationVerifyKey("AddressPrefix", \ + ConfigTest_Exists | ConfigTest_LastEntry) + +// AddressPrefix is, for example, http://localhost:1080 -- ie the beginning of the URI +// This is used for handling redirections. + +// Server level +#define HTTPSERVER_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) + +#endif // HTTPSERVER__H + diff --git a/lib/httpserver/Makefile.extra b/lib/httpserver/Makefile.extra new file mode 100644 index 00000000..ef47f398 --- /dev/null +++ b/lib/httpserver/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_HTTPException.h autogen_HTTPException.cpp: $(MAKEEXCEPTION) HTTPException.txt + $(_PERL) $(MAKEEXCEPTION) HTTPException.txt + diff --git a/lib/httpserver/S3Client.cpp b/lib/httpserver/S3Client.cpp new file mode 100644 index 00000000..21814066 --- /dev/null +++ b/lib/httpserver/S3Client.cpp @@ -0,0 +1,292 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +// #include +// #include + +#include + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Client.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::GetObject(const std::string& rObjectURI) +// Purpose: Retrieve the object with the specified URI (key) +// from your S3 bucket. +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::GetObject(const std::string& rObjectURI) +{ + return FinishAndSendRequest(HTTPRequest::Method_GET, rObjectURI); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::HeadObject(const std::string& rObjectURI) +// Purpose: Retrieve the metadata for the object with the +// specified URI (key) from your S3 bucket. +// Created: 03/08/2015 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::HeadObject(const std::string& rObjectURI) +{ + return FinishAndSendRequest(HTTPRequest::Method_HEAD, rObjectURI); +} + + +HTTPResponse HeadObject(const std::string& rObjectURI); +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::PutObject(const std::string& rObjectURI, +// IOStream& rStreamToSend, const char* pContentType) +// Purpose: Upload the stream to S3, creating or overwriting the +// object with the specified URI (key) in your S3 +// bucket. +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType) +{ + return FinishAndSendRequest(HTTPRequest::Method_PUT, rObjectURI, + &rStreamToSend, pContentType); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::FinishAndSendRequest( +// HTTPRequest::Method Method, +// const std::string& rRequestURI, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which creates an HTTP request to S3, +// populates the date and authorization header fields, +// and sends it to S3 (or the simulator), attaching +// the specified stream if any to the request. Opens a +// connection to the server if necessary, which may +// throw a ConnectionException. Returns the HTTP +// response returned by S3, which may be a 500 error. +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, IOStream* pStreamToSend, + const char* pStreamContentType) +{ + HTTPRequest request(Method, rRequestURI); + request.SetHostName(mHostName); + + std::ostringstream date; + time_t tt = time(NULL); + struct tm *tp = gmtime(&tt); + if (!tp) + { + BOX_ERROR("Failed to get current time"); + THROW_EXCEPTION(HTTPException, Internal); + } + const char *dow[] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; + date << dow[tp->tm_wday] << ", "; + const char *month[] = {"Jan","Feb","Mar","Apr","May","Jun", + "Jul","Aug","Sep","Oct","Nov","Dec"}; + date << std::internal << std::setfill('0') << + std::setw(2) << tp->tm_mday << " " << + month[tp->tm_mon] << " " << + (tp->tm_year + 1900) << " "; + date << std::setw(2) << tp->tm_hour << ":" << + std::setw(2) << tp->tm_min << ":" << + std::setw(2) << tp->tm_sec << " GMT"; + request.AddHeader("Date", date.str()); + + if (pStreamContentType) + { + request.AddHeader("Content-Type", pStreamContentType); + } + + std::string s3suffix = ".s3.amazonaws.com"; + std::string bucket; + if (mHostName.size() > s3suffix.size()) + { + std::string suffix = mHostName.substr(mHostName.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = mHostName.substr(0, mHostName.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << request.GetVerb() << "\n"; + data << "\n"; /* Content-MD5 */ + data << request.GetContentType() << "\n"; + data << date.str() << "\n"; + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << request.GetRequestURI(); + std::string data_string = data.str(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + mSecretKey.c_str(), mSecretKey.size(), + (const unsigned char*)data_string.c_str(), + data_string.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + + base64::encoder encoder; + std::string auth_code = "AWS " + mAccessKey + ":" + + encoder.encode(digest); + + if (auth_code[auth_code.size() - 1] == '\n') + { + auth_code = auth_code.substr(0, auth_code.size() - 1); + } + + request.AddHeader("Authorization", auth_code); + + if (mpSimulator) + { + if (pStreamToSend) + { + pStreamToSend->CopyStreamTo(request); + } + + request.SetForReading(); + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + mpSimulator->Handle(request, response); + return response; + } + else + { + try + { + if (!mapClientSocket.get()) + { + mapClientSocket.reset(new SocketStream()); + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + } + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + catch (ConnectionException &ce) + { + if (ce.GetType() == ConnectionException::SocketWriteError) + { + // server may have disconnected us, + // try to reconnect, just once + mapClientSocket->Open(Socket::TypeINET, + mHostName, mPort); + return SendRequest(request, pStreamToSend, + pStreamContentType); + } + else + { + BOX_TRACE("S3Client: " << mHostName << " ! " << ce.what()); + throw; + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::SendRequest(HTTPRequest& rRequest, +// IOStream* pStreamToSend, +// const char* pStreamContentType) +// Purpose: Internal method which sends a pre-existing HTTP +// request to S3. Attaches the specified stream if any +// to the request. Opens a connection to the server if +// necessary, which may throw a ConnectionException. +// Returns the HTTP response returned by S3, which may +// be a 500 error. +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +HTTPResponse S3Client::SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend, const char* pStreamContentType) +{ + HTTPResponse response; + + if (pStreamToSend) + { + rRequest.SendWithStream(*mapClientSocket, mNetworkTimeout, + pStreamToSend, response); + } + else + { + rRequest.Send(*mapClientSocket, mNetworkTimeout); + response.Receive(*mapClientSocket, mNetworkTimeout); + } + + if(!response.IsKeepAlive()) + { + BOX_TRACE("Server will close the connection, closing our end too."); + mapClientSocket.reset(); + } + else + { + BOX_TRACE("Server will keep the connection open for more requests."); + } + + return response; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Client::CheckResponse(HTTPResponse&, +// std::string& message) +// Purpose: Check the status code of an Amazon S3 response, and +// throw an exception with a useful message (including +// the supplied message) if it's not a 200 OK response. +// Created: 26/07/2015 +// +// -------------------------------------------------------------------------- + +void S3Client::CheckResponse(const HTTPResponse& response, const std::string& message) const +{ + if(response.GetResponseCode() != HTTPResponse::Code_OK) + { + THROW_EXCEPTION_MESSAGE(HTTPException, RequestFailedUnexpectedly, + message); + } +} + diff --git a/lib/httpserver/S3Client.h b/lib/httpserver/S3Client.h new file mode 100644 index 00000000..4cbb4b96 --- /dev/null +++ b/lib/httpserver/S3Client.h @@ -0,0 +1,78 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.h +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3CLIENT__H +#define S3CLIENT__H + +#include +#include + +#include "HTTPRequest.h" +#include "SocketStream.h" + +class HTTPResponse; +class HTTPServer; +class IOStream; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Client +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Client +{ + public: + S3Client(HTTPServer* pSimulator, const std::string& rHostName, + const std::string& rAccessKey, const std::string& rSecretKey) + : mpSimulator(pSimulator), + mHostName(rHostName), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey), + mNetworkTimeout(30000) + { } + + S3Client(std::string HostName, int Port, const std::string& rAccessKey, + const std::string& rSecretKey) + : mpSimulator(NULL), + mHostName(HostName), + mPort(Port), + mAccessKey(rAccessKey), + mSecretKey(rSecretKey), + mNetworkTimeout(30000) + { } + + HTTPResponse GetObject(const std::string& rObjectURI); + HTTPResponse HeadObject(const std::string& rObjectURI); + HTTPResponse PutObject(const std::string& rObjectURI, + IOStream& rStreamToSend, const char* pContentType = NULL); + void CheckResponse(const HTTPResponse& response, const std::string& message) const; + int GetNetworkTimeout() const { return mNetworkTimeout; } + + private: + HTTPServer* mpSimulator; + std::string mHostName; + int mPort; + std::auto_ptr mapClientSocket; + std::string mAccessKey, mSecretKey; + int mNetworkTimeout; // milliseconds + + HTTPResponse FinishAndSendRequest(HTTPRequest::Method Method, + const std::string& rRequestURI, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); + HTTPResponse SendRequest(HTTPRequest& rRequest, + IOStream* pStreamToSend = NULL, + const char* pStreamContentType = NULL); +}; + +#endif // S3CLIENT__H + diff --git a/lib/httpserver/S3Simulator.cpp b/lib/httpserver/S3Simulator.cpp new file mode 100644 index 00000000..df8910d7 --- /dev/null +++ b/lib/httpserver/S3Simulator.cpp @@ -0,0 +1,312 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Client.cpp +// Purpose: Amazon S3 client helper implementation class +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +// #include +// #include + +#include + +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "autogen_HTTPException.h" +#include "IOStream.h" +#include "Logging.h" +#include "S3Simulator.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: HTTPServer::GetConfigVerify() +// Purpose: Returns additional configuration options for the +// S3 simulator. Currently the access key, secret key +// and store directory can be configured. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify* S3Simulator::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + HTTPSERVER_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerifyKey verifyrootkeys[] = + { + ConfigurationVerifyKey("AccessKey", ConfigTest_Exists), + ConfigurationVerifyKey("SecretKey", ConfigTest_Exists), + ConfigurationVerifyKey("StoreDirectory", ConfigTest_Exists), + HTTPSERVER_VERIFY_ROOT_KEYS + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + verifyrootkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::Handle(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles any incoming S3 request, by checking +// authorization and then dispatching to one of the +// private Handle* methods. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + // if anything goes wrong, return a 500 error + rResponse.SetResponseCode(HTTPResponse::Code_InternalServerError); + rResponse.SetContentType("text/plain"); + + try + { + const Configuration& rConfig(GetConfiguration()); + std::string access_key = rConfig.GetKeyValue("AccessKey"); + std::string secret_key = rConfig.GetKeyValue("SecretKey"); + + std::string md5, date, bucket; + rRequest.GetHeader("content-md5", &md5); + rRequest.GetHeader("date", &date); + + std::string host = rRequest.GetHostName(); + std::string s3suffix = ".s3.amazonaws.com"; + if (host.size() > s3suffix.size()) + { + std::string suffix = host.substr(host.size() - + s3suffix.size(), s3suffix.size()); + if (suffix == s3suffix) + { + bucket = host.substr(0, host.size() - + s3suffix.size()); + } + } + + std::ostringstream data; + data << rRequest.GetVerb() << "\n"; + data << md5 << "\n"; + data << rRequest.GetContentType() << "\n"; + data << date << "\n"; + + // header names are already in lower case, i.e. canonical form + + std::vector headers = rRequest.GetHeaders(); + std::sort(headers.begin(), headers.end()); + + for (std::vector::iterator + i = headers.begin(); i != headers.end(); i++) + { + if (i->first.substr(0, 5) == "x-amz") + { + data << i->first << ":" << i->second << "\n"; + } + } + + if (! bucket.empty()) + { + data << "/" << bucket; + } + + data << rRequest.GetRequestURI(); + std::string data_string = data.str(); + + unsigned char digest_buffer[EVP_MAX_MD_SIZE]; + unsigned int digest_size = sizeof(digest_buffer); + /* unsigned char* mac = */ HMAC(EVP_sha1(), + secret_key.c_str(), secret_key.size(), + (const unsigned char*)data_string.c_str(), + data_string.size(), digest_buffer, &digest_size); + std::string digest((const char *)digest_buffer, digest_size); + + base64::encoder encoder; + std::string expectedAuth = "AWS " + access_key + ":" + + encoder.encode(digest); + + if (expectedAuth[expectedAuth.size() - 1] == '\n') + { + expectedAuth = expectedAuth.substr(0, + expectedAuth.size() - 1); + } + + std::string actualAuth; + if (!rRequest.GetHeader("authorization", &actualAuth) || + actualAuth != expectedAuth) + { + rResponse.SetResponseCode(HTTPResponse::Code_Unauthorized); + SendInternalErrorResponse("Authentication Failed", + rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_GET) + { + HandleGet(rRequest, rResponse); + } + else if (rRequest.GetMethod() == HTTPRequest::Method_PUT) + { + HandlePut(rRequest, rResponse); + } + else + { + rResponse.SetResponseCode(HTTPResponse::Code_MethodNotAllowed); + SendInternalErrorResponse("Unsupported Method", + rResponse); + } + } + catch (CommonException &ce) + { + SendInternalErrorResponse(ce.what(), rResponse); + } + catch (std::exception &e) + { + SendInternalErrorResponse(e.what(), rResponse); + } + catch (...) + { + SendInternalErrorResponse("Unknown exception", rResponse); + } + + if (rResponse.GetResponseCode() != 200 && + rResponse.GetSize() == 0) + { + // no error message written, provide a default + std::ostringstream s; + s << rResponse.GetResponseCode(); + SendInternalErrorResponse(s.str().c_str(), rResponse); + } + + BOX_NOTICE(rResponse.GetResponseCode() << " " << rRequest.GetMethodName() << " " << + rRequest.GetRequestURI()); + + return; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandleGet(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 GET request, i.e. downloading an +// existing object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr apFile; + + try + { + apFile.reset(new FileStream(path)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/UsingRESTOperations.html + apFile->CopyStreamTo(rResponse); + rResponse.AddHeader("x-amz-id-2", "qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: S3Simulator::HandlePut(HTTPRequest &rRequest, +// HTTPResponse &rResponse) +// Purpose: Handles an S3 PUT request, i.e. uploading data to +// create or replace an object. +// Created: 09/01/09 +// +// -------------------------------------------------------------------------- + +void S3Simulator::HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + std::string path = GetConfiguration().GetKeyValue("StoreDirectory"); + path += rRequest.GetRequestURI(); + std::auto_ptr apFile; + + try + { + apFile.reset(new FileStream(path, O_CREAT | O_WRONLY)); + } + catch (CommonException &ce) + { + if (ce.GetSubType() == CommonException::OSFileOpenError) + { + rResponse.SetResponseCode(HTTPResponse::Code_NotFound); + } + else if (ce.GetSubType() == CommonException::AccessDenied) + { + rResponse.SetResponseCode(HTTPResponse::Code_Forbidden); + } + throw; + } + + if (rRequest.IsExpectingContinue()) + { + rResponse.SendContinue(); + } + + rRequest.ReadContent(*apFile); + + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTObjectPUT.html + rResponse.AddHeader("x-amz-id-2", "LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7"); + rResponse.AddHeader("x-amz-request-id", "F2A8CCCA26B4B26D"); + rResponse.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + rResponse.AddHeader("Last-Modified", "Sun, 1 Jan 2006 12:00:00 GMT"); + rResponse.AddHeader("ETag", "\"828ef3fdfa96f00ad9f27c383fc9ac7f\""); + rResponse.SetContentType(""); + rResponse.AddHeader("Server", "AmazonS3"); + rResponse.SetResponseCode(HTTPResponse::Code_OK); +} diff --git a/lib/httpserver/S3Simulator.h b/lib/httpserver/S3Simulator.h new file mode 100644 index 00000000..eef4f400 --- /dev/null +++ b/lib/httpserver/S3Simulator.h @@ -0,0 +1,47 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: S3Simulator.h +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- + +#ifndef S3SIMULATOR__H +#define S3SIMULATOR__H + +#include "HTTPServer.h" + +class ConfigurationVerify; +class HTTPRequest; +class HTTPResponse; + +// -------------------------------------------------------------------------- +// +// Class +// Name: S3Simulator +// Purpose: Amazon S3 simulation HTTP server for S3 testing +// Created: 09/01/2009 +// +// -------------------------------------------------------------------------- +class S3Simulator : public HTTPServer +{ +public: + // Increase timeout to 5 minutes, from HTTPServer default of 1 minute, + // to help with debugging. + S3Simulator() : HTTPServer(300000) { } + ~S3Simulator() { } + + const ConfigurationVerify* GetConfigVerify() const; + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandleGet(HTTPRequest &rRequest, HTTPResponse &rResponse); + virtual void HandlePut(HTTPRequest &rRequest, HTTPResponse &rResponse); + + virtual const char *DaemonName() const + { + return "s3simulator"; + } +}; + +#endif // S3SIMULATOR__H + diff --git a/lib/httpserver/cdecode.cpp b/lib/httpserver/cdecode.cpp new file mode 100644 index 00000000..e632f182 --- /dev/null +++ b/lib/httpserver/cdecode.cpp @@ -0,0 +1,92 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cdecode.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +} diff --git a/lib/httpserver/cdecode.h b/lib/httpserver/cdecode.h new file mode 100644 index 00000000..d0d7f489 --- /dev/null +++ b/lib/httpserver/cdecode.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/lib/httpserver/cencode.cpp b/lib/httpserver/cencode.cpp new file mode 100644 index 00000000..b33c0683 --- /dev/null +++ b/lib/httpserver/cencode.cpp @@ -0,0 +1,113 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +extern "C" +{ + +#include "cencode.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = '\n'; + + return codechar - code_out; +} + +} diff --git a/lib/httpserver/cencode.h b/lib/httpserver/cencode.h new file mode 100644 index 00000000..cf321312 --- /dev/null +++ b/lib/httpserver/cencode.h @@ -0,0 +1,32 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ + diff --git a/lib/httpserver/decode.h b/lib/httpserver/decode.h new file mode 100644 index 00000000..fe59ef7a --- /dev/null +++ b/lib/httpserver/decode.h @@ -0,0 +1,77 @@ +// :mode=c++: +/* +decode.h - c++ wrapper for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_DECODE_H +#define BASE64_DECODE_H + +#include + +namespace base64 +{ + + extern "C" + { + #include "cdecode.h" + } + + struct decoder + { + base64_decodestate _state; + int _buffersize; + + decoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int decode(char value_in) + { + return base64_decode_value(value_in); + } + int decode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_decode_block(code_in, length_in, plaintext_out, &_state); + } + std::string decode(const std::string& input) + { + base64_init_decodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = decode(input.c_str(), input.size(), + output); + std::string output_string(output, outlength); + base64_init_decodestate(&_state); + delete [] output; + return output_string; + } + void decode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_decodestate(&_state); + // + const int N = _buffersize; + char* code = new char[N]; + char* plaintext = new char[N]; + int codelength; + int plainlength; + + do + { + istream_in.read((char*)code, N); + codelength = istream_in.gcount(); + plainlength = decode(code, codelength, plaintext); + ostream_in.write((const char*)plaintext, plainlength); + } + while (istream_in.good() && codelength > 0); + // + base64_init_decodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_DECODE_H diff --git a/lib/httpserver/encode.h b/lib/httpserver/encode.h new file mode 100644 index 00000000..81957a0f --- /dev/null +++ b/lib/httpserver/encode.h @@ -0,0 +1,87 @@ +// :mode=c++: +/* +encode.h - c++ wrapper for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_ENCODE_H +#define BASE64_ENCODE_H + +#include + +namespace base64 +{ + + extern "C" + { + #include "cencode.h" + } + + struct encoder + { + base64_encodestate _state; + int _buffersize; + + encoder(int buffersize_in = 4096) + : _buffersize(buffersize_in) + {} + int encode(char value_in) + { + return base64_encode_value(value_in); + } + int encode(const char* code_in, const int length_in, char* plaintext_out) + { + return base64_encode_block(code_in, length_in, plaintext_out, &_state); + } + int encode_end(char* plaintext_out) + { + return base64_encode_blockend(plaintext_out, &_state); + } + std::string encode(const std::string& input) + { + base64_init_encodestate(&_state); + char* output = new char[2*input.size()]; + int outlength = encode(input.c_str(), input.size(), + output); + outlength += encode_end(output + outlength); + std::string output_string(output, outlength); + base64_init_encodestate(&_state); + delete [] output; + return output_string; + } + void encode(std::istream& istream_in, std::ostream& ostream_in) + { + base64_init_encodestate(&_state); + // + const int N = _buffersize; + char* plaintext = new char[N]; + char* code = new char[2*N]; + int plainlength; + int codelength; + + do + { + istream_in.read(plaintext, N); + plainlength = istream_in.gcount(); + // + codelength = encode(plaintext, plainlength, code); + ostream_in.write(code, codelength); + } + while (istream_in.good() && plainlength > 0); + + codelength = encode_end(code); + ostream_in.write(code, codelength); + // + base64_init_encodestate(&_state); + + delete [] code; + delete [] plaintext; + } + }; + +} // namespace base64 + +#endif // BASE64_ENCODE_H + diff --git a/lib/intercept/intercept.cpp b/lib/intercept/intercept.cpp new file mode 100644 index 00000000..88ea0d6e --- /dev/null +++ b/lib/intercept/intercept.cpp @@ -0,0 +1,672 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: intercept.cpp +// Purpose: Syscall interception code for the raidfile test +// Created: 2003/07/22 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include "intercept.h" + +#ifdef HAVE_SYS_SYSCALL_H + #include +#endif +#include + +#ifdef HAVE_SYS_UIO_H + #include +#endif + +#include +#include + +#ifdef HAVE_DLFCN_H +#include +#endif + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + +#if !defined(HAVE_SYSCALL) && !defined(HAVE___SYSCALL) && !defined(HAVE___SYSCALL_NEED_DEFN) + #define PLATFORM_NO_SYSCALL +#endif + +#ifdef PLATFORM_NO_SYSCALL + // For some reason, syscall just doesn't work on Darwin + // so instead, we build functions using assembler in a varient + // of the technique used in the Darwin Libc + extern "C" int + TEST_open(const char *path, int flags, mode_t mode); + extern "C" int + TEST_close(int d); + extern "C" ssize_t + TEST_write(int d, const void *buf, size_t nbytes); + extern "C" ssize_t + TEST_read(int d, void *buf, size_t nbytes); + extern "C" ssize_t + TEST_readv(int d, const struct iovec *iov, int iovcnt); + extern "C" off_t + TEST_lseek(int fildes, off_t offset, int whence); +#else + // if we have __syscall, we should use it for everything + // (on FreeBSD 7 this is required for 64-bit alignment of off_t). + // if not, we should continue to use the old syscall(). + #ifdef HAVE___SYSCALL_NEED_DEFN + // Need this, not declared in syscall.h nor unistd.h + extern "C" off_t __syscall(quad_t number, ...); + #endif + #ifdef HAVE___SYSCALL + #undef syscall + #define syscall __syscall + #endif +#endif + +#include +#include + +#include "MemLeakFindOn.h" + +int intercept_count = 0; +const char *intercept_filename = 0; +int intercept_filedes = -1; +off_t intercept_errorafter = 0; +int intercept_errno = 0; +int intercept_syscall = 0; +off_t intercept_filepos = 0; +int intercept_delay_ms = 0; + +static opendir_t* opendir_real = NULL; +static readdir_t* readdir_real = NULL; +static readdir_t* readdir_hook = NULL; +static closedir_t* closedir_real = NULL; +static lstat_t* lstat_real = NULL; +static lstat_t* lstat_hook = NULL; +static const char* lstat_file = NULL; +static lstat_t* stat_real = NULL; +static lstat_t* stat_hook = NULL; +static const char* stat_file = NULL; + +static lstat_post_hook_t* lstat_post_hook = NULL; +static lstat_post_hook_t* stat_post_hook = NULL; + +#define SIZE_ALWAYS_ERROR -773 + +void intercept_clear_setup() +{ + intercept_count = 0; + intercept_filename = 0; + intercept_filedes = -1; + intercept_errorafter = 0; + intercept_syscall = 0; + intercept_filepos = 0; + intercept_delay_ms = 0; + readdir_hook = NULL; + stat_hook = NULL; + lstat_hook = NULL; + stat_post_hook = NULL; + lstat_post_hook = NULL; +} + +bool intercept_triggered() +{ + return intercept_count == 0; +} + +void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror) +{ + BOX_TRACE("Setup for error: " << filename << + ", after " << errorafter << + ", err " << errortoreturn << + ", syscall " << syscalltoerror); + + intercept_count = 1; + intercept_filename = filename; + intercept_filedes = -1; + intercept_errorafter = errorafter; + intercept_syscall = syscalltoerror; + intercept_errno = errortoreturn; + intercept_filepos = 0; + intercept_delay_ms = 0; +} + +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay, int num_delays) +{ + BOX_TRACE("Setup for delay: " << filename << + ", after " << delay_after << + ", wait " << delay_ms << " ms" << + ", times " << num_delays << + ", syscall " << syscall_to_delay); + + intercept_count = num_delays; + intercept_filename = filename; + intercept_filedes = -1; + intercept_errorafter = delay_after; + intercept_syscall = syscall_to_delay; + intercept_errno = 0; + intercept_filepos = 0; + intercept_delay_ms = delay_ms; +} + +bool intercept_errornow(int d, int size, int syscallnum) +{ + ASSERT(intercept_count > 0) + + if (intercept_filedes == -1) + { + return false; // no error please! + } + + if (d != intercept_filedes) + { + return false; // no error please! + } + + if (syscallnum != intercept_syscall) + { + return false; // no error please! + } + + bool ret = false; // no error unless one of the conditions matches + + //printf("Checking for err, %d, %d, %d\n", d, size, syscallnum); + + if (intercept_delay_ms != 0) + { + BOX_TRACE("Delaying " << intercept_delay_ms << " ms " << + " for syscall " << syscallnum << + " at " << intercept_filepos); + + struct timespec tm; + tm.tv_sec = intercept_delay_ms / 1000; + tm.tv_nsec = (intercept_delay_ms % 1000) * 1000000; + while (nanosleep(&tm, &tm) != 0 && + errno == EINTR) { } + } + + if (size == SIZE_ALWAYS_ERROR) + { + // Looks good for an error! + BOX_TRACE("Returning error " << intercept_errno << + " for syscall " << syscallnum); + ret = true; + } + else if (intercept_filepos + size < intercept_errorafter) + { + return false; // no error please + } + else if (intercept_errno != 0) + { + BOX_TRACE("Returning error " << intercept_errno << + " for syscall " << syscallnum << + " at " << intercept_filepos); + ret = true; + } + + intercept_count--; + if (intercept_count == 0) + { + intercept_clear_setup(); + } + + return ret; +} + +int intercept_reterr() +{ + int err = intercept_errno; + intercept_clear_setup(); + return err; +} + +#define CHECK_FOR_FAKE_ERROR_COND(D, S, CALL, FAILRES) \ + if(intercept_count > 0) \ + { \ + if(intercept_errornow(D, S, CALL)) \ + { \ + errno = intercept_reterr(); \ + return FAILRES; \ + } \ + } + +#if defined _FILE_OFFSET_BITS && _FILE_OFFSET_BITS == 64 + #define DEFINE_ONLY_OPEN64 +#endif + +extern "C" int +#ifdef DEFINE_ONLY_OPEN64 + open64(const char *path, int flags, ...) +#else + open(const char *path, int flags, ...) +#endif // DEFINE_ONLY_OPEN64 +{ + if(intercept_count > 0) + { + if(intercept_filename != NULL && + intercept_syscall == SYS_open && + strcmp(path, intercept_filename) == 0) + { + errno = intercept_reterr(); + return -1; + } + } + + mode_t mode = 0; + if (flags & O_CREAT) + { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_open(path, flags, mode); +#else + int r = syscall(SYS_open, path, flags, mode); +#endif + + if(intercept_filename != NULL && + intercept_count > 0 && + intercept_filedes == -1) + { + // Right file? + if(strcmp(intercept_filename, path) == 0) + { + intercept_filedes = r; + //printf("Found file to intercept, h = %d\n", r); + } + } + + return r; +} + +#ifndef DEFINE_ONLY_OPEN64 +extern "C" int +// open64(const char *path, int flags, mode_t mode) +// open64(const char *path, int flags, ...) +open64 (__const char *path, int flags, ...) +{ + mode_t mode = 0; + if (flags & O_CREAT) + { + va_list ap; + va_start(ap, flags); + mode = va_arg(ap, int); + va_end(ap); + } + + // With _FILE_OFFSET_BITS set to 64 this should really use (flags | + // O_LARGEFILE) here, but not actually necessary for the tests and not + // worth the trouble finding O_LARGEFILE + return open(path, flags, mode); +} +#endif // !DEFINE_ONLY_OPEN64 + +extern "C" int +close(int d) +{ + CHECK_FOR_FAKE_ERROR_COND(d, SIZE_ALWAYS_ERROR, SYS_close, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_close(d); +#else + int r = syscall(SYS_close, d); +#endif + if(r == 0) + { + if(d == intercept_filedes) + { + intercept_filedes = -1; + } + } + return r; +} + +extern "C" ssize_t +write(int d, const void *buf, size_t nbytes) +{ + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_write, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_write(d, buf, nbytes); +#else + int r = syscall(SYS_write, d, buf, nbytes); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" ssize_t +read(int d, void *buf, size_t nbytes) +{ + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_read, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_read(d, buf, nbytes); +#else + int r = syscall(SYS_read, d, buf, nbytes); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" ssize_t +readv(int d, const struct iovec *iov, int iovcnt) +{ + // how many bytes? + int nbytes = 0; + for(int b = 0; b < iovcnt; ++b) + { + nbytes += iov[b].iov_len; + } + + CHECK_FOR_FAKE_ERROR_COND(d, nbytes, SYS_readv, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_readv(d, iov, iovcnt); +#else + int r = syscall(SYS_readv, d, iov, iovcnt); +#endif + if(r != -1) + { + intercept_filepos += r; + } + return r; +} + +extern "C" off_t +lseek(int fildes, off_t offset, int whence) +{ + // random magic for lseek syscall, see /usr/src/lib/libc/sys/lseek.c + CHECK_FOR_FAKE_ERROR_COND(fildes, 0, SYS_lseek, -1); +#ifdef PLATFORM_NO_SYSCALL + int r = TEST_lseek(fildes, offset, whence); +#else + #ifdef HAVE_LSEEK_DUMMY_PARAM + off_t r = syscall(SYS_lseek, fildes, 0 /* extra 0 required here! */, offset, whence); + #elif defined(_FILE_OFFSET_BITS) + // Don't bother trying to call SYS__llseek on 32 bit since it is + // fiddly and not needed for the tests + off_t r = syscall(SYS_lseek, fildes, (uint32_t)offset, whence); + #else + off_t r = syscall(SYS_lseek, fildes, offset, whence); + #endif +#endif + if(r != -1) + { + intercept_filepos = r; + } + return r; +} + +void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn) +{ + if (hookfn != NULL && dirname == NULL) + { + dirname = intercept_filename; + ASSERT(dirname != NULL); + } + + if (hookfn != NULL) + { + BOX_TRACE("readdir hooked to " << hookfn << " for " << dirname); + } + else if (intercept_filename != NULL) + { + BOX_TRACE("readdir unhooked from " << readdir_hook << + " for " << intercept_filename); + } + + intercept_filename = dirname; + readdir_hook = hookfn; +} + +void intercept_setup_lstat_hook(const char *filename, lstat_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + lstat_file = filename; + lstat_hook = hookfn; +} + +void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + lstat_post_hook = hookfn; +} + +void intercept_setup_stat_post_hook(lstat_post_hook_t hookfn) +{ + /* + if (hookfn != NULL) + { + BOX_TRACE("lstat hooked to " << hookfn << " for " << filename); + } + else + { + BOX_TRACE("lstat unhooked from " << lstat_hook << " for " << + lstat_file); + } + */ + + stat_post_hook = hookfn; +} + +static void * find_function(const char *pName) +{ + dlerror(); + void *result = NULL; + + #ifdef HAVE_LARGE_FILE_SUPPORT + { + // search for the 64-bit version first + std::string name64(pName); + name64 += "64"; + result = dlsym(RTLD_NEXT, name64.c_str()); + if (dlerror() == NULL && result != NULL) + { + return result; + } + } + #endif + + result = dlsym(RTLD_NEXT, pName); + const char *errmsg = (const char *)dlerror(); + + if (errmsg == NULL) + { + return result; + } + + BOX_ERROR("Failed to find real " << pName << " function: " << errmsg); + return NULL; +} + +extern "C" +DIR *opendir(const char *dirname) +{ + if (opendir_real == NULL) + { + opendir_real = (opendir_t*)find_function(FUNC_OPENDIR); + } + + if (opendir_real == NULL) + { + perror("cannot find real opendir"); + return NULL; + } + + DIR* r = opendir_real(dirname); + + if (readdir_hook != NULL && + intercept_filename != NULL && + intercept_filedes == -1 && + strcmp(intercept_filename, dirname) == 0) + { + intercept_filedes = dirfd(r); + //printf("Found file to intercept, h = %d\n", r); + } + + return r; +} + +extern "C" +struct dirent *readdir(DIR *dir) +{ + if (readdir_hook != NULL && dirfd(dir) == intercept_filedes) + { + return readdir_hook(dir); + } + + if (readdir_real == NULL) + { + readdir_real = (readdir_t*)find_function(FUNC_READDIR); + } + + if (readdir_real == NULL) + { + perror("cannot find real readdir"); + return NULL; + } + + return readdir_real(dir); +} + +extern "C" +int closedir(DIR *dir) +{ + if (dirfd(dir) == intercept_filedes) + { + intercept_filedes = -1; + } + + if (closedir_real == NULL) + { + closedir_real = (closedir_t*)find_function("closedir"); + } + + if (closedir_real == NULL) + { + perror("cannot find real closedir"); + errno = ENOSYS; + return -1; + } + + return closedir_real(dir); +} + +extern "C" int +#ifdef LINUX_WEIRD_LSTAT +__lxstat(int ver, const char *file_name, STAT_STRUCT *buf) +#else +lstat(const char *file_name, STAT_STRUCT *buf) +#endif +{ + if (lstat_real == NULL) + { + #ifdef LINUX_WEIRD_LSTAT + lstat_real = (lstat_t*)find_function("__lxstat"); + #else + lstat_real = (lstat_t*)find_function("lstat"); + #endif + } + + if (lstat_real == NULL) + { + perror("cannot find real lstat"); + errno = ENOSYS; + return -1; + } + + if (lstat_hook == NULL || strcmp(file_name, lstat_file) != 0) + { + #ifdef LINUX_WEIRD_LSTAT + int ret = lstat_real(ver, file_name, buf); + #else + int ret = lstat_real(file_name, buf); + #endif + if (lstat_post_hook != NULL) + { + ret = lstat_post_hook(ret, file_name, buf); + } + return ret; + } + + #ifdef LINUX_WEIRD_LSTAT + return lstat_hook(ver, file_name, buf); + #else + return lstat_hook(file_name, buf); + #endif +} + +extern "C" int +#ifdef LINUX_WEIRD_LSTAT +__xstat(int ver, const char *file_name, STAT_STRUCT *buf) +#else +stat(const char *file_name, STAT_STRUCT *buf) +#endif +{ + if (stat_real == NULL) + { + #ifdef LINUX_WEIRD_LSTAT + stat_real = (lstat_t*)find_function("__xstat"); + #else + stat_real = (lstat_t*)find_function("stat"); + #endif + } + + if (stat_real == NULL) + { + perror("cannot find real stat"); + errno = ENOSYS; + return -1; + } + + if (stat_hook == NULL || strcmp(file_name, stat_file) != 0) + { + #ifdef LINUX_WEIRD_LSTAT + int ret = stat_real(ver, file_name, buf); + #else + int ret = stat_real(file_name, buf); + #endif + if (stat_post_hook != NULL) + { + ret = stat_post_hook(ret, file_name, buf); + } + return ret; + } + + #ifdef LINUX_WEIRD_LSTAT + return stat_hook(ver, file_name, buf); + #else + return stat_hook(file_name, buf); + #endif +} + +#endif // n PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE diff --git a/lib/intercept/intercept.h b/lib/intercept/intercept.h new file mode 100644 index 00000000..c0d61638 --- /dev/null +++ b/lib/intercept/intercept.h @@ -0,0 +1,66 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: intercept.h +// Purpose: Syscall interception code for unit tests +// Created: 2006/11/29 +// +// -------------------------------------------------------------------------- + +#ifndef INTERCEPT_H +#define INTERCEPT_H +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + +#include + +#ifdef __NetBSD__ //__NetBSD_Version__ is defined in sys/param.h +#include +#endif + +#if defined __NetBSD_Version__ && __NetBSD_Version__ >= 399000800 //3.99.8 vers. +#define FUNC_OPENDIR "__opendir30" +#define FUNC_READDIR "__readdir30" +#else +#define FUNC_OPENDIR "opendir" +#define FUNC_READDIR "readdir" +#endif + +#include +#include + +extern "C" +{ + typedef DIR *(opendir_t) (const char *name); + typedef struct dirent *(readdir_t) (DIR *dir); + typedef struct dirent *(readdir_t) (DIR *dir); + typedef int (closedir_t)(DIR *dir); +#if defined __GNUC__ && __GNUC__ >= 2 + #define LINUX_WEIRD_LSTAT + #define STAT_STRUCT struct stat /* should be stat64 */ + typedef int (lstat_t) (int ver, const char *file_name, + STAT_STRUCT *buf); +#else + #define STAT_STRUCT struct stat + typedef int (lstat_t) (const char *file_name, + STAT_STRUCT *buf); +#endif +} + +typedef int (lstat_post_hook_t) (int old_ret, const char *file_name, + struct stat *buf); + +void intercept_setup_error(const char *filename, unsigned int errorafter, + int errortoreturn, int syscalltoerror); +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay, int num_delays); +bool intercept_triggered(); + +void intercept_setup_readdir_hook(const char *dirname, readdir_t hookfn); +void intercept_setup_lstat_hook (const char *filename, lstat_t hookfn); +void intercept_setup_lstat_post_hook(lstat_post_hook_t hookfn); +void intercept_setup_stat_post_hook (lstat_post_hook_t hookfn); + +void intercept_clear_setup(); + +#endif // !PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE +#endif // !INTERCEPT_H diff --git a/lib/raidfile/Makefile.extra b/lib/raidfile/Makefile.extra new file mode 100644 index 00000000..bf06ed2f --- /dev/null +++ b/lib/raidfile/Makefile.extra @@ -0,0 +1,7 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_RaidFileException.h autogen_RaidFileException.cpp: $(MAKEEXCEPTION) RaidFileException.txt + $(_PERL) $(MAKEEXCEPTION) RaidFileException.txt + diff --git a/lib/raidfile/RaidFileController.cpp b/lib/raidfile/RaidFileController.cpp new file mode 100644 index 00000000..cf93947f --- /dev/null +++ b/lib/raidfile/RaidFileController.cpp @@ -0,0 +1,232 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileController.cpp +// Purpose: Controls config and daemon comms for RaidFile classes +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "Configuration.h" + +#include "MemLeakFindOn.h" + +RaidFileController RaidFileController::mController; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::RaidFileController() +// Purpose: Constructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::RaidFileController() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::~RaidFileController() +// Purpose: Destructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::~RaidFileController() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::RaidFileController() +// Purpose: Copy constructor +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileController::RaidFileController(const RaidFileController &rController) +{ + THROW_EXCEPTION(RaidFileException, Internal) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::Initialise(const std::string&) +// Purpose: Initialises the system, loading the configuration file. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +void RaidFileController::Initialise(const std::string& rConfigFilename) +{ + MEMLEAKFINDER_NO_LEAKS; + + static const ConfigurationVerifyKey verifykeys[] = + { + ConfigurationVerifyKey("SetNumber", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("BlockSize", + ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("Dir0", ConfigTest_Exists), + ConfigurationVerifyKey("Dir1", ConfigTest_Exists), + ConfigurationVerifyKey("Dir2", + ConfigTest_Exists | ConfigTest_LastEntry) + }; + + static const ConfigurationVerify subverify = + { + "*", + 0, + verifykeys, + ConfigTest_LastEntry, + 0 + }; + + static const ConfigurationVerify verify = + { + "RAID FILE CONFIG", + &subverify, + 0, + ConfigTest_LastEntry, + 0 + }; + + // Load the configuration + std::string err; + std::auto_ptr pconfig = Configuration::LoadAndVerify( + rConfigFilename, &verify, err); + + if(pconfig.get() == 0 || !err.empty()) + { + BOX_ERROR("RaidFile configuration file errors: " << err); + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + + // Allow reinitializing the controller by remove any existing + // disc sets. Used by Boxi unit tests. + mSetList.clear(); + + // Use the values + int expectedSetNum = 0; + std::vector confdiscs(pconfig->GetSubConfigurationNames()); + for(std::vector::const_iterator i(confdiscs.begin()); i != confdiscs.end(); ++i) + { + const Configuration &disc(pconfig->GetSubConfiguration((*i).c_str())); + + int setNum = disc.GetKeyValueInt("SetNumber"); + if(setNum != expectedSetNum) + { + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + RaidFileDiscSet set(setNum, (unsigned int)disc.GetKeyValueInt("BlockSize")); + // Get the values of the directory keys + std::string d0(disc.GetKeyValue("Dir0")); + std::string d1(disc.GetKeyValue("Dir1")); + std::string d2(disc.GetKeyValue("Dir2")); + // Are they all different (using RAID) or all the same (not using RAID) + if(d0 != d1 && d1 != d2 && d0 != d2) + { + set.push_back(d0); + set.push_back(d1); + set.push_back(d2); + } + else if(d0 == d1 && d0 == d2) + { + // Just push the first one, which is the non-RAID place to store files + set.push_back(d0); + } + else + { + // One must be the same as another! Which is bad. + THROW_EXCEPTION(RaidFileException, BadConfigFile) + } + mSetList.push_back(set); + expectedSetNum++; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::GetDiscSet(int) +// Purpose: Returns the numbered disc set +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +RaidFileDiscSet &RaidFileController::GetDiscSet(unsigned int DiscSetNum) +{ + if(DiscSetNum < 0 || DiscSetNum >= mSetList.size()) + { + THROW_EXCEPTION_MESSAGE(RaidFileException, NoSuchDiscSet, DiscSetNum << + " (" << mSetList.size() << " disc sets configured)") + } + + return mSetList[DiscSetNum]; +} + +// Overload to make usable in gdb debugger. +int RaidFileDiscSet::GetSetNumForWriteFiles(const char* filename) const +{ + return GetSetNumForWriteFiles(std::string(filename)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &) +// Purpose: Returns the set number the 'temporary' written files should +// be stored on, given a filename. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +int RaidFileDiscSet::GetSetNumForWriteFiles(const std::string &rFilename) const +{ + // Simple hash function, add up the ASCII values of all the characters, + // and get modulo number of partitions in the set. + std::string::const_iterator i(rFilename.begin()); + int h = 0; + for(; i != rFilename.end(); ++i) + { + h += (*i); + } + return h % size(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileController::DiscSetPathToFileSystemPath(unsigned int, const std::string &, int) +// Purpose: Given a Raid File style file name, return a filename for the physical filing system. +// DiscOffset is effectively the disc number (but remember files are rotated around the +// discs in a disc set) +// Created: 19/1/04 +// +// -------------------------------------------------------------------------- +std::string RaidFileController::DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset) +{ + if(DiscSetNum < 0 || DiscSetNum >= mController.mSetList.size()) + { + THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) + } + + // Work out which disc it's to be on + int disc = (mController.mSetList[DiscSetNum].GetSetNumForWriteFiles(rFilename) + DiscOffset) + % mController.mSetList[DiscSetNum].size(); + + // Make the string + std::string r((mController.mSetList[DiscSetNum])[disc]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + return r; +} + + + diff --git a/lib/raidfile/RaidFileController.h b/lib/raidfile/RaidFileController.h new file mode 100644 index 00000000..601cca22 --- /dev/null +++ b/lib/raidfile/RaidFileController.h @@ -0,0 +1,108 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileController.h +// Purpose: Controls config and daemon comms for RaidFile classes +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +/* NOTE: will log to local5: include a line like + local5.info /var/log/raidfile + in /etc/syslog.conf +*/ + +#ifndef RAIDFILECONTROLLER__H +#define RAIDFILECONTROLLER__H + +#include +#include + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileDiscSet +// Purpose: Describes a set of paritions for RAID like files. +// Use as list of directories containing the files. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +class RaidFileDiscSet : public std::vector +{ +public: + RaidFileDiscSet(int SetID, unsigned int BlockSize) + : mSetID(SetID), + mBlockSize(BlockSize) + { + } + RaidFileDiscSet(const RaidFileDiscSet &rToCopy) + : std::vector(rToCopy), + mSetID(rToCopy.mSetID), + mBlockSize(rToCopy.mBlockSize) + { + } + + ~RaidFileDiscSet() + { + } + + int GetSetID() const {return mSetID;} + + int GetSetNumForWriteFiles(const std::string &rFilename) const; + int GetSetNumForWriteFiles(const char* filename) const; + unsigned int GetBlockSize() const {return mBlockSize;} + + // Is this disc set a non-RAID disc set? (ie files never get transformed to raid storage) + bool IsNonRaidSet() const {return 1 == size();} + +private: + int mSetID; + unsigned int mBlockSize; +}; + +class _RaidFileController; // compiler warning avoidance + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileController +// Purpose: Manages the configuration of the RaidFile system, handles +// communication with the daemon. +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- +class RaidFileController +{ + friend class _RaidFileController; // to avoid compiler warning +private: + RaidFileController(); + RaidFileController(const RaidFileController &rController); +public: + ~RaidFileController(); + +public: + void Initialise(const std::string& rConfigFilename = + "/etc/boxbackup/raidfile.conf"); + int GetNumDiscSets() {return mSetList.size();} + + // -------------------------------------------------------------------------- + // + // Function + // Name: RaidFileController::GetController() + // Purpose: Gets the one and only controller object. + // Created: 2003/07/08 + // + // -------------------------------------------------------------------------- + static RaidFileController &GetController() {return mController;} + RaidFileDiscSet &GetDiscSet(unsigned int DiscSetNum); + + static std::string DiscSetPathToFileSystemPath(unsigned int DiscSetNum, const std::string &rFilename, int DiscOffset); + +private: + std::vector mSetList; + + static RaidFileController mController; +}; + +#endif // RAIDFILECONTROLLER__H + diff --git a/lib/raidfile/RaidFileException.h b/lib/raidfile/RaidFileException.h new file mode 100644 index 00000000..809d4d5a --- /dev/null +++ b/lib/raidfile/RaidFileException.h @@ -0,0 +1,17 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileException.h +// Purpose: Exception +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEEXCEPTION__H +#define RAIDFILEEXCEPTION__H + +// Compatibility +#include "autogen_RaidFileException.h" + +#endif // RAIDFILEEXCEPTION__H + diff --git a/lib/raidfile/RaidFileException.txt b/lib/raidfile/RaidFileException.txt new file mode 100644 index 00000000..c7ddfcc5 --- /dev/null +++ b/lib/raidfile/RaidFileException.txt @@ -0,0 +1,28 @@ +EXCEPTION RaidFile 2 + +Internal 0 +CantOpenConfigFile 1 The raidfile.conf file is not accessible. Check that it is present in the default location or daemon configuration files point to the correct location. +BadConfigFile 2 +NoSuchDiscSet 3 +CannotOverwriteExistingFile 4 +AlreadyOpen 5 +ErrorOpeningWriteFile 6 +NotOpen 7 +OSError 8 Error when accessing an underlying file. Check file permissions allow files to be read and written in the configured raid directories. +WriteFileOpenOnTransform 9 +WrongNumberOfDiscsInSet 10 There should be three directories in each disc set. +RaidFileDoesntExist 11 Error when accessing a file on the store. Check the store with bbstoreaccounts check. +ErrorOpeningFileForRead 12 +FileIsDamagedNotRecoverable 13 +InvalidRaidFile 14 +DirectoryIncomplete 15 +UnexpectedFileInDirPlace 16 +FileExistsInDirectoryCreation 17 +UnsupportedReadWriteOrClose 18 +CanOnlyGetUsageBeforeCommit 19 +CanOnlyGetFileSizeBeforeCommit 20 +ErrorOpeningWriteFileOnTruncate 21 +FileIsCurrentlyOpenForWriting 22 +RequestedModifyUnreferencedFile 23 Internal error: the server attempted to modify a file which has no references. +RequestedModifyMultiplyReferencedFile 24 Internal error: the server attempted to modify a file which has multiple references. +RequestedDeleteReferencedFile 25 Internal error: the server attempted to delete a file which is still referenced. diff --git a/lib/raidfile/RaidFileRead.cpp b/lib/raidfile/RaidFileRead.cpp new file mode 100644 index 00000000..7b755395 --- /dev/null +++ b/lib/raidfile/RaidFileRead.cpp @@ -0,0 +1,1789 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileRead.cpp +// Purpose: Read Raid like Files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include + +#ifdef HAVE_SYS_UIO_H + #include +#endif + +#ifdef HAVE_DIRENT_H + #include +#endif + +#include +#include +#include +#include +#include + +#include "RaidFileRead.h" +#include "RaidFileException.h" +#include "RaidFileController.h" +#include "RaidFileUtil.h" + +#include "MemLeakFindOn.h" + +#define READ_NUMBER_DISCS_REQUIRED 3 +#define READV_MAX_BLOCKS 64 + +// We want to use POSIX fstat() for now, not the emulated one, because it's +// difficult to rewrite all this code to use HANDLEs instead of ints. + +const RaidFileReadCategory RaidFileRead::OPEN_IN_RECOVERY("OpenInRecovery"); +const RaidFileReadCategory RaidFileRead::IO_ERROR("IoError"); +const RaidFileReadCategory RaidFileRead::RECOVERING_IO_ERROR("RecoverIoError"); + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead_NonRaid +// Purpose: Internal class for reading RaidFiles which haven't been transformed +// into the RAID like form yet. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead_NonRaid : public RaidFileRead +{ +public: + RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle); + virtual ~RaidFileRead_NonRaid(); +private: + RaidFileRead_NonRaid(const RaidFileRead_NonRaid &rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + virtual pos_type GetFileSize() const; + virtual bool StreamDataLeft(); + +private: + int mOSFileHandle; + bool mEOF; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid(int, const std::string &, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_NonRaid::RaidFileRead_NonRaid(int SetNumber, const std::string &Filename, int OSFileHandle) + : RaidFileRead(SetNumber, Filename), + mOSFileHandle(OSFileHandle), + mEOF(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::~RaidFileRead_NonRaid() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_NonRaid::~RaidFileRead_NonRaid() +{ + if(mOSFileHandle != -1) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Read(const void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +int RaidFileRead_NonRaid::Read(void *pBuffer, int NBytes, int Timeout) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Read data + int bytesRead = ::read(mOSFileHandle, pBuffer, NBytes); + if(bytesRead == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Check for EOF + if(bytesRead == 0) + { + mEOF = true; + } + + return bytesRead; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::GetPosition() +// Purpose: Returns current position +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_NonRaid::GetPosition() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Use lseek to find the current file position + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return p; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Seek(pos_type, int) +// Purpose: Seek within the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_NonRaid::Seek(IOStream::pos_type Offset, int SeekType) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Seek... + if(::lseek(mOSFileHandle, Offset, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Not EOF any more + mEOF = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::Close() +// Purpose: Close the file (automatically done by destructor) +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_NonRaid::Close() +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + mOSFileHandle = -1; + mEOF = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::GetFileSize() +// Purpose: Returns file size. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_NonRaid::GetFileSize() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // stat the file + struct stat st; + if(::fstat(mOSFileHandle, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + return st.st_size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::StreamDataLeft() +// Purpose: Any data left? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead_NonRaid::StreamDataLeft() +{ + return !mEOF; +} + +// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead_Raid +// Purpose: Internal class for reading RaidFiles have been transformed. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead_Raid : public RaidFileRead +{ +public: + friend class RaidFileRead; + RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, + int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, + bool LastBlockHasSize); + virtual ~RaidFileRead_Raid(); +private: + RaidFileRead_Raid(const RaidFileRead_Raid &rToCopy); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(IOStream::pos_type Offset, int SeekType); + virtual void Close(); + virtual pos_type GetFileSize() const; + virtual bool StreamDataLeft(); + +private: + int ReadRecovered(void *pBuffer, int NBytes); + void AttemptToRecoverFromIOError(bool Stripe1); + void SetPosition(pos_type FilePosition); + static void MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1); + +private: + int mStripe1Handle; + int mStripe2Handle; + int mParityHandle; + pos_type mFileSize; + unsigned int mBlockSize; + pos_type mCurrentPosition; + char *mRecoveryBuffer; + pos_type mRecoveryBufferStart; + bool mLastBlockHasSize; + bool mEOF; +}; + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid(int, const std::string &, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_Raid::RaidFileRead_Raid(int SetNumber, const std::string &Filename, int Stripe1Handle, int Stripe2Handle, int ParityHandle, pos_type FileSize, unsigned int BlockSize, bool LastBlockHasSize) + : RaidFileRead(SetNumber, Filename), + mStripe1Handle(Stripe1Handle), + mStripe2Handle(Stripe2Handle), + mParityHandle(ParityHandle), + mFileSize(FileSize), + mBlockSize(BlockSize), + mCurrentPosition(0), + mRecoveryBuffer(0), + mRecoveryBufferStart(-1), + mLastBlockHasSize(LastBlockHasSize), + mEOF(false) +{ + // Make sure size of the IOStream::pos_type matches the pos_type used + ASSERT(sizeof(pos_type) >= sizeof(off_t)); + + // Sanity check handles + if(mStripe1Handle != -1 && mStripe2Handle != -1) + { + // Everything is lovely, got two perfect files + } + else + { + // Check we have at least one stripe and a parity file + if((mStripe1Handle == -1 && mStripe2Handle == -1) || mParityHandle == -1) + { + // Should never have got this far + THROW_EXCEPTION(RaidFileException, Internal) + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::~RaidFileRead_Raid() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead_Raid::~RaidFileRead_Raid() +{ + Close(); + if(mRecoveryBuffer != 0) + { + ::free(mRecoveryBuffer); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Read(const void *, int) +// Purpose: Reads bytes from the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +int RaidFileRead_Raid::Read(void *pBuffer, int NBytes, int Timeout) +{ + // How many more bytes could we read? + unsigned int maxRead = mFileSize - mCurrentPosition; + if((unsigned int)NBytes > maxRead) + { + NBytes = maxRead; + } + + // Return immediately if there's nothing to read, and set EOF + if(NBytes == 0) + { + mEOF = true; + return 0; + } + + // Can we use the normal file reading routine? + if(mStripe1Handle == -1 || mStripe2Handle == -1) + { + // File is damaged, try a the recovery read function + return ReadRecovered(pBuffer, NBytes); + } + + // Vectors for reading stuff from the files + struct iovec stripe1Reads[READV_MAX_BLOCKS]; + struct iovec stripe2Reads[READV_MAX_BLOCKS]; + struct iovec *stripeReads[2] = {stripe1Reads, stripe2Reads}; + unsigned int stripeReadsDataSize[2] = {0, 0}; + unsigned int stripeReadsSize[2] = {0, 0}; + int stripeHandles[2] = {mStripe1Handle, mStripe2Handle}; + + // Which block are we doing? + unsigned int currentBlock = mCurrentPosition / mBlockSize; + unsigned int bytesLeftInCurrentBlock = mBlockSize - (mCurrentPosition % mBlockSize); + ASSERT(bytesLeftInCurrentBlock > 0) + unsigned int leftToRead = NBytes; + char *bufferPtr = (char*)pBuffer; + + // Now... add some whole block entries in... + try + { + while(leftToRead > 0) + { + int whichStripe = (currentBlock & 1); + size_t rlen = mBlockSize; + // Adjust if it's the first block + if(bytesLeftInCurrentBlock != 0) + { + rlen = bytesLeftInCurrentBlock; + bytesLeftInCurrentBlock = 0; + } + // Adjust if we're out of bytes + if(rlen > leftToRead) + { + rlen = leftToRead; + } + stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_base = bufferPtr; + stripeReads[whichStripe][stripeReadsSize[whichStripe]].iov_len = rlen; + stripeReadsSize[whichStripe]++; + stripeReadsDataSize[whichStripe] += rlen; + leftToRead -= rlen; + bufferPtr += rlen; + currentBlock++; + + // Read data? + for(int s = 0; s < 2; ++s) + { + if((leftToRead == 0 || stripeReadsSize[s] >= READV_MAX_BLOCKS) && stripeReadsSize[s] > 0) + { + int r = ::readv(stripeHandles[s], stripeReads[s], stripeReadsSize[s]); + if(r == -1) + { + // Bad news... IO error? + if(errno == EIO) + { + // Attempt to recover from this failure + AttemptToRecoverFromIOError((s == 0) /* is stripe 1 */); + // Retry + return Read(pBuffer, NBytes, Timeout); + } + else + { + // Can't do anything, throw + THROW_EXCEPTION(RaidFileException, OSError) + } + } + else if(r != (int)stripeReadsDataSize[s]) + { + // Got the file sizes wrong/logic error! + THROW_EXCEPTION(RaidFileException, Internal) + } + stripeReadsSize[s] = 0; + stripeReadsDataSize[s] = 0; + } + } + } + } + catch(...) + { + // Get file pointers to right place (to meet exception safe stuff) + SetPosition(mCurrentPosition); + + throw; + } + + // adjust current position + mCurrentPosition += NBytes; + + return NBytes; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::MoveDamagedFileAlertDaemon(bool) +// Purpose: Moves a file into the damaged directory, and alerts the Daemon to recover it properly later. +// Created: 2003/07/22 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::MoveDamagedFileAlertDaemon(int SetNumber, const std::string &Filename, bool Stripe1) +{ + // Move the dodgy file away + // Get the controller and the disc set we're on + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + // Start disc + int startDisc = rdiscSet.GetSetNumForWriteFiles(Filename); + int errOnDisc = (startDisc + (Stripe1?0:1)) % READ_NUMBER_DISCS_REQUIRED; + + // Make a munged filename for renaming + std::string mungeFn(Filename + RAIDFILE_EXTENSION); + std::string awayName; + for(std::string::const_iterator i = mungeFn.begin(); i != mungeFn.end(); ++i) + { + char c = (*i); + if(c == DIRECTORY_SEPARATOR_ASCHAR) + { + awayName += '_'; + } + else if(c == '_') + { + awayName += "__"; + } + else + { + awayName += c; + } + } + // Make sure the error files directory exists + std::string dirname(rdiscSet[errOnDisc] + DIRECTORY_SEPARATOR ".raidfile-unreadable"); + int mdr = ::mkdir(dirname.c_str(), 0750); + if(mdr != 0 && errno != EEXIST) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + // Attempt to rename the file there -- ignore any return code here, as it's dubious anyway + std::string errorFile(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, errOnDisc)); + ::rename(errorFile.c_str(), (dirname + DIRECTORY_SEPARATOR_ASCHAR + awayName).c_str()); + + // TODO: Inform the recovery daemon +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::AttemptToRecoverFromIOError(bool) +// Purpose: Attempt to recover from an IO error, setting up to read from parity instead. +// Will exception if this isn't possible. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::AttemptToRecoverFromIOError(bool Stripe1) +{ + BOX_LOG_CATEGORY(Log::WARNING, RaidFileRead::RECOVERING_IO_ERROR, + "Attempting to recover from I/O error: " << mSetNumber << + " " << mFilename << ", on stripe " << (Stripe1?1:2)); + + // Close offending file + if(Stripe1) + { + if(mStripe1Handle != -1) + { + ::close(mStripe1Handle); + mStripe1Handle = -1; + } + } + else + { + if(mStripe2Handle != -1) + { + ::close(mStripe2Handle); + mStripe2Handle = -1; + } + } + + // Check... + ASSERT((Stripe1?mStripe2Handle:mStripe1Handle) != -1); + + // Get rid of the damaged file + MoveDamagedFileAlertDaemon(mSetNumber, mFilename, Stripe1); + + // Get the controller and the disc set we're on + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + // Start disc + int startDisc = rdiscSet.GetSetNumForWriteFiles(mFilename); + + // Mark as nothing in recovery buffer + mRecoveryBufferStart = -1; + + // Seek to zero on the remaining file -- get to nice state + if(::lseek(Stripe1?mStripe2Handle:mStripe1Handle, 0, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Open the parity file + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + mParityHandle = ::open(parityFilename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(mParityHandle == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Work out whether or not there's a size XORed into the last block + unsigned int bytesInLastTwoBlocks = mFileSize % (mBlockSize * 2); + if(bytesInLastTwoBlocks > mBlockSize && bytesInLastTwoBlocks < ((mBlockSize * 2) - sizeof(FileSizeType))) + { + // Yes, there's something to XOR in the last block + mLastBlockHasSize = true; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::ReadRecovered(const void *, int) +// Purpose: Reads data recreating from the parity stripe +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +int RaidFileRead_Raid::ReadRecovered(void *pBuffer, int NBytes) +{ + // Note: NBytes has been adjusted to definately be a range + // inside the given file length. + + // Make sure a buffer is allocated + if(mRecoveryBuffer == 0) + { + mRecoveryBuffer = (char*)::malloc(mBlockSize * 2); + if(mRecoveryBuffer == 0) + { + throw std::bad_alloc(); + } + } + + // Which stripe? + int stripe = (mStripe1Handle != -1)?mStripe1Handle:mStripe2Handle; + if(stripe == -1) + { + // Not enough file handles around + THROW_EXCEPTION(RaidFileException, FileIsDamagedNotRecoverable) + } + + char *outptr = (char*)pBuffer; + int bytesToGo = NBytes; + + pos_type preservedCurrentPosition = mCurrentPosition; + + try + { + // Start offset within buffer + int offset = (mCurrentPosition - mRecoveryBufferStart); + // Let's go! + while(bytesToGo > 0) + { + int bytesLeftInBuffer = 0; + if(mRecoveryBufferStart != -1) + { + bytesLeftInBuffer = (mRecoveryBufferStart + (mBlockSize*2)) - mCurrentPosition; + ASSERT(bytesLeftInBuffer >= 0); + } + + // How many bytes can be copied out? + int toCopy = bytesLeftInBuffer; + if(toCopy > bytesToGo) toCopy = bytesToGo; + //printf("offset = %d, tocopy = %d, bytestogo = %d, leftinbuffer = %d\n", (int)offset, toCopy, bytesToGo, bytesLeftInBuffer); + if(toCopy > 0) + { + for(int l = 0; l < toCopy; ++l) + { + *(outptr++) = mRecoveryBuffer[offset++]; + } + bytesToGo -= toCopy; + mCurrentPosition += toCopy; + } + + // Load in the next buffer? + if(bytesToGo > 0) + { + // Calculate the blocks within the file that are needed to be loaded. + pos_type fileBlock = mCurrentPosition / (mBlockSize * 2); + // Is this the last block + bool isLastBlock = (fileBlock == (mFileSize / (mBlockSize * 2))); + + // Need to reposition file pointers? + if(mRecoveryBufferStart == -1) + { + // Yes! + // And the offset from which to read it + pos_type filePos = fileBlock * mBlockSize; + // Then seek + if(::lseek(stripe, filePos, SEEK_SET) == -1 + || ::lseek(mParityHandle, filePos, SEEK_SET) == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Load a block from each file, getting the ordering the right way round + int r1 = ::read((mStripe1Handle != -1)?stripe:mParityHandle, mRecoveryBuffer, mBlockSize); + int r2 = ::read((mStripe1Handle != -1)?mParityHandle:stripe, mRecoveryBuffer + mBlockSize, mBlockSize); + if(r1 == -1 || r2 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // error checking and manipulation + if(isLastBlock) + { + // Allow not full reads, and append zeros if necessary to fill the space. + int r1zeros = mBlockSize - r1; + if(r1zeros > 0) + { + ::memset(mRecoveryBuffer + r1, 0, r1zeros); + } + int r2zeros = mBlockSize - r2; + if(r2zeros > 0) + { + ::memset(mRecoveryBuffer + mBlockSize + r2, 0, r2zeros); + } + + // if it's got the file size in it, XOR it off + if(mLastBlockHasSize) + { + int sizeXorOffset = (mBlockSize - sizeof(FileSizeType)) + ((mStripe1Handle != -1)?mBlockSize:0); + *((FileSizeType*)(mRecoveryBuffer + sizeXorOffset)) ^= box_ntoh64(mFileSize); + } + } + else + { + // Must have got a full block, otherwise things are a bit bad here. + if(r1 != (int)mBlockSize || r2 != (int)mBlockSize) + { + THROW_EXCEPTION(RaidFileException, InvalidRaidFile) + } + } + + // Go XORing! + unsigned int *b1 = (unsigned int*)mRecoveryBuffer; + unsigned int *b2 = (unsigned int *)(mRecoveryBuffer + mBlockSize); + if(mStripe1Handle == -1) + { + b1 = b2; + b2 = (unsigned int*)mRecoveryBuffer; + } + for(int x = ((mBlockSize/sizeof(unsigned int)) - 1); x >= 0; --x) + { + *b2 = (*b1) ^ (*b2); + ++b1; + ++b2; + } + + // New block location + mRecoveryBufferStart = fileBlock * (mBlockSize * 2); + + // New offset withing block + offset = (mCurrentPosition - mRecoveryBufferStart); + ASSERT(offset >= 0); + } + } + } + catch(...) + { + // Change variables so 1) buffer is invalidated and 2) the file will be seeked properly the next time round + mRecoveryBufferStart = -1; + mCurrentPosition = preservedCurrentPosition; + throw; + } + + return NBytes; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::GetPosition() +// Purpose: Returns current position +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead_Raid::GetPosition() const +{ + return mCurrentPosition; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Seek(RaidFileRead::pos_type, bool) +// Purpose: Seek within the file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::Seek(IOStream::pos_type Offset, int SeekType) +{ + pos_type newpos = mCurrentPosition; + switch(SeekType) + { + case IOStream::SeekType_Absolute: + newpos = Offset; + break; + + case IOStream::SeekType_Relative: + newpos += Offset; + break; + + case IOStream::SeekType_End: + newpos = mFileSize + Offset; + break; + + default: + THROW_EXCEPTION(CommonException, IOStreamBadSeekType) + } + + if(newpos != mCurrentPosition) + { + SetPosition(newpos); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::SetPosition(pos_type) +// Purpose: Move the file pointers +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::SetPosition(pos_type FilePosition) +{ + if(FilePosition > mFileSize) + { + FilePosition = mFileSize; + } + + if(mStripe1Handle != -1 && mStripe2Handle != -1) + { + // right then... which block is it in? + pos_type block = FilePosition / mBlockSize; + pos_type offset = FilePosition % mBlockSize; + + // Calculate offsets for each file + pos_type basepos = (block / 2) * mBlockSize; + pos_type s1p, s2p; + if((block & 1) == 0) + { + s1p = basepos + offset; + s2p = basepos; + } + else + { + s1p = basepos + mBlockSize; + s2p = basepos + offset; + } + // Note: lseek isn't in the man pages to return EIO, but assuming that it can return this, + // as it calls various OS bits and returns their error codes, and those fns look like they might. + if(::lseek(mStripe1Handle, s1p, SEEK_SET) == -1) + { + if(errno == EIO) + { + BOX_LOG_CATEGORY(Log::ERROR, RaidFileRead::IO_ERROR, + "I/O error when seeking in set " << mSetNumber << + ": " << mFilename << " (to " << FilePosition << + "), " << "stripe 1"); + // Attempt to recover + AttemptToRecoverFromIOError(true /* is stripe 1 */); + ASSERT(mStripe1Handle == -1); + // Retry + SetPosition(FilePosition); + return; + } + else + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + if(::lseek(mStripe2Handle, s2p, SEEK_SET) == -1) + { + if(errno == EIO) + { + BOX_LOG_CATEGORY(Log::ERROR, RaidFileRead::IO_ERROR, + "I/O error when seeking in set " << mSetNumber << + ": " << mFilename << " (to " << FilePosition << + "), " << "stripe 2"); + // Attempt to recover + AttemptToRecoverFromIOError(false /* is stripe 2 */); + ASSERT(mStripe2Handle == -1); + // Retry + SetPosition(FilePosition); + return; + } + else + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Store position + mCurrentPosition = FilePosition; + } + else + { + // Simply store, and mark the recovery buffer invalid + mCurrentPosition = FilePosition; + mRecoveryBufferStart = -1; + } + + // not EOF any more + mEOF = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::Close() +// Purpose: Close the file (automatically done by destructor) +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileRead_Raid::Close() +{ + if(mStripe1Handle != -1) + { + ::close(mStripe1Handle); + mStripe1Handle = -1; + } + if(mStripe2Handle != -1) + { + ::close(mStripe2Handle); + mStripe2Handle = -1; + } + if(mParityHandle != -1) + { + ::close(mParityHandle); + mParityHandle = -1; + } + + mEOF = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_NonRaid::StreamDataLeft() +// Purpose: Any data left? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead_Raid::StreamDataLeft() +{ + return !mEOF; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead_Raid::GetFileSize() +// Purpose: Returns file size. +// Created: 2003/07/14 +// +// -------------------------------------------------------------------------- +RaidFileRead::pos_type RaidFileRead_Raid::GetFileSize() const +{ + return mFileSize; +} + + +// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::RaidFileRead(int, const std::string &) +// Purpose: Constructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::RaidFileRead(int SetNumber, const std::string &Filename) + : mSetNumber(SetNumber), + mFilename(Filename) +{ +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::~RaidFileRead() +// Purpose: Destructor +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +RaidFileRead::~RaidFileRead() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::Open(int, const std::string &, int) +// Purpose: Opens a RaidFile for reading. +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +std::auto_ptr RaidFileRead::Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID, int BufferSizeHint) +{ + // See what's available... + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + if(READ_NUMBER_DISCS_REQUIRED != rdiscSet.size() && 1 != rdiscSet.size()) // allow non-RAID configurations + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + + // See if the file exists + int startDisc = 0, existingFiles = 0; + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, Filename, &startDisc, &existingFiles, pRevisionID); + if(existance == RaidFileUtil::NoFile) + { + THROW_FILE_ERROR("Expected raidfile does not exist", + Filename, RaidFileException, RaidFileDoesntExist); + } + else if(existance == RaidFileUtil::NonRaid) + { + // Simple non-RAID file so far... + + // Get the filename for the write file + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, Filename)); + + // Attempt to open + int osFileHandle = ::open(writeFilename.c_str(), + O_RDONLY | O_BINARY, 0); + if(osFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, ErrorOpeningFileForRead) + } + + // Return a read object for this file + try + { + return std::auto_ptr(new RaidFileRead_NonRaid(SetNumber, Filename, osFileHandle)); + } + catch(...) + { + ::close(osFileHandle); + throw; + } + } + else if(existance == RaidFileUtil::AsRaid + || ((existingFiles & RaidFileUtil::Stripe1Exists) && (existingFiles & RaidFileUtil::Stripe2Exists))) + { + if(existance != RaidFileUtil::AsRaid) + { + BOX_ERROR("Opening " << SetNumber << " " << + Filename << " in normal mode, but " + "parity file doesn't exist"); + // TODO: Alert recovery daemon + } + + // Open the two stripe files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + int stripe1 = -1; + int stripe1errno = 0; + int stripe2 = -1; + int stripe2errno = 0; + + try + { + // Open stripe1 + stripe1 = ::open(stripe1Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe1 == -1) + { + stripe1errno = errno; + } + // Open stripe2 + stripe2 = ::open(stripe2Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe2 == -1) + { + stripe2errno = errno; + } + + 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); + } + + // stat stripe 1 to find ('half' of) length... + struct stat st; + if(::fstat(stripe1, &st) != 0) + { + stripe1errno = errno; + } + pos_type length = st.st_size; + + // stat stripe2 to find (other 'half' of) length... + if(::fstat(stripe2, &st) != 0) + { + stripe2errno = errno; + } + length += st.st_size; + + // Handle errors + 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); + } + + // Make a nice object to represent this file + return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, -1, length, rdiscSet.GetBlockSize(), false /* actually we don't know */)); + } + catch(...) + { + // Close open files + if(stripe1 != -1) + { + ::close(stripe1); + stripe1 = -1; + } + if(stripe2 != -1) + { + ::close(stripe2); + stripe2 = -1; + } + + // Now... maybe we can try again with one less file? + bool oktotryagain = true; + if(stripe1errno == EIO) + { + BOX_LOG_CATEGORY(Log::ERROR, + RaidFileRead::RECOVERING_IO_ERROR, "I/O error " + "on opening " << SetNumber << " " << Filename << + " stripe 1, trying recovery mode"); + RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, true /* is stripe 1 */); + + existingFiles = existingFiles & ~RaidFileUtil::Stripe1Exists; + existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) + ?RaidFileUtil::AsRaidWithMissingNotRecoverable + :RaidFileUtil::AsRaidWithMissingReadable; + } + else if(stripe1errno != 0) + { + oktotryagain = false; + } + + if(stripe2errno == EIO) + { + BOX_LOG_CATEGORY(Log::ERROR, + RaidFileRead::RECOVERING_IO_ERROR, "I/O error " + "on opening " << SetNumber << " " << Filename << + " stripe 2, trying recovery mode"); + RaidFileRead_Raid::MoveDamagedFileAlertDaemon(SetNumber, Filename, false /* is stripe 2 */); + + existingFiles = existingFiles & ~RaidFileUtil::Stripe2Exists; + existance = (existance == RaidFileUtil::AsRaidWithMissingReadable) + ?RaidFileUtil::AsRaidWithMissingNotRecoverable + :RaidFileUtil::AsRaidWithMissingReadable; + } + else if(stripe2errno != 0) + { + oktotryagain = false; + } + + if(!oktotryagain) + { + throw; + } + } + } + + if(existance == RaidFileUtil::AsRaidWithMissingReadable) + { + BOX_LOG_CATEGORY(Log::ERROR, RaidFileRead::OPEN_IN_RECOVERY, + "Attempting to open RAID file " << SetNumber << + " " << Filename << " in recovery mode (stripe " << + ((existingFiles & RaidFileUtil::Stripe1Exists)?1:2) << + " present)"); + + // Generate the filenames of all the lovely files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (0 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (1 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, Filename, (2 + startDisc) % READ_NUMBER_DISCS_REQUIRED)); + + int stripe1 = -1; + int stripe2 = -1; + int parity = -1; + + try + { + // Open stripe1? + if(existingFiles & RaidFileUtil::Stripe1Exists) + { + stripe1 = ::open(stripe1Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe1 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + // Open stripe2? + if(existingFiles & RaidFileUtil::Stripe2Exists) + { + stripe2 = ::open(stripe2Filename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(stripe2 == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + // Open parity + parity = ::open(parityFilename.c_str(), + O_RDONLY | O_BINARY, 0555); + if(parity == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Find the length. This is slightly complex. + unsigned int blockSize = rdiscSet.GetBlockSize(); + pos_type length = 0; + + // The easy one... if the parity file is of an integral block size + sizeof(FileSizeType) + // then it's stored at the end of the parity file + struct stat st; + if(::fstat(parity, &st) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + pos_type paritySize = st.st_size; + FileSizeType parityLastData = 0; + bool parityIntegralPlusOffT = ((paritySize % blockSize) == sizeof(FileSizeType)); + if(paritySize >= static_cast(sizeof(parityLastData)) && (parityIntegralPlusOffT || stripe1 != -1)) + { + // Seek to near the end + ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... + if(::lseek(parity, -8 /*(0 - sizeof(FileSizeType))*/, SEEK_END) == -1) + { + THROW_SYS_FILE_ERROR("Failed to seek " + "in parity RaidFile", + parityFilename, + RaidFileException, OSError); + } + // Read it in + if(::read(parity, &parityLastData, sizeof(parityLastData)) != sizeof(parityLastData)) + { + 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); + } + } + + bool lastBlockHasSize = false; + if(parityIntegralPlusOffT) + { + // Wonderful! Have the value + length = box_ntoh64(parityLastData); + } + else + { + // Have to resort to more devious means. + if(existingFiles & RaidFileUtil::Stripe1Exists) + { + // Procedure for stripe 1 existence... + // Get size of stripe1. + // If this is not an integral block size, then size can use this + // to work out the size of the file. + // Otherwise, read in the end of the last block, and use a bit of XORing + // to get the size from the FileSizeType value at end of the file. + if(::fstat(stripe1, &st) != 0) + { + THROW_SYS_FILE_ERROR("Failed to " + "stat RaidFile stripe 1", + stripe1Filename, + RaidFileException, OSError); + } + pos_type stripe1Size = st.st_size; + // Is size integral? + if((stripe1Size % ((pos_type)blockSize)) != 0) + { + // No, so know the size. + length = stripe1Size + ((stripe1Size / blockSize) * blockSize); + } + else + { + // Must read the last bit of data from the block and XOR. + FileSizeType stripe1LastData = 0; // initialise to zero, as we may not read everything from it + + // Work out how many bytes to read + int btr = 0; // bytes to read off end + unsigned int lbs = stripe1Size % blockSize; + if(lbs == 0 && stripe1Size > 0) + { + // integral size, need the entire bit + btr = sizeof(FileSizeType); + } + else if(lbs > (blockSize - sizeof(FileSizeType))) + { + btr = lbs - (blockSize - sizeof(FileSizeType)); + } + + // Seek to near the end + if(btr > 0) + { + ASSERT(sizeof(FileSizeType) == 8); // compiler bug (I think) prevents from using 0 - sizeof(FileSizeType)... + ASSERT(btr <= (int)sizeof(FileSizeType)); + if(::lseek(stripe1, 0 - btr, SEEK_END) == -1) + { + THROW_SYS_FILE_ERROR("Failed to " + "seek in RaidFile stripe 1", + stripe1Filename, + RaidFileException, OSError); + } + // Read it in + if(::read(stripe1, &stripe1LastData, btr) != btr) + { + THROW_SYS_FILE_ERROR("Failed to " + "read RaidFile stripe 1", + stripe1Filename, + RaidFileException, OSError); + } + // Set back to beginning of file + if(::lseek(stripe1, 0, SEEK_SET) == -1) + { + THROW_SYS_FILE_ERROR("Failed to " + "seek in RaidFile stripe 1", + stripe1Filename, + RaidFileException, OSError); + } + } + // Lovely! + length = stripe1LastData ^ parityLastData; + // Convert to host byte order + length = box_ntoh64(length); + ASSERT(length <= (paritySize + stripe1Size)); + // Mark is as having this to aid code later + lastBlockHasSize = true; + } + } + else + { + ASSERT(existingFiles & RaidFileUtil::Stripe2Exists); + } + + if(existingFiles & RaidFileUtil::Stripe2Exists) + { + // Get size of stripe2 file + if(::fstat(stripe2, &st) != 0) + { + THROW_SYS_FILE_ERROR("Failed to " + "stat RaidFile stripe 2", + stripe2Filename, + RaidFileException, OSError); + } + pos_type stripe2Size = st.st_size; + + // Is it an integral size? + if(stripe2Size % blockSize != 0) + { + // No. Working out the size is easy. + length = stripe2Size + (((stripe2Size / blockSize)+1) * blockSize); + // Got last block size in there? + if((stripe2Size % blockSize) <= static_cast((blockSize - sizeof(pos_type)))) + { + // Yes... + lastBlockHasSize = true; + } + } + else + { + // Yes. So we need to compare with the parity file to get a clue... + pos_type stripe2Blocks = stripe2Size / blockSize; + pos_type parityBlocks = paritySize / blockSize; + if(stripe2Blocks == parityBlocks) + { + // Same size, so stripe1 must be the same size + length = (stripe2Blocks * 2) * blockSize; + } + else + { + // Different size, so stripe1 must be one block bigger + ASSERT(stripe2Blocks < parityBlocks); + length = ((stripe2Blocks * 2)+1) * blockSize; + } + + // Then... add in the extra bit of the parity length + unsigned int lastBlockSize = paritySize % blockSize; + length += lastBlockSize; + } + } + else + { + ASSERT(existingFiles & RaidFileUtil::Stripe1Exists); + } + } + + // Create a lovely object to return + return std::auto_ptr(new RaidFileRead_Raid(SetNumber, Filename, stripe1, stripe2, parity, length, blockSize, lastBlockHasSize)); + } + catch(...) + { + // Close open files + if(stripe1 != -1) + { + ::close(stripe1); + stripe1 = -1; + } + if(stripe2 != -1) + { + ::close(stripe2); + stripe2 = -1; + } + if(parity != -1) + { + ::close(parity); + parity = -1; + } + throw; + } + } + + THROW_FILE_ERROR("Failed to recover RaidFile", Filename, + RaidFileException, FileIsDamagedNotRecoverable); + + // Avoid compiler warning -- it'll never get here... + return std::auto_ptr(); +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::DirectoryExists(int, const std::string &) +// Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::DirectoryExists(int SetNumber, const std::string &rDirName) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + return DirectoryExists(rdiscSet, rDirName); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::DirectoryExists(const RaidFileDiscSet &, const std::string &) +// Purpose: Returns true if the directory exists. Throws exception if it's partially in existence. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName) +{ + // For each directory, test to see if it exists + unsigned int nexist = 0; + for(unsigned int l = 0; l < rSet.size(); ++l) + { + // build name + std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // check for existence + EMU_STRUCT_STAT st; + if(EMU_STAT(dn.c_str(), &st) == 0) + { + // Directory? + if(st.st_mode & S_IFDIR) + { + // yes + nexist++; + } + else + { + // No. It's a file. Bad! + THROW_FILE_ERROR("Expected a directory, " + "found something else", dn, + RaidFileException, + UnexpectedFileInDirPlace); + } + } + else + { + // Was it a non-exist error? + if(errno != ENOENT) + { + // No. Bad things. + THROW_SYS_FILE_ERROR("Failed to check for " + "existing RaidFile directory", dn, + RaidFileException, OSError); + } + } + } + + // Were all of them found? + if(nexist == 0) + { + // None. + return false; + } + else if(nexist == rSet.size()) + { + // All + return true; + } + + // Some exist. We don't like this -- it shows something bad happened before + // TODO: notify recovery daemon + THROW_EXCEPTION(RaidFileException, DirectoryIncomplete) + return false; // avoid compiler warning +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::FileExists(int, const std::string &, int64_t *) +// Purpose: Does a Raid file exist? Optionally return a revision number, which is +// unique to this saving of the file. (revision number may change +// after transformation to RAID -- so only use for cache control, +// not detecting changes to content). +// Created: 2003/09/02 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + return RaidFileUtil::RaidFileExists(rdiscSet, rFilename, 0, 0, pRevisionID) != RaidFileUtil::NoFile; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ReadDirectoryContents(int, const std::string &, int, std::vector &) +// Purpose: Read directory contents, returning whether or not all entries are likely to be readable or not +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput) +{ + // Remove anything in the vector to begin with. + rOutput.clear(); + + // Controller and set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + + // Collect the directory listings + std::map counts; + + unsigned int numDiscs = rdiscSet.size(); + + for(unsigned int l = 0; l < numDiscs; ++l) + { + // build name + std::string dn(rdiscSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // read the contents... + DIR *dirHandle = 0; + try + { + dirHandle = ::opendir(dn.c_str()); + if(dirHandle == 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + struct dirent *en = 0; + while((en = ::readdir(dirHandle)) != 0) + { + if(en->d_name[0] == '.' && + (en->d_name[1] == '\0' || (en->d_name[1] == '.' && en->d_name[2] == '\0'))) + { + // ignore, it's . or .. + continue; + } + + // Entry... + std::string name; + unsigned int countToAdd = 1; + + // stat the file to find out what type it is +#ifdef HAVE_VALID_DIRENT_D_TYPE + if(DirReadType == DirReadType_FilesOnly && en->d_type == DT_REG) +#else + EMU_STRUCT_STAT st; + std::string fullName(dn + DIRECTORY_SEPARATOR + en->d_name); + if(EMU_LSTAT(fullName.c_str(), &st) != 0) + { + THROW_SYS_FILE_ERROR("Failed to stat", + fullName, RaidFileException, + OSError); + } + + if(DirReadType == DirReadType_FilesOnly && (st.st_mode & S_IFDIR) == 0) +#endif + { + // File. Complex, need to check the extension + int dot = -1; + int p = 0; + while(en->d_name[p] != '\0') + { + if(en->d_name[p] == '.') + { + // store location of dot + dot = p; + } + ++p; + } + // p is length of string + if(dot != -1 && ((p - dot) == 3 || (p - dot) == 4) + && en->d_name[dot+1] == 'r' && en->d_name[dot+2] == 'f' + && (en->d_name[dot+3] == 'w' || en->d_name[dot+3] == '\0')) + { + // so has right extension + name.assign(en->d_name, dot); /* get name up to last . */ + // Was it a write file (which counts as everything) + if(en->d_name[dot+3] == 'w') + { + countToAdd = numDiscs; + } + } + } +#ifdef HAVE_VALID_DIRENT_D_TYPE + if(DirReadType == DirReadType_DirsOnly && en->d_type == DT_DIR) +#else + if(DirReadType == DirReadType_DirsOnly && (st.st_mode & S_IFDIR)) +#endif + { + // Directory, and we want directories + name = en->d_name; + } + // Eligable for entry? + if(!name.empty()) + { + // add to map... + std::map::iterator i = counts.find(name); + if(i != counts.end()) + { + // add to count + i->second += countToAdd; + } + else + { + // insert into map + counts[name] = countToAdd; + } + } + } + + if(::closedir(dirHandle) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + dirHandle = 0; + } + catch(...) + { + if(dirHandle != 0) + { + ::closedir(dirHandle); + } + throw; + } + } + + // Now go through the map, adding in entries + bool everythingReadable = true; + + for(std::map::const_iterator i = counts.begin(); i != counts.end(); ++i) + { + if(i->second < (numDiscs - 1)) + { + // Too few discs to be confident of reading everything + everythingReadable = false; + } + + // Add name to vector + rOutput.push_back(i->first); + } + + return everythingReadable; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::Write(const void *, int) +// Purpose: Not support, throws exception +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void RaidFileRead::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::StreamClosed() +// Purpose: Never any data to write +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileRead::StreamClosed() +{ + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::BytesLeftToRead() +// Purpose: Can tell how many bytes there are to go +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead::BytesLeftToRead() +{ + return GetFileSize() - GetPosition(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileRead::GetDiscUsageInBlocks() +// Purpose: Return how many blocks are used. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileRead::GetDiscUsageInBlocks() +{ + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + return RaidFileUtil::DiscUsageInBlocks(GetFileSize(), rdiscSet); +} + +std::string RaidFileRead::ToString() const +{ + return std::string("RaidFile ") + mFilename; +} diff --git a/lib/raidfile/RaidFileRead.h b/lib/raidfile/RaidFileRead.h new file mode 100644 index 00000000..a3c792d0 --- /dev/null +++ b/lib/raidfile/RaidFileRead.h @@ -0,0 +1,87 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileRead.h +// Purpose: Read Raid like Files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEREAD__H +#define RAIDFILEREAD__H + +#include +#include +#include +#include + +#include "IOStream.h" +#include "Logging.h" + +class RaidFileDiscSet; + +class RaidFileReadCategory : public Log::Category +{ + public: + RaidFileReadCategory(const std::string& name) + : Log::Category(std::string("RaidFileRead/") + name) + { } +}; + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileRead +// Purpose: Read RAID like files +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +class RaidFileRead : public IOStream +{ +protected: + RaidFileRead(int SetNumber, const std::string &Filename); +public: + virtual ~RaidFileRead(); +private: + RaidFileRead(const RaidFileRead &rToCopy); + +public: + // Open a raid file + static std::auto_ptr Open(int SetNumber, const std::string &Filename, int64_t *pRevisionID = 0, int BufferSizeHint = 4096); + + // Extra info + virtual pos_type GetFileSize() const = 0; + + // Utility functions + static bool FileExists(int SetNumber, const std::string &rFilename, int64_t *pRevisionID = 0); + static bool DirectoryExists(const RaidFileDiscSet &rSet, const std::string &rDirName); + static bool DirectoryExists(int SetNumber, const std::string &rDirName); + enum + { + DirReadType_FilesOnly = 0, + DirReadType_DirsOnly = 1 + }; + static bool ReadDirectoryContents(int SetNumber, const std::string &rDirName, int DirReadType, std::vector &rOutput); + + // Common IOStream interface implementation + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual bool StreamClosed(); + virtual pos_type BytesLeftToRead(); + + pos_type GetDiscUsageInBlocks(); + std::string ToString() const; + + typedef int64_t FileSizeType; + + static const RaidFileReadCategory OPEN_IN_RECOVERY; + static const RaidFileReadCategory IO_ERROR; + static const RaidFileReadCategory RECOVERING_IO_ERROR; + +protected: + int mSetNumber; + std::string mFilename; +}; + +#endif // RAIDFILEREAD__H + diff --git a/lib/raidfile/RaidFileUtil.cpp b/lib/raidfile/RaidFileUtil.cpp new file mode 100644 index 00000000..48625997 --- /dev/null +++ b/lib/raidfile/RaidFileUtil.cpp @@ -0,0 +1,202 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileUtil.cpp +// Purpose: Utilities for raid files +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "RaidFileUtil.h" +#include "FileModificationTime.h" +#include "RaidFileRead.h" // for type definition + +#include "MemLeakFindOn.h" + +int64_t adjust_timestamp(int64_t timestamp, size_t file_size) +{ +#ifndef BOX_RELEASE_BUILD + // Remove the microseconds part of the timestamp, + // to simulate filesystem with 1-second timestamp + // resolution, e.g. MacOS X HFS, old Linuxes. + // Otherwise it's easy to write tests that rely + // on more accurate timestamps, and pass on + // platforms that have them, and fail on others. + timestamp -= (timestamp % MICRO_SEC_IN_SEC); +#endif + + // The resolution of timestamps may be very + // low, e.g. 1 second. So add the size to it + // to give a bit more chance of it changing. + // TODO: Make this better. + timestamp += file_size; + + return timestamp; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileUtil::RaidFileExists(RaidFileDiscSet &, +// const std::string &, int *, int *, int64_t *) +// Purpose: Check to see the state of a RaidFile on disc +// (doesn't look at contents, just at existence of +// files) +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +RaidFileUtil::ExistType RaidFileUtil::RaidFileExists(RaidFileDiscSet &rDiscSet, + const std::string &rFilename, int *pStartDisc, int *pExistingFiles, + int64_t *pRevisionID) +{ + if(pExistingFiles) + { + *pExistingFiles = 0; + } + + EMU_STRUCT_STAT st; + + // check various files + int startDisc = 0; + { + std::string writeFile(RaidFileUtil::MakeWriteFileName(rDiscSet, rFilename, &startDisc)); + if(pStartDisc) + { + *pStartDisc = startDisc; + } + if(EMU_STAT(writeFile.c_str(), &st) == 0) + { + // write file exists, use that + + // Get unique ID + if(pRevisionID != 0) + { + *pRevisionID = FileModificationTime(st); + *pRevisionID = adjust_timestamp(*pRevisionID, st.st_size); + } + + // return non-raid file + return NonRaid; + } + } + + // Now see how many of the raid components exist + int64_t revisionID = 0; + int setSize = rDiscSet.size(); + int rfCount = 0; + + // TODO: replace this with better linux revision ID detection + int64_t revisionIDplus = 0; + + for(int f = 0; f < setSize; ++f) + { + std::string componentFile(RaidFileUtil::MakeRaidComponentName(rDiscSet, rFilename, (f + startDisc) % setSize)); + if(EMU_STAT(componentFile.c_str(), &st) == 0) + { + // Component file exists, add to count + rfCount++; + // Set flags for existance? + if(pExistingFiles) + { + (*pExistingFiles) |= (1 << f); + } + // Revision ID + if(pRevisionID != 0) + { + int64_t rid = FileModificationTime(st); + if(rid > revisionID) revisionID = rid; + revisionIDplus += st.st_size; + } + } + } + if(pRevisionID != 0) + { + revisionID = adjust_timestamp(revisionID, revisionIDplus); + (*pRevisionID) = revisionID; + } + + // Return a status based on how many parts are available + if(rfCount == setSize) + { + return AsRaid; + } + else if((setSize > 1) && rfCount == (setSize - 1)) + { + return AsRaidWithMissingReadable; + } + else if(rfCount > 0) + { + return AsRaidWithMissingNotRecoverable; + } + + return NoFile; // Obviously doesn't exist +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileUtil::DiscUsageInBlocks(int64_t, const RaidFileDiscSet &) +// Purpose: Returns the size of the file in blocks, given the file size and disc set +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +int64_t RaidFileUtil::DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet) +{ + // Get block size + int blockSize = rDiscSet.GetBlockSize(); + + // OK... so as the size of the file is always sizes of stripe1 + stripe2, we can + // do a very simple calculation for the main data. + int64_t blocks = (FileSize + (((int64_t)blockSize) - 1)) / ((int64_t)blockSize); + + // It's just that simple calculation for non-RAID disc sets + if(rDiscSet.IsNonRaidSet()) + { + return blocks; + } + + // It's the parity which is mildly complex. + // First of all, add in size for all but the last two blocks. + int64_t parityblocks = (FileSize / ((int64_t)blockSize)) / 2; + blocks += parityblocks; + + // Work out how many bytes are left + int bytesOver = (int)(FileSize - (parityblocks * ((int64_t)(blockSize*2)))); + + // Then... (let compiler optimise this out) + if(bytesOver == 0) + { + // Extra block for the size info + blocks++; + } + else if(bytesOver == sizeof(RaidFileRead::FileSizeType)) + { + // For last block of parity, plus the size info + blocks += 2; + } + else if(bytesOver < blockSize) + { + // Just want the parity block + blocks += 1; + } + else if(bytesOver == blockSize || bytesOver >= ((blockSize*2)-((int)sizeof(RaidFileRead::FileSizeType)))) + { + // Last block, plus size info + blocks += 2; + } + else + { + // Just want parity block + blocks += 1; + } + + return blocks; +} + + diff --git a/lib/raidfile/RaidFileUtil.h b/lib/raidfile/RaidFileUtil.h new file mode 100644 index 00000000..a581047c --- /dev/null +++ b/lib/raidfile/RaidFileUtil.h @@ -0,0 +1,97 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileUtil.h +// Purpose: Utilities for the raid file classes +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEUTIL__H +#define RAIDFILEUTIL__H + +#include "RaidFileController.h" +#include "RaidFileException.h" + +// note: these are hardcoded into the directory searching code +#define RAIDFILE_EXTENSION ".rf" +#define RAIDFILE_WRITE_EXTENSION ".rfw" + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileUtil +// Purpose: Utility functions for RaidFile classes +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +class RaidFileUtil +{ +public: + typedef enum + { + NoFile = 0, + NonRaid = 1, + AsRaid = 2, + AsRaidWithMissingReadable = 3, + AsRaidWithMissingNotRecoverable = 4 + } ExistType; + + enum + { + Stripe1Exists = 1, + Stripe2Exists = 2, + ParityExists = 4 + }; + + static ExistType RaidFileExists(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pStartDisc = 0, int *pExisitingFiles = 0, int64_t *pRevisionID = 0); + + static int64_t DiscUsageInBlocks(int64_t FileSize, const RaidFileDiscSet &rDiscSet); + + // -------------------------------------------------------------------------- + // + // Function + // Name: std::string MakeRaidComponentName(RaidFileDiscSet &, const std::string &, int) + // Purpose: Returns the OS filename for a file of part of a disc set + // Created: 2003/07/11 + // + // -------------------------------------------------------------------------- + static inline std::string MakeRaidComponentName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int Disc) + { + if(Disc < 0 || Disc >= (int)rDiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, NoSuchDiscSet) + } + std::string r(rDiscSet[Disc]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + r += RAIDFILE_EXTENSION; + return r; + } + + // -------------------------------------------------------------------------- + // + // Function + // Name: std::string MakeWriteFileName(RaidFileDiscSet &, const std::string &) + // Purpose: Returns the OS filename for the temporary write file + // Created: 2003/07/11 + // + // -------------------------------------------------------------------------- + static inline std::string MakeWriteFileName(RaidFileDiscSet &rDiscSet, const std::string &rFilename, int *pOnDiscSet = 0) + { + int livesOnSet = rDiscSet.GetSetNumForWriteFiles(rFilename); + + // does the caller want to know which set it's on? + if(pOnDiscSet) *pOnDiscSet = livesOnSet; + + // Make the string + std::string r(rDiscSet[livesOnSet]); + r += DIRECTORY_SEPARATOR_ASCHAR; + r += rFilename; + r += RAIDFILE_WRITE_EXTENSION; + return r; + } +}; + +#endif // RAIDFILEUTIL__H + diff --git a/lib/raidfile/RaidFileWrite.cpp b/lib/raidfile/RaidFileWrite.cpp new file mode 100644 index 00000000..8f95ba65 --- /dev/null +++ b/lib/raidfile/RaidFileWrite.cpp @@ -0,0 +1,992 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileWrite.cpp +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#ifdef HAVE_UNISTD_H +# include +#endif + +#include +#include + +#ifdef HAVE_SYS_FILE_H +# include +#endif + +#include +#include + +#include "Guards.h" +#include "RaidFileWrite.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileUtil.h" +#include "Utils.h" +// For DirectoryExists fn +#include "RaidFileRead.h" + +#include "MemLeakFindOn.h" + +// should be a multiple of 2 +#define TRANSFORM_BLOCKS_TO_LOAD 4 +// Must have this number of discs in the set +#define TRANSFORM_NUMBER_DISCS_REQUIRED 3 + +// We want to use POSIX fstat() for now, not the emulated one, because it's +// difficult to rewrite all this code to use HANDLEs instead of ints. + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::RaidFileWrite(int, const std::string &) +// Purpose: Simple constructor, just stores required details +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename) + : mSetNumber(SetNumber), + mFilename(Filename), + mOSFileHandle(-1), // not valid file handle + mRefCount(-1) // unknown refcount +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::RaidFileWrite(int, +// const std::string &, int refcount) +// Purpose: Constructor with check for overwriting file +// with multiple references +// Created: 2009/07/05 +// +// -------------------------------------------------------------------------- +RaidFileWrite::RaidFileWrite(int SetNumber, const std::string &Filename, + int refcount) + : mSetNumber(SetNumber), + mFilename(Filename), + mOSFileHandle(-1), // not valid file handle + mRefCount(refcount) +{ + // Can't check for zero refcount here, because it's legal + // to create a RaidFileWrite to delete an object with zero refcount. + // Check in Commit() and Delete() instead. + if (refcount > 1) + { + BOX_ERROR("Attempted to modify object " << mFilename << + ", which has " << refcount << " references"); + THROW_EXCEPTION(RaidFileException, + RequestedModifyMultiplyReferencedFile); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::~RaidFileWrite() +// Purpose: Destructor (will discard written file if not commited) +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +RaidFileWrite::~RaidFileWrite() +{ + if(mOSFileHandle != -1) + { + // 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"); + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Open() +// Purpose: Opens the file for writing +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Open(bool AllowOverwrite) +{ + if(mOSFileHandle != -1) + { + THROW_EXCEPTION(RaidFileException, AlreadyOpen) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // Check for overwriting? (step 1) + if(!AllowOverwrite) + { + // See if the file exists already -- can't overwrite existing files + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); + if(existance != RaidFileUtil::NoFile) + { + THROW_FILE_ERROR("Attempted to overwrite raidfile " << + mSetNumber, mFilename, RaidFileException, + CannotOverwriteExistingFile); + } + } + + // Get the filename for the write file + mTempFilename = RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename); + // Add on a temporary extension + mTempFilename += 'X'; + + // Attempt to open + mOSFileHandle = ::open(mTempFilename.c_str(), + O_WRONLY | O_CREAT | O_BINARY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if(mOSFileHandle == -1) + { + THROW_SYS_FILE_ERROR("Failed to open RaidFile", mTempFilename, + RaidFileException, ErrorOpeningWriteFile); + } + + // Get a lock on the write file +#ifdef HAVE_FLOCK + int errnoBlock = EWOULDBLOCK; + if(::flock(mOSFileHandle, LOCK_EX | LOCK_NB) != 0) +#elif HAVE_DECL_F_SETLK + int errnoBlock = EAGAIN; + struct flock desc; + desc.l_type = F_WRLCK; + desc.l_whence = SEEK_SET; + desc.l_start = 0; + desc.l_len = 0; + if(::fcntl(mOSFileHandle, F_SETLK, &desc) != 0) +#else + int errnoBlock = ENOSYS; + if (0) +#endif + { + 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); + } + else + { + // Random error occured + THROW_SYS_FILE_ERRNO("Failed to lock RaidFile", + mTempFilename, errnoSaved, RaidFileException, + OSError); + } + } + + // Truncate it to size zero + if(::ftruncate(mOSFileHandle, 0) != 0) + { + int errnoSaved = errno; + + // Close the file + ::close(mOSFileHandle); + mOSFileHandle = -1; + + THROW_SYS_FILE_ERRNO("Failed to truncate RaidFile", + mTempFilename, errnoSaved, RaidFileException, + ErrorOpeningWriteFileOnTruncate); + } + + // Done! +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Write(const void *, int) +// Purpose: Writes a block of data +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Write(const void *pBuffer, int Length, int Timeout) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Write data + int written = ::write(mOSFileHandle, pBuffer, Length); + if(written != Length) + { + THROW_SYS_FILE_ERROR("Failed to write to RaidFile (attempted " + "to write " << Length << " bytes but managed only " << + written << ")", mTempFilename, RaidFileException, + OSError); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetPosition() +// Purpose: Returns current position in file +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetPosition() const +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Use lseek to find the current file position + off_t p = ::lseek(mOSFileHandle, 0, SEEK_CUR); + if(p == -1) + { + THROW_SYS_FILE_ERROR("Failed to get position in RaidFile", + mTempFilename, RaidFileException, OSError); + } + + return p; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Seek(RaidFileWrite::pos_type, bool) +// Purpose: Seeks in the file, relative to current position if Relative is true. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Seek(IOStream::pos_type SeekTo, int SeekType) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Seek... + if(::lseek(mOSFileHandle, SeekTo, ConvertSeekTypeToOSWhence(SeekType)) == -1) + { + THROW_SYS_FILE_ERROR("Failed to set position in RaidFile", + mTempFilename, RaidFileException, OSError); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Commit(bool) +// Purpose: Closes, and commits the written file +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Commit(bool ConvertToRaidNow) +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + if (mRefCount == 0) + { + THROW_FILE_ERROR("Attempted to modify object file with " + "no references", mTempFilename, RaidFileException, + RequestedModifyUnreferencedFile); + } + + // Rename it into place -- BEFORE it's closed so lock remains + +#ifdef WIN32 + // Except on Win32 which doesn't allow renaming open files + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_WIN_FILE_ERROR("Failed to close RaidFile for rename", + mTempFilename, RaidFileException, OSError); + } + mOSFileHandle = -1; +#endif // WIN32 + + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + // Get the filename for the write file + std::string renameTo(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + // And the current name + std::string renameFrom(renameTo + 'X'); + +#ifdef WIN32 + // need to delete the target first + 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); + } + } +#endif + + if(::rename(renameFrom.c_str(), renameTo.c_str()) != 0) + { + THROW_SYS_ERROR("Failed to rename file: " << renameFrom << + " to " << renameTo, RaidFileException, OSError); + } + +#ifndef WIN32 + // Close file... + if(::close(mOSFileHandle) != 0) + { + THROW_SYS_FILE_ERROR("Failed to close committed RaidFile", + mTempFilename, RaidFileException, OSError); + } + mOSFileHandle = -1; +#endif // !WIN32 + + // Raid it? + if(ConvertToRaidNow) + { + TransformToRaidStorage(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Discard() +// Purpose: Closes, discarding the data written. +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Discard() +{ + // open? + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, NotOpen) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // Get the filename for the write file (temporary) + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + writeFilename += 'X'; + + // Unlink and close it + +#ifdef WIN32 + // On Win32 we must close it first + if (::close(mOSFileHandle) != 0 || + ::unlink(writeFilename.c_str()) != 0) +#else // !WIN32 + if (::unlink(writeFilename.c_str()) != 0 || + ::close(mOSFileHandle) != 0) +#endif // !WIN32 + { + THROW_SYS_FILE_ERROR("Failed to delete file", writeFilename, + RaidFileException, OSError); + } + + // reset file handle + mOSFileHandle = -1; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::TransformToRaidStorage() +// Purpose: Turns the file into the RAID storage form +// Created: 2003/07/11 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::TransformToRaidStorage() +{ + // open? + if(mOSFileHandle != -1) + { + THROW_EXCEPTION(RaidFileException, WriteFileOpenOnTransform) + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + if(rdiscSet.IsNonRaidSet()) + { + // Not in RAID mode -- do nothing + return; + } + // Otherwise check that it's the right sized set + if(TRANSFORM_NUMBER_DISCS_REQUIRED != rdiscSet.size()) + { + THROW_EXCEPTION(RaidFileException, WrongNumberOfDiscsInSet) + } + unsigned int blockSize = rdiscSet.GetBlockSize(); + + // Get the filename for the write file (and get the disc set name for the start disc) + int startDisc = 0; + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename, &startDisc)); + + // Open it + FileHandleGuard<> writeFile(writeFilename.c_str()); + + // Get file information for write file + struct stat writeFileStat; + if(::fstat(writeFile, &writeFileStat) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } +// // DEBUG MODE -- check file system size block size is same as block size for files +// // doesn't really apply, as space benefits of using fragment size are worth efficiency, +// // and anyway, it'll be buffered eventually so it won't matter. +// #ifndef BOX_RELEASE_BUILD +// { +// if(writeFileStat.st_blksize != blockSize) +// { +// TRACE2("TransformToRaidStorage: optimal block size of file = %d, of set = %d, MISMATCH\n", +// writeFileStat.st_blksize, blockSize); +// } +// } +// #endif + + // How many blocks is the file? (rounding up) + int writeFileSizeInBlocks = (writeFileStat.st_size + (blockSize - 1)) / blockSize; + // And how big should the buffer be? (round up to multiple of 2, and no bigger than the preset limit) + int bufferSizeBlocks = (writeFileSizeInBlocks + 1) & ~1; + if(bufferSizeBlocks > TRANSFORM_BLOCKS_TO_LOAD) bufferSizeBlocks = TRANSFORM_BLOCKS_TO_LOAD; + // How big should the buffer be? + int bufferSize = (TRANSFORM_BLOCKS_TO_LOAD * blockSize); + + // Allocate buffer... + MemoryBlockGuard buffer(bufferSize); + + // Allocate buffer for parity file + MemoryBlockGuard parityBuffer(blockSize); + + // Get filenames of eventual files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 0) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 1) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, (startDisc + 2) % TRANSFORM_NUMBER_DISCS_REQUIRED)); + // Make write equivalents + std::string stripe1FilenameW(stripe1Filename + 'P'); + std::string stripe2FilenameW(stripe2Filename + 'P'); + std::string parityFilenameW(parityFilename + 'P'); + + + // Then open them all for writing (in strict order) + try + { +#if HAVE_DECL_O_EXLOCK + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe1(stripe1FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> stripe2(stripe2FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_EXLOCK | O_BINARY)> parity(parityFilenameW.c_str()); +#else + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe1(stripe1FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> stripe2(stripe2FilenameW.c_str()); + FileHandleGuard<(O_WRONLY | O_CREAT | O_EXCL | O_BINARY)> parity(parityFilenameW.c_str()); +#endif + + // Then... read in data... + int bytesRead = -1; + bool sizeRecordRequired = false; + int blocksDone = 0; + while((bytesRead = ::read(writeFile, buffer, bufferSize)) > 0) + { + // Blocks to do... + int blocksToDo = (bytesRead + (blockSize - 1)) / blockSize; + + // Need to add zeros to end? + int blocksRoundUp = (blocksToDo + 1) & ~1; + int zerosEnd = (blocksRoundUp * blockSize); + if(bytesRead != zerosEnd) + { + // Set the end of the blocks to zero + ::memset(buffer + bytesRead, 0, zerosEnd - bytesRead); + } + + // number of int's to XOR + unsigned int num = blockSize / sizeof(unsigned int); + + // Then... calculate and write parity data + for(int b = 0; b < blocksToDo; b += 2) + { + // Calculate int pointers + unsigned int *pstripe1 = (unsigned int *)(buffer + (b * blockSize)); + unsigned int *pstripe2 = (unsigned int *)(buffer + ((b+1) * blockSize)); + unsigned int *pparity = (unsigned int *)((char*)parityBuffer); + + // Do XOR + for(unsigned int n = 0; n < num; ++n) + { + pparity[n] = pstripe1[n] ^ pstripe2[n]; + } + + // Size of parity to write... + int parityWriteSize = blockSize; + + // Adjust if it's the last block + if((blocksDone + (b + 2)) >= writeFileSizeInBlocks) + { + // Yes... + unsigned int bytesInLastTwoBlocks = bytesRead - (b * blockSize); + + // Some special cases... + // Zero will never happen... but in the (imaginary) case it does, the file size will be appended + // by the test at the end. + if(bytesInLastTwoBlocks == sizeof(RaidFileRead::FileSizeType) + || bytesInLastTwoBlocks == blockSize) + { + // Write the entire block, and put the file size at end + sizeRecordRequired = true; + } + else if(bytesInLastTwoBlocks < blockSize) + { + // write only these bits + parityWriteSize = bytesInLastTwoBlocks; + } + else if(bytesInLastTwoBlocks < ((blockSize * 2) - sizeof(RaidFileRead::FileSizeType))) + { + // XOR in the size at the end of the parity block + ASSERT(sizeof(RaidFileRead::FileSizeType) == (2*sizeof(unsigned int))); + ASSERT(sizeof(RaidFileRead::FileSizeType) >= sizeof(off_t)); + int sizePos = (blockSize/sizeof(unsigned int)) - 2; + union { RaidFileRead::FileSizeType l; unsigned int i[2]; } sw; + + sw.l = box_hton64(writeFileStat.st_size); + pparity[sizePos+0] = pstripe1[sizePos+0] ^ sw.i[0]; + pparity[sizePos+1] = pstripe1[sizePos+1] ^ sw.i[1]; + } + else + { + // Write the entire block, and put the file size at end + sizeRecordRequired = true; + } + } + + // Write block + if(::write(parity, parityBuffer, parityWriteSize) != parityWriteSize) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Write stripes + char *writeFrom = buffer; + for(int l = 0; l < blocksToDo; ++l) + { + // Write the block + int toWrite = (l == (blocksToDo - 1)) + ?(bytesRead - ((blocksToDo-1)*blockSize)) + :blockSize; + if(::write(((l&1)==0)?stripe1:stripe2, writeFrom, toWrite) != toWrite) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Next block + writeFrom += blockSize; + } + + // Count of blocks done + blocksDone += blocksToDo; + } + // Error on read? + if(bytesRead == -1) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Special case for zero length files + if(writeFileStat.st_size == 0) + { + sizeRecordRequired = true; + } + + // Might need to write the file size to the end of the parity file + // if it can't be worked out some other means -- size is required to rebuild the file if one of the stripe files is missing + if(sizeRecordRequired) + { + ASSERT(sizeof(writeFileStat.st_size) <= sizeof(RaidFileRead::FileSizeType)); + RaidFileRead::FileSizeType sw = box_hton64(writeFileStat.st_size); + ASSERT((::lseek(parity, 0, SEEK_CUR) % blockSize) == 0); + if(::write(parity, &sw, sizeof(sw)) != sizeof(sw)) + { + BOX_LOG_SYS_ERROR("Failed to write to file: " << + writeFilename); + THROW_EXCEPTION(RaidFileException, OSError) + } + } + + // Then close the written files (note in reverse order of opening) + parity.Close(); + stripe2.Close(); + stripe1.Close(); + +#ifdef WIN32 + // Must delete before renaming + #define CHECK_UNLINK(file) \ + { \ + if (::unlink(file) != 0 && errno != ENOENT) \ + { \ + THROW_EMU_ERROR("Failed to unlink raidfile " \ + "stripe: " << file, RaidFileException, \ + OSError); \ + } \ + } + CHECK_UNLINK(stripe1Filename.c_str()); + CHECK_UNLINK(stripe2Filename.c_str()); + CHECK_UNLINK(parityFilename.c_str()); + #undef CHECK_UNLINK +#endif + + // Rename them into place + if(::rename(stripe1FilenameW.c_str(), stripe1Filename.c_str()) != 0 + || ::rename(stripe2FilenameW.c_str(), stripe2Filename.c_str()) != 0 + || ::rename(parityFilenameW.c_str(), parityFilename.c_str()) != 0) + { + THROW_EXCEPTION(RaidFileException, OSError) + } + + // Close the write file + writeFile.Close(); + + // Finally delete the write file + if(::unlink(writeFilename.c_str()) != 0) + { + BOX_LOG_SYS_ERROR("Failed to delete file: " << + writeFilename); + THROW_EXCEPTION(RaidFileException, OSError) + } + } + catch(...) + { + // Unlink all the dodgy files + ::unlink(stripe1Filename.c_str()); + ::unlink(stripe2Filename.c_str()); + ::unlink(parityFilename.c_str()); + ::unlink(stripe1FilenameW.c_str()); + ::unlink(stripe2FilenameW.c_str()); + ::unlink(parityFilenameW.c_str()); + + // and send the error on its way + throw; + } +} + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Delete() +// Purpose: Deletes a RAID file +// Created: 2003/07/13 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Delete() +{ + if (mRefCount != 0 && mRefCount != -1) + { + THROW_FILE_ERROR("Attempted to delete object with " << + mRefCount << " references", mFilename, + RaidFileException, RequestedDeleteReferencedFile); + } + + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + + // See if the file exists already -- can't delete files which don't exist + RaidFileUtil::ExistType existance = RaidFileUtil::RaidFileExists(rdiscSet, mFilename); + if(existance == RaidFileUtil::NoFile) + { + THROW_FILE_ERROR("Attempted to delete object which doesn't " + "exist", mFilename, RaidFileException, + RaidFileDoesntExist); + } + + // Get the filename for the write file + std::string writeFilename(RaidFileUtil::MakeWriteFileName(rdiscSet, mFilename)); + + // Attempt to delete it + bool deletedSomething = false; + if(::unlink(writeFilename.c_str()) == 0) + { + deletedSomething = true; + } + + // If we're not running in RAID mode, stop now + if(rdiscSet.size() == 1) + { + return; + } + + // Now the other files + std::string stripe1Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 0 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string stripe2Filename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 1 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + std::string parityFilename(RaidFileUtil::MakeRaidComponentName(rdiscSet, mFilename, 2 % TRANSFORM_NUMBER_DISCS_REQUIRED)); + if(::unlink(stripe1Filename.c_str()) == 0) + { + deletedSomething = true; + } + if(::unlink(stripe2Filename.c_str()) == 0) + { + deletedSomething = true; + } + if(::unlink(parityFilename.c_str()) == 0) + { + deletedSomething = true; + } + + // Check something happened + if(!deletedSomething) + { + THROW_FILE_ERROR("Failed to delete a RaidFile stripe set", + mFilename, RaidFileException, OSError); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::CreateDirectory(int, const std::string &, bool, int) +// Purpose: Creates a directory within the raid file directories with the given name. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive, int mode) +{ + // Get disc set + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(SetNumber)); + // Pass on... + CreateDirectory(rdiscSet, rDirName, Recursive, mode); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::CreateDirectory(const RaidFileDiscSet &, const std::string &, bool, int) +// Purpose: Creates a directory within the raid file directories with the given name. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive, int mode) +{ + if(Recursive) + { + // split up string + std::vector elements; + SplitString(rDirName, DIRECTORY_SEPARATOR_ASCHAR, elements); + + // Do each element in turn + std::string pn; + for(unsigned int e = 0; e < elements.size(); ++e) + { + // Only do this if the element has some text in it + if(elements[e].size() > 0) + { + pn += elements[e]; + if(!RaidFileRead::DirectoryExists(rSet, pn)) + { + CreateDirectory(rSet, pn, false, mode); + } + + // add separator + pn += DIRECTORY_SEPARATOR_ASCHAR; + } + } + + return; + } + + // Create a directory in every disc of the set + for(unsigned int l = 0; l < rSet.size(); ++l) + { + // build name + std::string dn(rSet[l] + DIRECTORY_SEPARATOR + rDirName); + + // attempt to create + if(::mkdir(dn.c_str(), mode) != 0) + { + if(errno == EEXIST) + { + // No. Bad things. + 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); + } + } + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Read(void *, int, int) +// Purpose: Unsupported, will exception +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +int RaidFileWrite::Read(void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(RaidFileException, UnsupportedReadWriteOrClose) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::Close() +// Purpose: Close, discarding file. +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +void RaidFileWrite::Close() +{ + if(mOSFileHandle != -1) + { + BOX_WARNING("RaidFileWrite::Close() called, discarding file"); + Discard(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::StreamDataLeft() +// Purpose: Never any data left to read! +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileWrite::StreamDataLeft() +{ + return false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::StreamClosed() +// Purpose: Is stream closed for writing? +// Created: 2003/08/21 +// +// -------------------------------------------------------------------------- +bool RaidFileWrite::StreamClosed() +{ + return mOSFileHandle == -1; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetFileSize() +// Purpose: Returns the size of the file written. +// Can only be used before the file is commited. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetFileSize() +{ + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, CanOnlyGetFileSizeBeforeCommit) + } + + // Stat to get size + struct stat st; + if(fstat(mOSFileHandle, &st) != 0) + { + THROW_SYS_FILE_ERROR("Failed to stat RaidFile", mTempFilename, + RaidFileException, OSError); + } + + return st.st_size; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: RaidFileWrite::GetDiscUsageInBlocks() +// Purpose: Returns the amount of disc space used, in blocks. +// Can only be used before the file is commited. +// Created: 2003/09/03 +// +// -------------------------------------------------------------------------- +IOStream::pos_type RaidFileWrite::GetDiscUsageInBlocks() +{ + if(mOSFileHandle == -1) + { + THROW_EXCEPTION(RaidFileException, CanOnlyGetUsageBeforeCommit) + } + + // Stat to get size + struct stat st; + if(fstat(mOSFileHandle, &st) != 0) + { + THROW_SYS_FILE_ERROR("Failed to stat RaidFile", mTempFilename, + RaidFileException, OSError); + } + + // Then return calculation + RaidFileController &rcontroller(RaidFileController::GetController()); + RaidFileDiscSet rdiscSet(rcontroller.GetDiscSet(mSetNumber)); + return RaidFileUtil::DiscUsageInBlocks(st.st_size, rdiscSet); +} + + diff --git a/lib/raidfile/RaidFileWrite.h b/lib/raidfile/RaidFileWrite.h new file mode 100644 index 00000000..ab9b399a --- /dev/null +++ b/lib/raidfile/RaidFileWrite.h @@ -0,0 +1,79 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: RaidFileWrite.h +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- + +#ifndef RAIDFILEWRITE__H +#define RAIDFILEWRITE__H + +#include + +#include "IOStream.h" + +class RaidFileDiscSet; + +// -------------------------------------------------------------------------- +// +// Class +// Name: RaidFileWrite +// Purpose: Writing RAID like files +// Created: 2003/07/10 +// +// -------------------------------------------------------------------------- +class RaidFileWrite : public IOStream +{ +public: + // 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); + + RaidFileWrite(int SetNumber, const std::string &Filename, int refcount); + ~RaidFileWrite(); + +private: + RaidFileWrite(const RaidFileWrite &rToCopy); + +public: + // IOStream interface + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); // will exception + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual pos_type GetPosition() const; + virtual void Seek(pos_type Offset, int SeekType); + virtual void Close(); // will discard the file! Use commit instead. + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + // Extra bits + void Open(bool AllowOverwrite = false); + void Commit(bool ConvertToRaidNow = false); + void Discard(); + void TransformToRaidStorage(); + void Delete(); + pos_type GetFileSize(); + pos_type GetDiscUsageInBlocks(); + + static void CreateDirectory(int SetNumber, const std::string &rDirName, bool Recursive = false, int mode = 0777); + static void CreateDirectory(const RaidFileDiscSet &rSet, const std::string &rDirName, bool Recursive = false, int mode = 0777); + +private: + int mSetNumber; + std::string mFilename, mTempFilename; + int mOSFileHandle; + int mRefCount; +}; + +#endif // RAIDFILEWRITE__H + diff --git a/lib/raidfile/raidfile-config.in b/lib/raidfile/raidfile-config.in new file mode 100755 index 00000000..b8ea73a5 --- /dev/null +++ b/lib/raidfile/raidfile-config.in @@ -0,0 +1,100 @@ +#!@PERL@ +use strict; + +# should be running as root +if($> != 0) +{ + printf "\nWARNING: this should be run as root\n\n" +} + +# check and get command line parameters +if($#ARGV != 4 && $#ARGV != 2) +{ + print <<__E; + +Setup raidfile config utility. + +Bad command line parameters. +Usage: + raidfile-config config-dir block-size dir0 [dir1 dir2] + +Parameters: + config-dir is usually @sysconfdir_expanded@/boxbackup + block-size must be a power of two, and usually the block or + fragment size of your file system + dir0, dir1, dir2 are the directories used as the root of the raid + file system + +If only one directory is specified, then userland RAID is disabled. +Specifying three directories enables it. + +__E + exit(1); +} + +my ($config_dir,$block_size,@dirs) = @ARGV; + +my $conf = $config_dir . '/raidfile.conf'; + +# check dirs are unique, and exist +my %d; +for(@dirs) +{ + die "$_ is used twice" if exists $d{$_}; + die "$_ is not a directory" unless -d $_; + die "$_ should be an absolute path" unless m/\A\//; + $d{$_} = 1; +} + +# check block size is OK +$block_size = int($block_size); +die "Bad block size" if $block_size <= 0; +my $c = 1; +while(1) +{ + last if $c == $block_size; + die "Block size $block_size is not a power of two" if $c > $block_size; + $c = $c * 2; +} + +# check that it doesn't already exist +if(-f $conf) +{ + die "$conf already exists. Delete and try again"; +} + +# create directory +if(!-d $config_dir) +{ + print "Creating $config_dir...\n"; + mkdir $config_dir,0755 or die "Can't create $config_dir"; +} + +# adjust if userland RAID is disabled +if($#dirs == 0) +{ + $dirs[1] = $dirs[0]; + $dirs[2] = $dirs[0]; + print "WARNING: userland RAID is disabled.\n"; +} + +# write the file +open CONFIG,">$conf" or die "Can't open $conf for writing"; + +print CONFIG <<__E; + +disc0 +{ + SetNumber = 0 + BlockSize = $block_size + Dir0 = $dirs[0] + Dir1 = $dirs[1] + Dir2 = $dirs[2] +} + +__E + +close CONFIG; + +print "Config file written.\n"; + diff --git a/lib/server/ConnectionException.txt b/lib/server/ConnectionException.txt new file mode 100644 index 00000000..7dcaadeb --- /dev/null +++ b/lib/server/ConnectionException.txt @@ -0,0 +1,28 @@ +EXCEPTION Connection 7 + +# for historic reasons not all numbers are used + +SocketWriteError 6 Probably a network issue between client and server. +SocketReadError 7 Probably a network issue between client and server. +SocketNameLookupError 9 Check hostname specified. +SocketShutdownError 12 +SocketConnectError 15 Probably a network issue between client and server, bad hostname, or server not running. +TLSHandshakeFailed 30 +TLSShutdownFailed 32 +TLSWriteFailed 33 Probably a network issue between client and server. +TLSReadFailed 34 Probably a network issue between client and server, or a problem with the server. +TLSNoPeerCertificate 36 +TLSPeerCertificateInvalid 37 Check certification process +TLSClosedWhenWriting 38 +TLSHandshakeTimedOut 39 +Protocol_Timeout 41 Probably a network issue between client and server. +Protocol_ObjTooBig 42 +Protocol_BadCommandRecieved 44 +Protocol_UnknownCommandRecieved 45 +Protocol_TriedToExecuteReplyCommand 46 +Protocol_UnexpectedReply 47 Server probably reported an error. +Protocol_HandshakeFailed 48 +Protocol_StreamWhenObjExpected 49 +Protocol_ObjWhenStreamExpected 50 +Protocol_TimeOutWhenSendingStream 52 Probably a network issue between client and server. +Protocol_StreamsNotConsumed 53 The server command handler did not consume all streams that were sent. diff --git a/lib/server/Daemon.cpp b/lib/server/Daemon.cpp new file mode 100644 index 00000000..836948bf --- /dev/null +++ b/lib/server/Daemon.cpp @@ -0,0 +1,989 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Daemon.cpp +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_BSD_UNISTD_H + #include +#endif + +#ifdef WIN32 + #include + #include + #include +#endif + +#include "depot.h" + +#include + +#ifdef NEED_BOX_VERSION_H +# include "BoxVersion.h" +#endif + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "Configuration.h" +#include "Daemon.h" +#include "FileModificationTime.h" +#include "Guards.h" +#include "Logging.h" +#include "UnixUser.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +Daemon *Daemon::spDaemon = 0; + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Daemon() +// Purpose: Constructor +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +Daemon::Daemon() + : mReloadConfigWanted(false), + mTerminateWanted(false), + #ifdef WIN32 + mSingleProcess(true), + mRunInForeground(true), + mKeepConsoleOpenAfterFork(true), + #else + mSingleProcess(false), + mRunInForeground(false), + mKeepConsoleOpenAfterFork(false), + #endif + mHaveConfigFile(false), + mLogFileLevel(Log::INVALID), + mAppName(DaemonName()) +{ + // In debug builds, switch on assert failure logging to syslog + ASSERT_FAILS_TO_SYSLOG_ON + // And trace goes to syslog too + TRACE_TO_SYSLOG(true) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::~Daemon() +// Purpose: Destructor +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +Daemon::~Daemon() +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetOptionString() +// Purpose: Returns the valid Getopt command-line options. +// This should be overridden by subclasses to add +// their own options, which should override +// ProcessOption, handle their own, and delegate to +// ProcessOption for the standard options. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +std::string Daemon::GetOptionString() +{ + return std::string("c:" + #ifndef WIN32 + "DF" + #endif + "hkKo:O:") + Logging::OptionParser::GetOptionString(); +} + +void Daemon::Usage() +{ + std::cout << + DaemonBanner() << "\n" + "(built with QDBM " << dpversion << ")\n" + "\n" + "Usage: " << mAppName << " [options] [config file]\n" + "\n" + "Options:\n" + " -c Use the specified configuration file. If -c is omitted, the last\n" + " argument is the configuration file, or else the default \n" + " [" << GetConfigFileName() << "]\n" +#ifndef WIN32 + " -D Debugging mode, do not fork, one process only, one client only\n" + " -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" + " -K Stop writing log messages to console while daemon is running\n" + " -o Log to a file, defaults to maximum verbosity\n" + " -O Set file log verbosity to error/warning/notice/info/trace/everything\n" + << Logging::OptionParser::GetUsageString(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::ProcessOption(int option) +// Purpose: Processes the supplied option (equivalent to the +// return code from getopt()). Return zero if the +// option was handled successfully, or nonzero to +// abort the program with that return value. +// Created: 2007/09/18 +// +// -------------------------------------------------------------------------- +int Daemon::ProcessOption(signed int option) +{ + switch(option) + { + case 'c': + { + mConfigFileName = optarg; + mHaveConfigFile = true; + } + break; + +#ifndef WIN32 + case 'D': + { + mSingleProcess = true; + } + break; + + case 'F': + { + mRunInForeground = true; + } + break; +#endif // !WIN32 + + case 'h': + { + Usage(); + return 2; + } + break; + + case 'k': + { + mKeepConsoleOpenAfterFork = true; + } + break; + + case 'K': + { + mKeepConsoleOpenAfterFork = false; + } + break; + + 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; + } + } + break; + + default: + { + return mLogLevel.ProcessOption(option); + } + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const char *, int, const char *[]) +// Purpose: Parses command-line options, and then calls +// Main(std::string& configFile, bool singleProcess) +// to start the daemon. +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +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[]) +{ + if (argc == 2 && strcmp(argv[1], "/?") == 0) + { + Usage(); + return 2; + } + + signed int c; + + // reset getopt, just in case anybody used it before. + // unfortunately glibc and BSD differ on this point! + // http://www.ussg.iu.edu/hypermail/linux/kernel/0305.3/0262.html + #if HAVE_DECL_OPTRESET == 1 || defined BOX_BSD_GETOPT + optind = 1; + optreset = 1; + #elif defined __GLIBC__ + optind = 0; + #else // Solaris, any others? + optind = 1; + #endif + + while((c = getopt(argc, (char * const *)argv, + GetOptionString().c_str())) != -1) + { + int returnCode = ProcessOption(c); + + if (returnCode != 0) + { + return returnCode; + } + } + + if (argc > optind && !mHaveConfigFile) + { + mConfigFileName = argv[optind]; optind++; + mHaveConfigFile = true; + } + + if (argc > optind && ::strcmp(argv[optind], "SINGLEPROCESS") == 0) + { + mSingleProcess = true; optind++; + } + + if (argc > optind) + { + BOX_FATAL("Unknown parameter on command line: " + << "'" << std::string(argv[optind]) << "'"); + return 2; + } + + Logging::FilterConsole(mLogLevel.GetCurrentLevel()); + Logging::FilterSyslog (mLogLevel.GetCurrentLevel()); + + if (mLogFileLevel != Log::INVALID) + { + mapLogFileLogger.reset( + new FileLogger(mLogFile, mLogFileLevel, + !mLogLevel.mTruncateLogFile)); + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Configure(const std::string& rConfigFileName) +// Purpose: Loads daemon configuration. Useful when you have +// a local Daemon object and don't intend to fork() +// or call Main(). +// Created: 2008/04/19 +// +// -------------------------------------------------------------------------- + +bool Daemon::Configure(const std::string& rConfigFileName) +{ + // Load the configuration file. + std::string errors; + std::auto_ptr apConfig; + + try + { + if (!FileExists(rConfigFileName)) + { + BOX_FATAL("The main configuration file for " << + DaemonName() << " was not found: " << + rConfigFileName); + if (!mHaveConfigFile) + { + BOX_WARNING("The default configuration " + "directory has changed from /etc/box " + "to /etc/boxbackup"); + } + return false; + } + + apConfig = Configuration::LoadAndVerify(rConfigFileName, + GetConfigVerify(), errors); + } + catch(BoxException &e) + { + if(e.GetType() == CommonException::ExceptionType && + e.GetSubType() == CommonException::OSFileOpenError) + { + BOX_ERROR("Failed to open configuration file: " << + rConfigFileName); + return false; + } + + throw; + } + + // Got errors? + if(apConfig.get() == 0) + { + BOX_ERROR("Failed to load or verify configuration file"); + return false; + } + + if(!Configure(*apConfig)) + { + BOX_ERROR("Failed to verify configuration file"); + return false; + } + + // Store configuration + mConfigFileName = rConfigFileName; + mLoadedConfigModifiedTime = GetConfigFileModifiedTime(); + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Configure(const Configuration& rConfig) +// Purpose: Loads daemon configuration. Useful when you have +// a local Daemon object and don't intend to fork() +// or call Main(). +// Created: 2008/08/12 +// +// -------------------------------------------------------------------------- + +bool Daemon::Configure(const Configuration& rConfig) +{ + std::string errors; + + // Verify() may modify the configuration, e.g. adding default values + // for required keys, so need to make a copy here + std::auto_ptr apConf(new Configuration(rConfig)); + apConf->Verify(*GetConfigVerify(), errors); + + // Got errors? + if(!errors.empty()) + { + BOX_ERROR("Configuration errors: " << errors); + return false; + } + + // Store configuration + mapConfiguration = apConf; + + // Let the derived class have a go at setting up stuff + // in the initial process + SetupInInitialProcess(); + + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Main(const std::string& rConfigFileName) +// Purpose: Starts the daemon off -- equivalent of C main() function +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +int Daemon::Main(const std::string &rConfigFileName) +{ + // Banner (optional) + { + BOX_SYSLOG(Log::NOTICE, DaemonBanner()); + } + + std::string pidFileName; + + bool asDaemon = !mSingleProcess && !mRunInForeground; + + try + { + if (!Configure(rConfigFileName)) + { + BOX_FATAL("Failed to start: failed to load " + "configuration file: " << rConfigFileName); + return 1; + } + + // Server configuration + const Configuration &serverConfig( + mapConfiguration->GetSubConfiguration("Server")); + + if(serverConfig.KeyExists("LogFacility")) + { + std::string facility = + serverConfig.GetKeyValue("LogFacility"); + Logging::SetFacility(Syslog::GetNamedFacility(facility)); + } + + // Open PID file for writing + pidFileName = serverConfig.GetKeyValue("PidFile"); + FileHandleGuard<(O_WRONLY | O_CREAT | O_TRUNC), (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)> pidFile(pidFileName.c_str()); + +#ifndef WIN32 + // Handle changing to a different user + if(serverConfig.KeyExists("User")) + { + // Config file specifies an user -- look up + UnixUser daemonUser(serverConfig.GetKeyValue("User").c_str()); + + // Change the owner on the PID file, so it can be deleted properly on termination + if(::fchown(pidFile, daemonUser.GetUID(), daemonUser.GetGID()) != 0) + { + THROW_EXCEPTION(ServerException, CouldNotChangePIDFileOwner) + } + + // Change the process ID + daemonUser.ChangeProcessUser(); + } + + if(asDaemon) + { + // Let's go... Daemonise... + switch(::fork()) + { + case -1: + // error + THROW_EXCEPTION(ServerException, DaemoniseFailed) + break; + + default: + // parent + // _exit(0); + return 0; + break; + + case 0: + // child + break; + } + + // In child + + // Set new session + if(::setsid() == -1) + { + BOX_LOG_SYS_ERROR("Failed to setsid()"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } + + // Fork again... + switch(::fork()) + { + case -1: + // error + BOX_LOG_SYS_ERROR("Failed to fork() a child"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + break; + + default: + // parent + _exit(0); + return 0; + break; + + case 0: + // child + break; + } + } +#endif // !WIN32 + + // Must set spDaemon before installing signal handler, + // otherwise the handler will crash if invoked too soon. + if(spDaemon != NULL) + { + THROW_EXCEPTION(ServerException, AlreadyDaemonConstructed) + } + spDaemon = this; + +#ifndef WIN32 + // Set signal handler + // Don't do this in the parent, since it might be anything + // (e.g. test/bbackupd) + + struct sigaction sa; + sa.sa_handler = SignalHandler; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); // macro + if(::sigaction(SIGHUP, &sa, NULL) != 0 || + ::sigaction(SIGTERM, &sa, NULL) != 0) + { + BOX_LOG_SYS_ERROR("Failed to set signal handlers"); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } +#endif // !WIN32 + + // Write PID to file + char pid[32]; + + int pidsize = snprintf(pid, sizeof(pid), "%d", (int)getpid()); + + if(::write(pidFile, pid, pidsize) != pidsize) + { + BOX_LOG_SYS_FATAL("Failed to write PID file: " << + pidFileName); + THROW_EXCEPTION(ServerException, DaemoniseFailed) + } + + // Set up memory leak reporting + #ifdef BOX_MEMORY_LEAK_TESTING + { + memleakfinder_setup_exit_report(std::string(DaemonName()) + + ".memleaks", DaemonName()); + } + #endif // BOX_MEMORY_LEAK_TESTING + + if(asDaemon && !mKeepConsoleOpenAfterFork) + { +#ifndef WIN32 + // Close standard streams + ::close(0); + ::close(1); + ::close(2); + + // Open and redirect them into /dev/null + int devnull = ::open(PLATFORM_DEV_NULL, O_RDWR, 0); + if(devnull == -1) + { + BOX_LOG_SYS_ERROR("Failed to open /dev/null"); + THROW_EXCEPTION(CommonException, OSFileError); + } + // Then duplicate them to all three handles + if(devnull != 0) dup2(devnull, 0); + if(devnull != 1) dup2(devnull, 1); + if(devnull != 2) dup2(devnull, 2); + // Close the original handle if it was opened above the std* range + if(devnull > 2) + { + ::close(devnull); + } + + // And definitely don't try and send anything to those file descriptors + // -- this has in the past sent text to something which isn't expecting it. + TRACE_TO_STDOUT(false); +#endif // ! WIN32 + Logging::ToConsole(false); + } + + // Log the start message + BOX_NOTICE("Starting daemon, version: " << BOX_VERSION); + BOX_NOTICE("Using configuration file: " << mConfigFileName); + } + catch(BoxException &e) + { + BOX_FATAL("Failed to start: exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); + return 1; + } + catch(std::exception &e) + { + BOX_FATAL("Failed to start: exception " << e.what()); + return 1; + } + catch(...) + { + BOX_FATAL("Failed to start: unknown error"); + return 1; + } + +#ifdef WIN32 + // Under win32 we must initialise the Winsock library + // before using sockets + + WSADATA info; + + if (WSAStartup(0x0101, &info) == SOCKET_ERROR) + { + // will not run without sockets + BOX_FATAL("Failed to initialise Windows Sockets"); + THROW_EXCEPTION(CommonException, Internal) + } +#endif + + int retcode = 0; + + // Main Daemon running + try + { + while(!mTerminateWanted) + { + Run(); + + if(mReloadConfigWanted && !mTerminateWanted) + { + // Need to reload that config file... + BOX_NOTICE("Reloading configuration file: " + << mConfigFileName); + std::string errors; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + mConfigFileName.c_str(), + GetConfigVerify(), errors)); + + // Got errors? + if(pconfig.get() == 0 || !errors.empty()) + { + // Tell user about errors + BOX_FATAL("Error in configuration " + << "file: " << mConfigFileName + << ": " << errors); + // And give up + retcode = 1; + break; + } + + // Store configuration + mapConfiguration = pconfig; + mLoadedConfigModifiedTime = + GetConfigFileModifiedTime(); + + // Stop being marked for loading config again + mReloadConfigWanted = false; + } + } + + // Delete the PID file + ::unlink(pidFileName.c_str()); + + // Log + BOX_NOTICE("Terminating daemon"); + } + catch(BoxException &e) + { + BOX_FATAL("Terminating due to exception " << e.what() + << " (" << e.GetType() + << "/" << e.GetSubType() << ")"); + retcode = 1; + } + catch(std::exception &e) + { + BOX_FATAL("Terminating due to exception " << e.what()); + retcode = 1; + } + catch(...) + { + BOX_FATAL("Terminating due to unknown exception"); + retcode = 1; + } + +#ifdef WIN32 + WSACleanup(); +#else + // Should clean up here, but it breaks memory leak tests. + /* + if(asDaemon) + { + // we are running in the child by now, and should not return + mapConfiguration.reset(); + exit(0); + } + */ +#endif + + ASSERT(spDaemon == this); + spDaemon = NULL; + + return retcode; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::EnterChild() +// Purpose: Sets up for a child task of the main server. Call +// just after fork(). +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void Daemon::EnterChild() +{ +#ifndef WIN32 + // Unset signal handlers + struct sigaction sa; + sa.sa_handler = SIG_DFL; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); // macro + ::sigaction(SIGHUP, &sa, NULL); + ::sigaction(SIGTERM, &sa, NULL); +#endif +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::SignalHandler(int) +// Purpose: Signal handler +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +void Daemon::SignalHandler(int sigraised) +{ +#ifndef WIN32 + if(spDaemon != 0) + { + switch(sigraised) + { + case SIGHUP: + spDaemon->mReloadConfigWanted = true; + break; + + case SIGTERM: + spDaemon->mTerminateWanted = true; + break; + + default: + break; + } + } +#endif +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::DaemonName() +// Purpose: Returns name of the daemon +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const char *Daemon::DaemonName() const +{ + return "generic-daemon"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::DaemonBanner() +// Purpose: Returns the text banner for this daemon's startup +// Created: 1/1/04 +// +// -------------------------------------------------------------------------- +std::string Daemon::DaemonBanner() const +{ + return "Generic daemon using the Box Application Framework"; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::Run() +// Purpose: Main run function after basic Daemon initialisation +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +void Daemon::Run() +{ + while(!StopRun()) + { + ::sleep(10); + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfigVerify() +// Purpose: Returns the configuration file verification structure for this daemon +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const ConfigurationVerify *Daemon::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + DAEMON_VERIFY_SERVER_KEYS + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfiguration() +// Purpose: Returns the daemon configuration object +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +const Configuration &Daemon::GetConfiguration() const +{ + if(mapConfiguration.get() == 0) + { + // Shouldn't get anywhere near this if a configuration file can't be loaded + THROW_EXCEPTION_MESSAGE(ServerException, Internal, + "The daemon has not been configured; no config file " + "has been loaded."); + } + + return *mapConfiguration; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::SetupInInitialProcess() +// Purpose: A chance for the daemon to do something initial +// setting up in the process which initiates +// everything, and after the configuration file has +// been read and verified. +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void Daemon::SetupInInitialProcess() +{ + // Base class doesn't do anything. +} + + +void Daemon::SetProcessTitle(const char *format, ...) +{ + // On OpenBSD, setproctitle() sets the process title to imagename: (imagename) + // -- make sure other platforms include the image name somewhere so ps listings give + // useful information. + +#ifdef HAVE_SETPROCTITLE + // optional arguments + va_list args; + va_start(args, format); + + // Make the string + char title[256]; + ::vsnprintf(title, sizeof(title), format, args); + +#ifdef WIN32 + StringCchCatA(title, sizeof(title)," - " PACKAGE_NAME); + SetConsoleTitleA(title); +#else // !WIN32 + // Set process title + ::setproctitle("%s", title); +#endif + +#endif // HAVE_SETPROCTITLE +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetConfigFileModifiedTime() +// Purpose: Returns the timestamp when the configuration file +// was last modified +// +// Created: 2006/01/29 +// +// -------------------------------------------------------------------------- + +box_time_t Daemon::GetConfigFileModifiedTime() const +{ + EMU_STRUCT_STAT st; + + if(EMU_STAT(GetConfigFileName().c_str(), &st) != 0) + { + if (errno == ENOENT) + { + return 0; + } + BOX_LOG_SYS_ERROR("Failed to stat configuration file: " << + GetConfigFileName()); + THROW_EXCEPTION(CommonException, OSFileError) + } + + return FileModificationTime(st); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Daemon::GetLoadedConfigModifiedTime() +// Purpose: Returns the timestamp when the configuration file +// had been last modified, at the time when it was +// loaded +// +// Created: 2006/01/29 +// +// -------------------------------------------------------------------------- + +box_time_t Daemon::GetLoadedConfigModifiedTime() const +{ + return mLoadedConfigModifiedTime; +} + diff --git a/lib/server/Daemon.h b/lib/server/Daemon.h new file mode 100644 index 00000000..b5384918 --- /dev/null +++ b/lib/server/Daemon.h @@ -0,0 +1,125 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Daemon.h +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + +/* NOTE: will log to local6: include a line like + local6.info /var/log/box + in /etc/syslog.conf +*/ + + +#ifndef DAEMON__H +#define DAEMON__H + +#include + +#include "BoxTime.h" +#include "Configuration.h" + +class ConfigurationVerify; + +// -------------------------------------------------------------------------- +// +// Class +// Name: Daemon +// Purpose: Basic daemon functionality +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- +class Daemon +{ +public: + Daemon(); + virtual ~Daemon(); +private: + Daemon(const Daemon &rToCopy); +public: + + virtual int Main(const std::string& rDefaultConfigFile, int argc, + const char *argv[]); + virtual int ProcessOptions(int argc, const char *argv[]); + + /* override this Main() if you want custom option processing: */ + virtual int Main(const std::string &rConfigFile); + + virtual void Run(); + const Configuration &GetConfiguration() const; + const std::string &GetConfigFileName() const {return mConfigFileName;} + + virtual const char *DaemonName() const; + virtual std::string DaemonBanner() const; + virtual const ConfigurationVerify *GetConfigVerify() const; + virtual void Usage(); + + virtual bool Configure(const std::string& rConfigFileName); + virtual bool Configure(const Configuration& rConfig); + + bool StopRun() {return mReloadConfigWanted | mTerminateWanted;} + bool IsReloadConfigWanted() {return mReloadConfigWanted;} + bool IsTerminateWanted() {return mTerminateWanted;} + + // To allow derived classes to get these signals in other ways + void SetReloadConfigWanted() {mReloadConfigWanted = true;} + void SetTerminateWanted() {mTerminateWanted = true;} + + virtual void EnterChild(); + + static void SetProcessTitle(const char *format, ...); + void SetRunInForeground(bool foreground) + { + mRunInForeground = foreground; + } + void SetSingleProcess(bool value) + { + mSingleProcess = value; + } + +protected: + virtual void SetupInInitialProcess(); + box_time_t GetLoadedConfigModifiedTime() const; + bool IsSingleProcess() { return mSingleProcess; } + virtual std::string GetOptionString(); + virtual int ProcessOption(signed int option); + void ResetLogFile() + { + if(mapLogFileLogger.get()) + { + mapLogFileLogger.reset( + new FileLogger(mLogFile, mLogFileLevel, + !mLogLevel.mTruncateLogFile)); + } + } + +private: + static void SignalHandler(int sigraised); + box_time_t GetConfigFileModifiedTime() const; + + std::string mConfigFileName; + std::auto_ptr mapConfiguration; + box_time_t mLoadedConfigModifiedTime; + bool mReloadConfigWanted; + bool mTerminateWanted; + bool mSingleProcess; + bool mRunInForeground; + bool mKeepConsoleOpenAfterFork; + bool mHaveConfigFile; + Logging::OptionParser mLogLevel; + std::string mLogFile; + Log::Level mLogFileLevel; + std::auto_ptr mapLogFileLogger; + static Daemon *spDaemon; + std::string mAppName; +}; + +#define DAEMON_VERIFY_SERVER_KEYS \ + ConfigurationVerifyKey("PidFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("LogFacility", 0), \ + ConfigurationVerifyKey("User", ConfigTest_LastEntry) + +#endif // DAEMON__H + diff --git a/lib/server/LocalProcessStream.cpp b/lib/server/LocalProcessStream.cpp new file mode 100644 index 00000000..c331a135 --- /dev/null +++ b/lib/server/LocalProcessStream.cpp @@ -0,0 +1,180 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: LocalProcessStream.cpp +// Purpose: Opens a process, and presents stdin/stdout as a stream. +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_SYS_SOCKET_H + #include +#endif + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include "LocalProcessStream.h" +#include "autogen_ServerException.h" +#include "Utils.h" + +#ifdef WIN32 + #include "FileStream.h" +#else + #include "SocketStream.h" +#endif + +#include "MemLeakFindOn.h" + +#define MAX_ARGUMENTS 64 + +// -------------------------------------------------------------------------- +// +// Function +// Name: LocalProcessStream(const char *, pid_t &) +// Purpose: Run a new process, and return a stream giving access +// to its stdin and stdout (stdout and stderr on +// Win32). Returns the PID of the new process -- this +// must be waited on at some point to avoid zombies +// (except on Win32). +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- +std::auto_ptr LocalProcessStream(const std::string& rCommandLine, + pid_t &rPidOut) +{ +#ifndef WIN32 + + // Split up command + std::vector command; + SplitString(rCommandLine, ' ', command); + + // Build arguments + char *args[MAX_ARGUMENTS + 4]; + { + int a = 0; + std::vector::const_iterator i(command.begin()); + while(a < MAX_ARGUMENTS && i != command.end()) + { + args[a++] = (char*)(*(i++)).c_str(); + } + args[a] = NULL; + } + + // Create a socket pair to communicate over. + int sv[2] = {-1,-1}; + if(::socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, sv) != 0) + { + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + + std::auto_ptr stream(new SocketStream(sv[0])); + + // Fork + pid_t pid = 0; + switch(pid = vfork()) + { + case -1: // error + ::close(sv[0]); + ::close(sv[1]); + THROW_EXCEPTION(ServerException, ServerForkError) + break; + + case 0: // child + // Close end of the socket not being used + ::close(sv[0]); + // Duplicate the file handles to stdin and stdout + if(sv[1] != 0) ::dup2(sv[1], 0); + if(sv[1] != 1) ::dup2(sv[1], 1); + // Close the now redundant socket + if(sv[1] != 0 && sv[1] != 1) + { + ::close(sv[1]); + } + // Execute command! + ::execv(args[0], args); + ::_exit(127); // report error + break; + + default: + // just continue... + break; + } + + // Close the file descriptor not being used + ::close(sv[1]); + + // Return the stream object and PID + rPidOut = pid; + return stream; + +#else // WIN32 + + SECURITY_ATTRIBUTES secAttr; + secAttr.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttr.bInheritHandle = TRUE; + secAttr.lpSecurityDescriptor = NULL; + + HANDLE writeInChild, readFromChild; + if(!CreatePipe(&readFromChild, &writeInChild, &secAttr, 0)) + { + BOX_ERROR("Failed to CreatePipe for child process: " << + GetErrorMessage(GetLastError())); + THROW_EXCEPTION(ServerException, SocketPairFailed) + } + SetHandleInformation(readFromChild, HANDLE_FLAG_INHERIT, 0); + + PROCESS_INFORMATION procInfo; + STARTUPINFO startupInfo; + + ZeroMemory(&procInfo, sizeof(procInfo)); + ZeroMemory(&startupInfo, sizeof(startupInfo)); + startupInfo.cb = sizeof(startupInfo); + startupInfo.hStdError = writeInChild; + startupInfo.hStdOutput = writeInChild; + startupInfo.hStdInput = INVALID_HANDLE_VALUE; + startupInfo.dwFlags |= STARTF_USESTDHANDLES; + + CHAR* commandLineCopy = (CHAR*)malloc(rCommandLine.size() + 1); + strcpy(commandLineCopy, rCommandLine.c_str()); + + BOOL result = CreateProcess(NULL, + commandLineCopy, // command line + NULL, // process security attributes + NULL, // primary thread security attributes + TRUE, // handles are inherited + 0, // creation flags + NULL, // use parent's environment + NULL, // use parent's current directory + &startupInfo, // STARTUPINFO pointer + &procInfo); // receives PROCESS_INFORMATION + + free(commandLineCopy); + + if(!result) + { + BOX_ERROR("Failed to CreateProcess: '" << rCommandLine << + "': " << GetErrorMessage(GetLastError())); + CloseHandle(writeInChild); + CloseHandle(readFromChild); + THROW_EXCEPTION(ServerException, ServerForkError) + } + + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + CloseHandle(writeInChild); + + rPidOut = (int)(procInfo.dwProcessId); + + std::auto_ptr stream(new FileStream(readFromChild)); + return stream; + +#endif // ! WIN32 +} + + + + diff --git a/lib/server/LocalProcessStream.h b/lib/server/LocalProcessStream.h new file mode 100644 index 00000000..51e51f8a --- /dev/null +++ b/lib/server/LocalProcessStream.h @@ -0,0 +1,20 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: LocalProcessStream.h +// Purpose: Opens a process, and presents stdin/stdout as a stream. +// Created: 12/3/04 +// +// -------------------------------------------------------------------------- + +#ifndef LOCALPROCESSSTREAM__H +#define LOCALPROCESSSTREAM__H + +#include +#include "IOStream.h" + +std::auto_ptr LocalProcessStream(const std::string& rCommandLine, + pid_t &rPidOut); + +#endif // LOCALPROCESSSTREAM__H + diff --git a/lib/server/Makefile.extra b/lib/server/Makefile.extra new file mode 100644 index 00000000..7fc6baf9 --- /dev/null +++ b/lib/server/Makefile.extra @@ -0,0 +1,11 @@ + +MAKEEXCEPTION = ../../lib/common/makeexception.pl + +# AUTOGEN SEEDING +autogen_ServerException.h autogen_ServerException.cpp: $(MAKEEXCEPTION) ServerException.txt + $(_PERL) $(MAKEEXCEPTION) ServerException.txt + +# AUTOGEN SEEDING +autogen_ConnectionException.h autogen_ConnectionException.cpp: $(MAKEEXCEPTION) ConnectionException.txt + $(_PERL) $(MAKEEXCEPTION) ConnectionException.txt + 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..9f2245ec --- /dev/null +++ b/lib/server/Message.h @@ -0,0 +1,70 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Message.h +// Purpose: Protocol object base class +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLOBJECT__H +#define PROTOCOLOBJECT__H + +#include + +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 { } + virtual std::string ToString() const = 0; +}; + +/* +class Reply; + +class Request : public Message +{ +public: + Request() { } + virtual ~Request() { } + Request(const Request &rToCopy) { } + virtual std::auto_ptr 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/OverlappedIO.h b/lib/server/OverlappedIO.h new file mode 100644 index 00000000..12495053 --- /dev/null +++ b/lib/server/OverlappedIO.h @@ -0,0 +1,42 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: OverlappedIO.h +// Purpose: Windows overlapped IO handle guard +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- + +#ifndef OVERLAPPEDIO__H +#define OVERLAPPEDIO__H + +class OverlappedIO +{ +public: + OVERLAPPED mOverlapped; + + OverlappedIO() + { + ZeroMemory(&mOverlapped, sizeof(mOverlapped)); + mOverlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, + NULL); + if (mOverlapped.hEvent == INVALID_HANDLE_VALUE) + { + BOX_LOG_WIN_ERROR("Failed to create event for " + "overlapped I/O"); + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + } + + ~OverlappedIO() + { + if (CloseHandle(mOverlapped.hEvent) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to delete event for " + "overlapped I/O"); + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + } +}; + +#endif // !OVERLAPPEDIO__H diff --git a/lib/server/Protocol.cpp b/lib/server/Protocol.cpp new file mode 100644 index 00000000..0adf9543 --- /dev/null +++ b/lib/server/Protocol.cpp @@ -0,0 +1,1193 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Protocol.cpp +// Purpose: Generic protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include + +#include +#include +#include + +#include + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "Protocol.h" +#include "ProtocolWire.h" +#include "SocketStream.h" +#include "PartialReadStream.h" +#include "ProtocolUncertainStream.h" +#include "Logging.h" + +#include "MemLeakFindOn.h" + +#ifdef BOX_RELEASE_BUILD + #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 +#else +// #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 1024 + #define PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK 4 +#endif + +#define UNCERTAIN_STREAM_SIZE_BLOCK (64*1024) + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Protocol(IOStream &rStream) +// Purpose: Constructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +Protocol::Protocol(std::auto_ptr apConn) +: mapConn(apConn), + mHandshakeDone(false), + mMaxObjectSize(PROTOCOL_DEFAULT_MAXOBJSIZE), + mTimeout(PROTOCOL_DEFAULT_TIMEOUT), + mpBuffer(0), + mBufferSize(0), + mReadOffset(-1), + mWriteOffset(-1), + mValidDataSize(-1), + mLogToSysLog(false), + mLogToFile(NULL) +{ + BOX_TRACE("Send block allocation size is " << + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::~Protocol() +// Purpose: Destructor +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +Protocol::~Protocol() +{ + // Free buffer? + if(mpBuffer != 0) + { + free(mpBuffer); + mpBuffer = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Handshake() +// Purpose: Handshake with peer (exchange ident strings) +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- +void Protocol::Handshake() +{ + // Already done? + if(mHandshakeDone) + { + THROW_EXCEPTION(CommonException, Internal) + } + + // Make handshake block + PW_Handshake hsSend; + ::memset(&hsSend, 0, sizeof(hsSend)); + // Copy in ident string + ::strncpy(hsSend.mIdent, GetProtocolIdentString(), sizeof(hsSend.mIdent)); + + // Send it + mapConn->Write(&hsSend, sizeof(hsSend), GetTimeout()); + mapConn->WriteAllBuffered(); + + // Receive a handshake from the peer + PW_Handshake hsReceive; + ::memset(&hsReceive, 0, sizeof(hsReceive)); + char *readInto = (char*)&hsReceive; + int bytesToRead = sizeof(hsReceive); + while(bytesToRead > 0) + { + // Get some data from the stream + int bytesRead = mapConn->Read(readInto, bytesToRead, GetTimeout()); + if(bytesRead == 0) + { + THROW_EXCEPTION(ConnectionException, Protocol_Timeout) + } + readInto += bytesRead; + bytesToRead -= bytesRead; + } + ASSERT(bytesToRead == 0); + + // Are they the same? + if(::memcmp(&hsSend, &hsReceive, sizeof(hsSend)) != 0) + { + THROW_EXCEPTION(ConnectionException, Protocol_HandshakeFailed) + } + + // Mark as done + mHandshakeDone = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::CheckAndReadHdr(void *) +// Purpose: Check read for recieve call and get object header from stream. +// Don't use type here to avoid dependency in .h file. +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::CheckAndReadHdr(void *hdr) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // Get some data into this header + if(!mapConn->ReadFullBuffer(hdr, sizeof(PW_ObjectHeader), + 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(ConnectionException, Protocol_Timeout) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::ReceiveInternal() +// Purpose: Receives an object from the stream, creating it +// from the factory object type +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +std::auto_ptr Protocol::ReceiveInternal() +{ + // Get object header + PW_ObjectHeader objHeader; + CheckAndReadHdr(&objHeader); + + // Hope it's not a stream + if(ntohl(objHeader.mObjType) == SPECIAL_STREAM_OBJECT_TYPE) + { + THROW_EXCEPTION(ConnectionException, Protocol_StreamWhenObjExpected) + } + + // Check the object size + uint32_t objSize = ntohl(objHeader.mObjSize); + if(objSize < sizeof(objHeader) || objSize > mMaxObjectSize) + { + THROW_EXCEPTION(ConnectionException, Protocol_ObjTooBig) + } + + // Create a blank object + std::auto_ptr obj(MakeMessage(ntohl(objHeader.mObjType))); + + // Make sure memory is allocated to read it into + EnsureBufferAllocated(objSize); + + // Read data + if(!mapConn->ReadFullBuffer(mpBuffer, objSize - sizeof(objHeader), + 0 /* not interested in bytes read if this fails */, mTimeout)) + { + THROW_EXCEPTION(ConnectionException, Protocol_Timeout) + } + + // Setup ready to read out data from the buffer + mValidDataSize = objSize - sizeof(objHeader); + mReadOffset = 0; + + // Get the object to read its properties from the data recieved + try + { + obj->SetPropertiesFromStreamData(*this); + } + catch(...) + { + // Make sure state is reset! + mValidDataSize = -1; + mReadOffset = -1; + throw; + } + + // Any data left over? + bool dataLeftOver = (mValidDataSize != mReadOffset); + + // Unset read state, so future read calls don't fail + mValidDataSize = -1; + mReadOffset = -1; + + // Exception if not all the data was consumed + if(dataLeftOver) + { + THROW_EXCEPTION(ConnectionException, Protocol_BadCommandRecieved) + } + + return obj; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::SendInternal() +// Purpose: Send an object to the other side of the connection. +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::SendInternal(const Message &rObject) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // Make sure there's a little bit of space allocated + EnsureBufferAllocated(((sizeof(PW_ObjectHeader) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); + ASSERT(mBufferSize >= (int)sizeof(PW_ObjectHeader)); + + // Setup for write operation + mValidDataSize = 0; // Not used, but must not be -1 + mWriteOffset = sizeof(PW_ObjectHeader); + + try + { + rObject.WritePropertiesToStreamData(*this); + } + catch(...) + { + // Make sure state is reset! + mValidDataSize = -1; + mWriteOffset = -1; + throw; + } + + // How big? + int writtenSize = mWriteOffset; + + // Reset write state + mValidDataSize = -1; + mWriteOffset = -1; + + // Make header in the existing block + PW_ObjectHeader *pobjHeader = (PW_ObjectHeader*)(mpBuffer); + pobjHeader->mObjSize = htonl(writtenSize); + pobjHeader->mObjType = htonl(rObject.GetType()); + + // Write data + mapConn->Write(mpBuffer, writtenSize, GetTimeout()); + mapConn->WriteAllBuffered(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::EnsureBufferAllocated(int) +// Purpose: Private. Ensures the buffer is at least the size requested. +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::EnsureBufferAllocated(int Size) +{ + if(mpBuffer != 0 && mBufferSize >= Size) + { + // Nothing to do! + return; + } + + // Need to allocate, or reallocate, the block + if(mpBuffer != 0) + { + // Reallocate + void *b = realloc(mpBuffer, Size); + if(b == 0) + { + throw std::bad_alloc(); + } + mpBuffer = (char*)b; + mBufferSize = Size; + } + else + { + // Just allocate + mpBuffer = (char*)malloc(Size); + if(mpBuffer == 0) + { + throw std::bad_alloc(); + } + mBufferSize = Size; + } +} + + +#define READ_START_CHECK \ + if(mValidDataSize == -1 || mWriteOffset != -1 || mReadOffset == -1) \ + { \ + THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ + } + +#define READ_CHECK_BYTES_AVAILABLE(bytesRequired) \ + if((mReadOffset + (int)(bytesRequired)) > mValidDataSize) \ + { \ + THROW_EXCEPTION(ConnectionException, Protocol_BadCommandRecieved) \ + } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(void *, int) +// Purpose: Read raw data from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(void *Buffer, int Size) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(Size) + + // Copy data out + ::memmove(Buffer, mpBuffer + mReadOffset, Size); + mReadOffset += Size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(std::string &, int) +// Purpose: Read raw data from the stream (buffered), into a std::string +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::Read(std::string &rOut, int Size) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(Size) + + rOut.assign(mpBuffer + mReadOffset, Size); + mReadOffset += Size; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int64_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int64_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int64_t)) + +#ifdef HAVE_ALIGNED_ONLY_INT64 + int64_t nvalue; + memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int64_t)); +#else + int64_t nvalue = *((int64_t*)(mpBuffer + mReadOffset)); +#endif + rOut = box_ntoh64(nvalue); + + mReadOffset += sizeof(int64_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int32_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int32_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int32_t)) + +#ifdef HAVE_ALIGNED_ONLY_INT32 + int32_t nvalue; + memcpy(&nvalue, mpBuffer + mReadOffset, sizeof(int32_t)); +#else + int32_t nvalue = *((int32_t*)(mpBuffer + mReadOffset)); +#endif + rOut = ntohl(nvalue); + mReadOffset += sizeof(int32_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int16_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int16_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int16_t)) + + rOut = ntohs(*((int16_t*)(mpBuffer + mReadOffset))); + mReadOffset += sizeof(int16_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(int8_t &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(int8_t &rOut) +{ + READ_START_CHECK + READ_CHECK_BYTES_AVAILABLE(sizeof(int8_t)) + + rOut = *((int8_t*)(mpBuffer + mReadOffset)); + mReadOffset += sizeof(int8_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Read(std::string &) +// Purpose: Read a value from the stream (buffered) +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Read(std::string &rOut) +{ + // READ_START_CHECK implied + int32_t size; + Read(size); + + READ_CHECK_BYTES_AVAILABLE(size) + + // initialise string + rOut.assign(mpBuffer + mReadOffset, size); + mReadOffset += size; +} + + + + +#define WRITE_START_CHECK \ + if(mValidDataSize == -1 || mWriteOffset == -1 || mReadOffset != -1) \ + { \ + THROW_EXCEPTION(ServerException, Protocol_BadUsage) \ + } + +#define WRITE_ENSURE_BYTES_AVAILABLE(bytesToWrite) \ + if(mWriteOffset + (int)(bytesToWrite) > mBufferSize) \ + { \ + EnsureBufferAllocated((((mWriteOffset + (int)(bytesToWrite)) + PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK - 1) / PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK) * PROTOCOL_ALLOCATE_SEND_BLOCK_CHUNK); \ + ASSERT(mWriteOffset + (int)(bytesToWrite) <= mBufferSize); \ + } + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(const void *, int) +// Purpose: Writes the contents of a buffer to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(const void *Buffer, int Size) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(Size) + + ::memmove(mpBuffer + mWriteOffset, Buffer, Size); + mWriteOffset += Size; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int64_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int64_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int64_t)) + + int64_t nvalue = box_hton64(Value); +#ifdef HAVE_ALIGNED_ONLY_INT64 + memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int64_t)); +#else + *((int64_t*)(mpBuffer + mWriteOffset)) = nvalue; +#endif + mWriteOffset += sizeof(int64_t); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int32_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int32_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int32_t)) + + int32_t nvalue = htonl(Value); +#ifdef HAVE_ALIGNED_ONLY_INT32 + memcpy(mpBuffer + mWriteOffset, &nvalue, sizeof(int32_t)); +#else + *((int32_t*)(mpBuffer + mWriteOffset)) = nvalue; +#endif + mWriteOffset += sizeof(int32_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int16_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int16_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int16_t)) + + *((int16_t*)(mpBuffer + mWriteOffset)) = htons(Value); + mWriteOffset += sizeof(int16_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(int8_t) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(int8_t Value) +{ + WRITE_START_CHECK + WRITE_ENSURE_BYTES_AVAILABLE(sizeof(int8_t)) + + *((int8_t*)(mpBuffer + mWriteOffset)) = Value; + mWriteOffset += sizeof(int8_t); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::Write(const std::string &) +// Purpose: Writes a value to the stream +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +void Protocol::Write(const std::string &rValue) +{ + // WRITE_START_CHECK implied + Write((int32_t)(rValue.size())); + + WRITE_ENSURE_BYTES_AVAILABLE(rValue.size()) + Write(rValue.c_str(), rValue.size()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::ReceiveStream() +// Purpose: Receive a stream from the remote side +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +std::auto_ptr Protocol::ReceiveStream() +{ + // Get object header + PW_ObjectHeader objHeader; + CheckAndReadHdr(&objHeader); + + // Hope it's not an object + if(ntohl(objHeader.mObjType) != SPECIAL_STREAM_OBJECT_TYPE) + { + THROW_EXCEPTION(ConnectionException, Protocol_ObjWhenStreamExpected) + } + + // Get the stream size + uint32_t streamSize = ntohl(objHeader.mObjSize); + + // Inform sub class + InformStreamReceiving(streamSize); + + // Return a stream object + if(streamSize == ProtocolStream_SizeUncertain) + { + BOX_TRACE("Receiving stream, size uncertain"); + return std::auto_ptr( + new ProtocolUncertainStream(*mapConn)); + } + else + { + BOX_TRACE("Receiving stream, size " << streamSize << " bytes"); + return std::auto_ptr( + new PartialReadStream(*mapConn, streamSize)); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::SendStream(IOStream &) +// Purpose: Send a stream to the remote side +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void Protocol::SendStream(IOStream &rStream) +{ + // Check usage + if(mValidDataSize != -1 || mWriteOffset != -1 || mReadOffset != -1) + { + THROW_EXCEPTION(ServerException, Protocol_BadUsage) + } + + // Handshake done? + if(!mHandshakeDone) + { + Handshake(); + } + + // How should this be streamed? + bool uncertainSize = false; + IOStream::pos_type streamSize = rStream.BytesLeftToRead(); + if(streamSize == IOStream::SizeOfStreamUnknown + || streamSize > 0x7fffffff) + { + // Can't send this using the fixed size header + uncertainSize = true; + } + + 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"); + } + + // Inform sub class + InformStreamSending(streamSize); + + // Make header + PW_ObjectHeader objHeader; + objHeader.mObjSize = htonl(uncertainSize?(ProtocolStream_SizeUncertain):streamSize); + objHeader.mObjType = htonl(SPECIAL_STREAM_OBJECT_TYPE); + + // Write header + mapConn->Write(&objHeader, sizeof(objHeader), GetTimeout()); + // Could be sent in one of two ways + if(uncertainSize) + { + // Don't know how big this is going to be -- so send it in chunks + + // Allocate memory + uint8_t *blockA = (uint8_t *)malloc(UNCERTAIN_STREAM_SIZE_BLOCK + sizeof(int)); + if(blockA == 0) + { + throw std::bad_alloc(); + } + uint8_t *block = blockA + sizeof(int); // so that everything is word aligned for reading, but can put the one byte header before it + + try + { + int bytesInBlock = 0; + while(rStream.StreamDataLeft()) + { + // Read some of it + bytesInBlock += rStream.Read(block + bytesInBlock, UNCERTAIN_STREAM_SIZE_BLOCK - bytesInBlock); + + // Send as much as we can out + bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); + } + + // Everything recieved from stream, but need to send whatevers left in the block + while(bytesInBlock > 0) + { + bytesInBlock -= SendStreamSendBlock(block, bytesInBlock); + } + + // Send final byte to finish the stream + BOX_TRACE("Sending end of stream byte"); + uint8_t endOfStream = ProtocolStreamHeader_EndOfStream; + mapConn->Write(&endOfStream, 1, GetTimeout()); + BOX_TRACE("Sent end of stream byte"); + } + catch(...) + { + free(blockA); + throw; + } + + // Clean up + free(blockA); + } + else + { + // Fixed size stream, send it all in one go + if(!rStream.CopyStreamTo(*mapConn, GetTimeout(), + 4096 /* slightly larger buffer */)) + { + THROW_EXCEPTION(ConnectionException, Protocol_TimeOutWhenSendingStream) + } + } + // Make sure everything is written + mapConn->WriteAllBuffered(); + +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::SendStreamSendBlock(uint8_t *, int) +// Purpose: Sends as much of the block as can be sent, moves the remainer down to the beginning, +// and returns the number of bytes sent. WARNING: Will write to Block[-1] +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- +int Protocol::SendStreamSendBlock(uint8_t *Block, int BytesInBlock) +{ + // Quick sanity check + if(BytesInBlock == 0) + { + BOX_TRACE("Zero size block, not sending anything"); + return 0; + } + + // Work out the header byte + uint8_t header = 0; + int writeSize = 0; + if(BytesInBlock >= (64*1024)) + { + header = ProtocolStreamHeader_SizeIs64k; + writeSize = (64*1024); + } + else + { + // Scan the table to find the most that can be written + for(int s = ProtocolStreamHeader_MaxEncodedSizeValue; s > 0; --s) + { + if(sProtocolStreamHeaderLengths[s] <= BytesInBlock) + { + header = s; + writeSize = sProtocolStreamHeaderLengths[s]; + break; + } + } + } + ASSERT(header > 0); + BOX_TRACE("Sending header byte " << (int)header << " plus " << + writeSize << " bytes to stream"); + + // Store the header + Block[-1] = header; + + // Write everything out + mapConn->Write(Block - 1, writeSize + 1, GetTimeout()); + + BOX_TRACE("Sent " << (writeSize+1) << " bytes to stream"); + // move the remainer to the beginning of the block for the next time round + if(writeSize != BytesInBlock) + { + ::memmove(Block, Block + writeSize, BytesInBlock - writeSize); + } + + return writeSize; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::InformStreamReceiving(uint32_t) +// Purpose: Informs sub classes about streams being received +// Created: 2003/10/27 +// +// -------------------------------------------------------------------------- +void Protocol::InformStreamReceiving(uint32_t Size) +{ + 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()); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Protocol::InformStreamSending(uint32_t) +// Purpose: Informs sub classes about streams being sent +// Created: 2003/10/27 +// +// -------------------------------------------------------------------------- +void Protocol::InformStreamSending(uint32_t Size) +{ + 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()); + } +} + + +/* +perl code to generate the table below + +#!/usr/bin/perl +use strict; +open OUT,">protolengths.txt"; +my $len = 0; +for(0 .. 255) +{ + print OUT "\t$len,\t// $_\n"; + my $inc = 1; + $inc = 8 if $_ >= 64; + $inc = 16 if $_ >= 96; + $inc = 32 if $_ >= 112; + $inc = 64 if $_ >= 128; + $inc = 128 if $_ >= 135; + $inc = 256 if $_ >= 147; + $inc = 512 if $_ >= 159; + $inc = 1024 if $_ >= 231; + $len += $inc; +} +close OUT; + +*/ +const uint16_t Protocol::sProtocolStreamHeaderLengths[256] = +{ + 0, // 0 + 1, // 1 + 2, // 2 + 3, // 3 + 4, // 4 + 5, // 5 + 6, // 6 + 7, // 7 + 8, // 8 + 9, // 9 + 10, // 10 + 11, // 11 + 12, // 12 + 13, // 13 + 14, // 14 + 15, // 15 + 16, // 16 + 17, // 17 + 18, // 18 + 19, // 19 + 20, // 20 + 21, // 21 + 22, // 22 + 23, // 23 + 24, // 24 + 25, // 25 + 26, // 26 + 27, // 27 + 28, // 28 + 29, // 29 + 30, // 30 + 31, // 31 + 32, // 32 + 33, // 33 + 34, // 34 + 35, // 35 + 36, // 36 + 37, // 37 + 38, // 38 + 39, // 39 + 40, // 40 + 41, // 41 + 42, // 42 + 43, // 43 + 44, // 44 + 45, // 45 + 46, // 46 + 47, // 47 + 48, // 48 + 49, // 49 + 50, // 50 + 51, // 51 + 52, // 52 + 53, // 53 + 54, // 54 + 55, // 55 + 56, // 56 + 57, // 57 + 58, // 58 + 59, // 59 + 60, // 60 + 61, // 61 + 62, // 62 + 63, // 63 + 64, // 64 + 72, // 65 + 80, // 66 + 88, // 67 + 96, // 68 + 104, // 69 + 112, // 70 + 120, // 71 + 128, // 72 + 136, // 73 + 144, // 74 + 152, // 75 + 160, // 76 + 168, // 77 + 176, // 78 + 184, // 79 + 192, // 80 + 200, // 81 + 208, // 82 + 216, // 83 + 224, // 84 + 232, // 85 + 240, // 86 + 248, // 87 + 256, // 88 + 264, // 89 + 272, // 90 + 280, // 91 + 288, // 92 + 296, // 93 + 304, // 94 + 312, // 95 + 320, // 96 + 336, // 97 + 352, // 98 + 368, // 99 + 384, // 100 + 400, // 101 + 416, // 102 + 432, // 103 + 448, // 104 + 464, // 105 + 480, // 106 + 496, // 107 + 512, // 108 + 528, // 109 + 544, // 110 + 560, // 111 + 576, // 112 + 608, // 113 + 640, // 114 + 672, // 115 + 704, // 116 + 736, // 117 + 768, // 118 + 800, // 119 + 832, // 120 + 864, // 121 + 896, // 122 + 928, // 123 + 960, // 124 + 992, // 125 + 1024, // 126 + 1056, // 127 + 1088, // 128 + 1152, // 129 + 1216, // 130 + 1280, // 131 + 1344, // 132 + 1408, // 133 + 1472, // 134 + 1536, // 135 + 1664, // 136 + 1792, // 137 + 1920, // 138 + 2048, // 139 + 2176, // 140 + 2304, // 141 + 2432, // 142 + 2560, // 143 + 2688, // 144 + 2816, // 145 + 2944, // 146 + 3072, // 147 + 3328, // 148 + 3584, // 149 + 3840, // 150 + 4096, // 151 + 4352, // 152 + 4608, // 153 + 4864, // 154 + 5120, // 155 + 5376, // 156 + 5632, // 157 + 5888, // 158 + 6144, // 159 + 6656, // 160 + 7168, // 161 + 7680, // 162 + 8192, // 163 + 8704, // 164 + 9216, // 165 + 9728, // 166 + 10240, // 167 + 10752, // 168 + 11264, // 169 + 11776, // 170 + 12288, // 171 + 12800, // 172 + 13312, // 173 + 13824, // 174 + 14336, // 175 + 14848, // 176 + 15360, // 177 + 15872, // 178 + 16384, // 179 + 16896, // 180 + 17408, // 181 + 17920, // 182 + 18432, // 183 + 18944, // 184 + 19456, // 185 + 19968, // 186 + 20480, // 187 + 20992, // 188 + 21504, // 189 + 22016, // 190 + 22528, // 191 + 23040, // 192 + 23552, // 193 + 24064, // 194 + 24576, // 195 + 25088, // 196 + 25600, // 197 + 26112, // 198 + 26624, // 199 + 27136, // 200 + 27648, // 201 + 28160, // 202 + 28672, // 203 + 29184, // 204 + 29696, // 205 + 30208, // 206 + 30720, // 207 + 31232, // 208 + 31744, // 209 + 32256, // 210 + 32768, // 211 + 33280, // 212 + 33792, // 213 + 34304, // 214 + 34816, // 215 + 35328, // 216 + 35840, // 217 + 36352, // 218 + 36864, // 219 + 37376, // 220 + 37888, // 221 + 38400, // 222 + 38912, // 223 + 39424, // 224 + 39936, // 225 + 40448, // 226 + 40960, // 227 + 41472, // 228 + 41984, // 229 + 42496, // 230 + 43008, // 231 + 44032, // 232 + 45056, // 233 + 46080, // 234 + 47104, // 235 + 48128, // 236 + 49152, // 237 + 50176, // 238 + 51200, // 239 + 52224, // 240 + 53248, // 241 + 54272, // 242 + 55296, // 243 + 56320, // 244 + 57344, // 245 + 58368, // 246 + 59392, // 247 + 60416, // 248 + 61440, // 249 + 62464, // 250 + 63488, // 251 + 64512, // 252 + 0, // 253 = 65536 / 64k + 0, // 254 = special (reserved) + 0 // 255 = special (reserved) +}; + +int64_t Protocol::GetBytesRead() const +{ + return mapConn->GetBytesRead(); +} + +int64_t Protocol::GetBytesWritten() const +{ + return mapConn->GetBytesWritten(); +} diff --git a/lib/server/Protocol.h b/lib/server/Protocol.h new file mode 100644 index 00000000..fbe6461c --- /dev/null +++ b/lib/server/Protocol.h @@ -0,0 +1,213 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Protocol.h +// Purpose: Generic protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOL__H +#define PROTOCOL__H + +#include + +#include +#include +#include + +#include "Message.h" + +class IOStream; +class SocketStream; + +// default timeout is 15 minutes +#define PROTOCOL_DEFAULT_TIMEOUT (15*60*1000) +// 16 default maximum object size -- should be enough +#define PROTOCOL_DEFAULT_MAXOBJSIZE (16*1024) + +// -------------------------------------------------------------------------- +// +// Class +// Name: Protocol +// Purpose: Generic command / response protocol support +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- +class Protocol +{ +public: + Protocol(std::auto_ptr apConn); + virtual ~Protocol(); + +private: + Protocol(const Protocol &rToCopy); + +protected: + // Unsafe to make public, as they may allow sending objects + // from a different protocol. The derived class prevents this. + std::auto_ptr ReceiveInternal(); + void SendInternal(const Message &rObject); + +public: + void Handshake(); + std::auto_ptr ReceiveStream(); + void SendStream(IOStream &rStream); + + enum + { + NoError = -1, + UnknownError = 0 + }; + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::SetTimeout(int) + // Purpose: Sets the timeout for sending and reciving + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void SetTimeout(int NewTimeout) {mTimeout = NewTimeout;} + + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::GetTimeout() + // Purpose: Get current timeout for sending and receiving + // Created: 2003/09/06 + // + // -------------------------------------------------------------------------- + int GetTimeout() {return mTimeout;} + + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::SetMaxObjectSize(int) + // Purpose: Sets the maximum size of an object which will be accepted + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void SetMaxObjectSize(unsigned int NewMaxObjSize) {mMaxObjectSize = NewMaxObjSize;} + + // For Message derived classes + void Read(void *Buffer, int Size); + void Read(std::string &rOut, int Size); + void Read(int64_t &rOut); + void Read(int32_t &rOut); + void Read(int16_t &rOut); + void Read(int8_t &rOut); + void Read(bool &rOut) {int8_t read; Read(read); rOut = (read == true);} + void Read(std::string &rOut); + template + void Read(type &rOut) + { + rOut.ReadFromProtocol(*this); + } + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::ReadVector(std::vector<> &) + // Purpose: Reads a vector/list of items from the stream + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + template + void ReadVector(std::vector &rOut) + { + rOut.clear(); + int16_t num = 0; + Read(num); + for(int16_t n = 0; n < num; ++n) + { + type v; + Read(v); + rOut.push_back(v); + } + } + + void Write(const void *Buffer, int Size); + void Write(int64_t Value); + void Write(int32_t Value); + void Write(int16_t Value); + void Write(int8_t Value); + void Write(bool Value) {int8_t write = Value; Write(write);} + void Write(const std::string &rValue); + template + void Write(const type &rValue) + { + rValue.WriteToProtocol(*this); + } + template + // -------------------------------------------------------------------------- + // + // Function + // Name: Protocol::WriteVector(const std::vector<> &) + // Purpose: Writes a vector/list of items from the stream + // Created: 2003/08/19 + // + // -------------------------------------------------------------------------- + void WriteVector(const std::vector &rValue) + { + int16_t num = rValue.size(); + Write(num); + for(int16_t n = 0; n < num; ++n) + { + Write(rValue[n]); + } + } + +public: + static const uint16_t sProtocolStreamHeaderLengths[256]; + enum + { + ProtocolStreamHeader_EndOfStream = 0, + ProtocolStreamHeader_MaxEncodedSizeValue = 252, + ProtocolStreamHeader_SizeIs64k = 253, + ProtocolStreamHeader_Reserved1 = 254, + ProtocolStreamHeader_Reserved2 = 255 + }; + enum + { + ProtocolStream_SizeUncertain = 0xffffffff + }; + bool GetLogToSysLog() { return mLogToSysLog; } + FILE *GetLogToFile() { return mLogToFile; } + void SetLogToSysLog(bool Log = false) {mLogToSysLog = Log;} + void SetLogToFile(FILE *File = 0) {mLogToFile = File;} + int64_t GetBytesRead() const; + int64_t GetBytesWritten() const; + +protected: + virtual std::auto_ptr MakeMessage(int ObjType) = 0; + virtual const char *GetProtocolIdentString() = 0; + + void CheckAndReadHdr(void *hdr); // don't use type here to avoid dependency + + // Will be used for logging + virtual void InformStreamReceiving(uint32_t Size); + virtual void InformStreamSending(uint32_t Size); + +private: + void EnsureBufferAllocated(int Size); + int SendStreamSendBlock(uint8_t *Block, int BytesInBlock); + + std::auto_ptr mapConn; + bool mHandshakeDone; + unsigned int mMaxObjectSize; + int mTimeout; + char *mpBuffer; + int mBufferSize; + int mReadOffset; + int mWriteOffset; + int mValidDataSize; + bool mLogToSysLog; + FILE *mLogToFile; +}; + +class ProtocolContext +{ +}; + +#endif // PROTOCOL__H diff --git a/lib/server/ProtocolUncertainStream.cpp b/lib/server/ProtocolUncertainStream.cpp new file mode 100644 index 00000000..aeb15816 --- /dev/null +++ b/lib/server/ProtocolUncertainStream.cpp @@ -0,0 +1,207 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolUncertainStream.h +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- + +#include "Box.h" +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "ProtocolUncertainStream.h" +#include "Protocol.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::ProtocolUncertainStream(IOStream &, int) +// Purpose: Constructor, taking another stream. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +ProtocolUncertainStream::ProtocolUncertainStream(IOStream &rSource) + : mrSource(rSource), + mBytesLeftInCurrentBlock(0), + mFinished(false) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::~ProtocolUncertainStream() +// Purpose: Destructor. Won't absorb any unread bytes. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +ProtocolUncertainStream::~ProtocolUncertainStream() +{ + if(!mFinished) + { + BOX_WARNING("ProtocolUncertainStream destroyed before " + "stream finished"); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::Read(void *, int, int) +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +int ProtocolUncertainStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + // Finished? + if(mFinished) + { + return 0; + } + + int read = 0; + while(read < NBytes) + { + // Anything we can get from the current block? + ASSERT(mBytesLeftInCurrentBlock >= 0); + if(mBytesLeftInCurrentBlock > 0) + { + // Yes, let's use some of these up + int toRead = (NBytes - read); + if(toRead > mBytesLeftInCurrentBlock) + { + // Adjust downwards to only read stuff out of the current block + toRead = mBytesLeftInCurrentBlock; + } + + BOX_TRACE("Reading " << toRead << " bytes from stream"); + + // Read it + int r = mrSource.Read(((uint8_t*)pBuffer) + read, toRead, Timeout); + // Give up now if it didn't return anything + if(r == 0) + { + BOX_TRACE("Read " << r << " bytes from " + "stream, returning"); + return read; + } + + // Adjust counts of bytes by the bytes recieved + read += r; + mBytesLeftInCurrentBlock -= r; + + // stop now if the stream returned less than we asked for -- avoid blocking + if(r != toRead) + { + BOX_TRACE("Read " << r << " bytes from " + "stream, returning"); + return read; + } + } + else + { + // Read the header byte to find out how much there is + // in the next block + uint8_t header; + if(mrSource.Read(&header, 1, Timeout) == 0) + { + // Didn't get the byte, return now + BOX_TRACE("Read 0 bytes of block header, " + "returning with " << read << " bytes " + "read this time"); + return read; + } + + // Interpret the byte... + if(header == Protocol::ProtocolStreamHeader_EndOfStream) + { + // All done. + mFinished = true; + BOX_TRACE("Stream finished, returning with " << + read << " bytes read this time"); + return read; + } + else if(header <= Protocol::ProtocolStreamHeader_MaxEncodedSizeValue) + { + // get size of the block from the Protocol's lovely list + mBytesLeftInCurrentBlock = Protocol::sProtocolStreamHeaderLengths[header]; + } + else if(header == Protocol::ProtocolStreamHeader_SizeIs64k) + { + // 64k + mBytesLeftInCurrentBlock = (64*1024); + } + else + { + // Bad. It used the reserved values. + THROW_EXCEPTION(ServerException, ProtocolUncertainStreamBadBlockHeader) + } + + BOX_TRACE("Read header byte " << (int)header << ", " + "next block has " << + mBytesLeftInCurrentBlock << " bytes"); + } + } + + // Return the number read + return read; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::BytesLeftToRead() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +IOStream::pos_type ProtocolUncertainStream::BytesLeftToRead() +{ + // Only know how much is left if everything is finished + return mFinished?(0):(IOStream::SizeOfStreamUnknown); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::Write(const void *, int) +// Purpose: As interface. But will exception. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +void ProtocolUncertainStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + THROW_EXCEPTION(ServerException, CantWriteToProtocolUncertainStream) +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::StreamDataLeft() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +bool ProtocolUncertainStream::StreamDataLeft() +{ + return !mFinished; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ProtocolUncertainStream::StreamClosed() +// Purpose: As interface. +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +bool ProtocolUncertainStream::StreamClosed() +{ + // always closed + return true; +} + diff --git a/lib/server/ProtocolUncertainStream.h b/lib/server/ProtocolUncertainStream.h new file mode 100644 index 00000000..2e97ba6a --- /dev/null +++ b/lib/server/ProtocolUncertainStream.h @@ -0,0 +1,48 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolUncertainStream.h +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLUNCERTAINSTREAM__H +#define PROTOCOLUNCERTAINSTREAM__H + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ProtocolUncertainStream +// Purpose: Read part of another stream +// Created: 2003/12/05 +// +// -------------------------------------------------------------------------- +class ProtocolUncertainStream : public IOStream +{ +public: + ProtocolUncertainStream(IOStream &rSource); + ~ProtocolUncertainStream(); +private: + // no copying allowed + ProtocolUncertainStream(const IOStream &); + ProtocolUncertainStream(const ProtocolUncertainStream &); + +public: + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual pos_type BytesLeftToRead(); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + +private: + IOStream &mrSource; + int mBytesLeftInCurrentBlock; + bool mFinished; +}; + +#endif // PROTOCOLUNCERTAINSTREAM__H + diff --git a/lib/server/ProtocolWire.h b/lib/server/ProtocolWire.h new file mode 100644 index 00000000..6dee445b --- /dev/null +++ b/lib/server/ProtocolWire.h @@ -0,0 +1,43 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ProtocolWire.h +// Purpose: On the wire structures for Protocol +// Created: 2003/08/19 +// +// -------------------------------------------------------------------------- + +#ifndef PROTOCOLWIRE__H +#define PROTOCOLWIRE__H + +#include + +// set packing to one byte +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "BeginStructPackForWire.h" +#else +BEGIN_STRUCTURE_PACKING_FOR_WIRE +#endif + +typedef struct +{ + char mIdent[32]; +} PW_Handshake; + +typedef struct +{ + uint32_t mObjSize; + uint32_t mObjType; +} PW_ObjectHeader; + +#define SPECIAL_STREAM_OBJECT_TYPE 0xffffffff + +// Use default packing +#ifdef STRUCTURE_PACKING_FOR_WIRE_USE_HEADERS +#include "EndStructPackForWire.h" +#else +END_STRUCTURE_PACKING_FOR_WIRE +#endif + +#endif // PROTOCOLWIRE__H + diff --git a/lib/server/SSLLib.cpp b/lib/server/SSLLib.cpp new file mode 100644 index 00000000..1bcadb0d --- /dev/null +++ b/lib/server/SSLLib.cpp @@ -0,0 +1,94 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SSLLib.cpp +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include +#include +#include + +#ifdef WIN32 + #include +#endif + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "CryptoUtils.h" +#include "SSLLib.h" + +#include "MemLeakFindOn.h" + +#ifndef BOX_RELEASE_BUILD + bool SSLLib__TraceErrors = false; +#endif + +// -------------------------------------------------------------------------- +// +// Function +// Name: SSLLib::Initialise() +// Purpose: Initialise SSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SSLLib::Initialise() +{ + if(!::SSL_library_init()) + { + THROW_EXCEPTION_MESSAGE(ServerException, + SSLLibraryInitialisationError, + CryptoUtils::LogError("initialising OpenSSL")); + } + + // More helpful error messages + ::SSL_load_error_strings(); + + // Extra seeding over and above what's already done by the library +#ifdef WIN32 + HCRYPTPROV provider; + if(!CryptAcquireContext(&provider, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT)) + { + BOX_LOG_WIN_ERROR("Failed to acquire crypto context"); + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); + } + else + { + // must free provider + BYTE buf[1024]; + + if(!CryptGenRandom(provider, sizeof(buf), buf)) + { + BOX_LOG_WIN_ERROR("Failed to get random data"); + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); + } + else + { + RAND_seed(buf, sizeof(buf)); + } + + if(!CryptReleaseContext(provider, 0)) + { + BOX_LOG_WIN_ERROR("Failed to release crypto context"); + } + } +#elif defined HAVE_RANDOM_DEVICE + if(::RAND_load_file(RANDOM_DEVICE, 1024) != 1024) + { + THROW_EXCEPTION(ServerException, SSLRandomInitFailed) + } +#else + BOX_WARNING("No random device -- additional seeding of " + "random number generator not performed."); +#endif +} + + diff --git a/lib/server/SSLLib.h b/lib/server/SSLLib.h new file mode 100644 index 00000000..d11c7804 --- /dev/null +++ b/lib/server/SSLLib.h @@ -0,0 +1,35 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SSLLib.h +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SSLLIB__H +#define SSLLIB__H + +#ifndef BOX_RELEASE_BUILD + extern bool SSLLib__TraceErrors; + #define SET_DEBUG_SSLLIB_TRACE_ERRORS {SSLLib__TraceErrors = true;} +#else + #define SET_DEBUG_SSLLIB_TRACE_ERRORS +#endif + + +// -------------------------------------------------------------------------- +// +// Namespace +// Name: SSLLib +// Purpose: Utility functions for dealing with the OpenSSL library +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +namespace SSLLib +{ + void Initialise(); +}; + +#endif // SSLLIB__H + diff --git a/lib/server/ServerControl.cpp b/lib/server/ServerControl.cpp new file mode 100644 index 00000000..f1a718df --- /dev/null +++ b/lib/server/ServerControl.cpp @@ -0,0 +1,290 @@ +#include "Box.h" + +#include +#include + +#ifdef HAVE_SYS_TYPES_H + #include +#endif + +#ifdef HAVE_SYS_WAIT_H + #include +#endif + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#include "BoxTime.h" +#include "IOStreamGetLine.h" +#include "ServerControl.h" +#include "Test.h" + +#ifdef WIN32 + +#include "WinNamedPipeStream.h" +#include "BoxPortsAndFiles.h" + +static std::string sPipeName; + +void SetNamedPipeName(const std::string& rPipeName) +{ + sPipeName = rPipeName; +} + +bool SendCommands(const std::string& rCmd) +{ + WinNamedPipeStream connection; + + try + { + connection.Connect(sPipeName); + } + catch(...) + { + BOX_ERROR("Failed to connect to daemon control socket"); + return false; + } + + // For receiving data + IOStreamGetLine getLine(connection); + + // Wait for the configuration summary + std::string configSummary; + if(!getLine.GetLine(configSummary)) + { + BOX_ERROR("Failed to receive configuration summary from daemon"); + return false; + } + + // Was the connection rejected by the server? + if(getLine.IsEOF()) + { + BOX_ERROR("Server rejected the connection"); + return false; + } + + // Decode it + int autoBackup, updateStoreInterval, minimumFileAge, maxUploadWait; + if(::sscanf(configSummary.c_str(), "bbackupd: %d %d %d %d", + &autoBackup, &updateStoreInterval, + &minimumFileAge, &maxUploadWait) != 4) + { + BOX_ERROR("Config summary didn't decode"); + return false; + } + + std::string cmds; + bool expectResponse; + + if (rCmd != "") + { + cmds = rCmd; + cmds += "\nquit\n"; + expectResponse = true; + } + else + { + cmds = "quit\n"; + expectResponse = false; + } + + connection.Write(cmds.c_str(), cmds.size()); + + // Read the response + std::string line; + bool statusOk = !expectResponse; + + while (expectResponse && !getLine.IsEOF() && getLine.GetLine(line)) + { + // Is this an OK or error line? + if (line == "ok") + { + statusOk = true; + } + else if (line == "error") + { + BOX_ERROR(rCmd); + break; + } + else + { + BOX_WARNING("Unexpected response to command '" << + rCmd << "': " << line) + } + } + + return statusOk; +} + +bool HUPServer(int pid) +{ + return SendCommands("reload"); +} + +bool KillServerInternal(int pid) +{ + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, false, pid); + if (hProcess == NULL) + { + BOX_ERROR("Failed to open process " << pid << ": " << + GetErrorMessage(GetLastError())); + return false; + } + + if (!TerminateProcess(hProcess, 1)) + { + BOX_ERROR("Failed to terminate process " << pid << ": " << + GetErrorMessage(GetLastError())); + CloseHandle(hProcess); + return false; + } + + CloseHandle(hProcess); + return true; +} + +#else // !WIN32 + +bool HUPServer(int pid) +{ + if(pid == 0) return false; + return ::kill(pid, SIGHUP) == 0; +} + +bool KillServerInternal(int pid) +{ + if(pid == 0 || pid == -1) return false; + bool killed = (::kill(pid, SIGTERM) == 0); + if (!killed) + { + BOX_LOG_SYS_ERROR("Failed to kill process " << pid); + } + TEST_THAT(killed); + return killed; +} + +#endif // WIN32 + +bool KillServer(int pid, bool WaitForProcess) +{ + if (!KillServerInternal(pid)) + { + return false; + } + + #ifdef HAVE_WAITPID + if (WaitForProcess) + { + int status, result; + + result = waitpid(pid, &status, 0); + if (result != pid) + { + BOX_LOG_SYS_ERROR("waitpid failed"); + } + TEST_THAT(result == pid); + + TEST_THAT(WIFEXITED(status)); + if (WIFEXITED(status)) + { + if (WEXITSTATUS(status) != 0) + { + BOX_WARNING("process exited with code " << + WEXITSTATUS(status)); + } + TEST_THAT(WEXITSTATUS(status) == 0); + } + } + #endif + + printf("Waiting for server to die (pid %d): ", pid); + + for (int i = 0; i < 300; i++) + { + if (i % 10 == 0) + { + printf("."); + fflush(stdout); + } + + if (!ServerIsAlive(pid)) break; + ShortSleep(MilliSecondsToBoxTime(100), false); + if (!ServerIsAlive(pid)) break; + } + + if (!ServerIsAlive(pid)) + { + printf(" done.\n"); + } + else + { + printf(" failed!\n"); + } + + fflush(stdout); + + return !ServerIsAlive(pid); +} + +bool KillServer(std::string pid_file, bool WaitForProcess) +{ + FileStream fs(pid_file); + IOStreamGetLine getline(fs); + std::string line = getline.GetLine(); + int pid = atoi(line.c_str()); + bool status = KillServer(pid, WaitForProcess); + TEST_EQUAL_LINE(true, status, std::string("kill(") + pid_file + ")"); + +#ifdef WIN32 + if(WaitForProcess) + { + int unlink_result = unlink(pid_file.c_str()); + TEST_EQUAL_LINE(0, unlink_result, std::string("unlink ") + pid_file); + if(unlink_result != 0) + { + return false; + } + } +#endif + + return status; +} + +int StartDaemon(int current_pid, const std::string& cmd_line, const char* pid_file) +{ + TEST_THAT_OR(current_pid == 0, return 0); + + int new_pid = LaunchServer(cmd_line, pid_file); + TEST_THAT_OR(new_pid != -1 && new_pid != 0, return 0); + + ::sleep(1); + TEST_THAT_OR(ServerIsAlive(new_pid), return 0); + return new_pid; +} + +bool StopDaemon(int current_pid, const std::string& pid_file, + const std::string& memleaks_file, bool wait_for_process) +{ + TEST_THAT_OR(current_pid != 0, return false); + TEST_THAT_OR(ServerIsAlive(current_pid), return false); + TEST_THAT_OR(KillServer(current_pid, wait_for_process), return false); + ::sleep(1); + + TEST_THAT_OR(!ServerIsAlive(current_pid), return false); + + #ifdef WIN32 + int unlink_result = unlink(pid_file.c_str()); + TEST_EQUAL_LINE(0, unlink_result, std::string("unlink ") + pid_file); + if(unlink_result != 0) + { + return false; + } + #else + TestRemoteProcessMemLeaks(memleaks_file.c_str()); + #endif + + return true; +} + + diff --git a/lib/server/ServerControl.h b/lib/server/ServerControl.h new file mode 100644 index 00000000..be2464c1 --- /dev/null +++ b/lib/server/ServerControl.h @@ -0,0 +1,22 @@ +#ifndef SERVER_CONTROL_H +#define SERVER_CONTROL_H + +#include "Test.h" + +bool HUPServer(int pid); +bool KillServer(int pid, bool WaitForProcess = false); +bool KillServer(std::string pid_file, bool WaitForProcess = false); +int StartDaemon(int current_pid, const std::string& cmd_line, const char* pid_file); +bool StopDaemon(int current_pid, const std::string& pid_file, + const std::string& memleaks_file, bool wait_for_process); + +#ifdef WIN32 + #include "WinNamedPipeStream.h" + #include "IOStreamGetLine.h" + #include "BoxPortsAndFiles.h" + + void SetNamedPipeName(const std::string& rPipeName); + // bool SendCommands(const std::string& rCmd); +#endif // WIN32 + +#endif // SERVER_CONTROL_H diff --git a/lib/server/ServerException.txt b/lib/server/ServerException.txt new file mode 100644 index 00000000..474b4067 --- /dev/null +++ b/lib/server/ServerException.txt @@ -0,0 +1,39 @@ +EXCEPTION Server 3 + +# for historic reasons, some codes are not used + +Internal 0 +FailedToLoadConfiguration 1 +DaemoniseFailed 2 +AlreadyDaemonConstructed 3 +BadSocketHandle 4 +DupError 5 +SocketAlreadyOpen 8 +SocketOpenError 10 +SocketPollError 11 +SocketCloseError 13 +SocketNameUNIXPathTooLong 14 +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 +SocketAcceptError 17 +ServerStreamBadListenAddrs 18 +ServerForkError 19 +ServerWaitOnChildError 20 +TooManySocketsInMultiListen 21 There is a limit on how many addresses you can listen on simulatiously. +ServerStreamTooManyListenAddresses 22 +TLSContextNotInitialised 23 +TLSAllocationFailed 24 +TLSLoadCertificatesFailed 25 +TLSLoadPrivateKeyFailed 26 +TLSLoadTrustedCAsFailed 27 +TLSSetCiphersFailed 28 +SSLLibraryInitialisationError 29 +TLSNoSSLObject 31 +TLSAlreadyHandshaked 35 +SocketSetNonBlockingFailed 40 +Protocol_BadUsage 43 +Protocol_UnsuitableStreamTypeForSending 51 +CantWriteToProtocolUncertainStream 53 +ProtocolUncertainStreamBadBlockHeader 54 +SocketPairFailed 55 +CouldNotChangePIDFileOwner 56 +SSLRandomInitFailed 57 Read from /dev/*random device failed diff --git a/lib/server/ServerStream.h b/lib/server/ServerStream.h new file mode 100644 index 00000000..3f6eed7e --- /dev/null +++ b/lib/server/ServerStream.h @@ -0,0 +1,446 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ServerStream.h +// Purpose: Stream based server daemons +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SERVERSTREAM__H +#define SERVERSTREAM__H + +#include +#include + +#ifndef WIN32 + #include +#endif + +#include "autogen_ServerException.h" +#include "Daemon.h" +#include "SocketListen.h" +#include "Utils.h" +#include "Configuration.h" +#include "WaitForEvent.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ServerStream +// Purpose: Stream based server daemon +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +template +class ServerStream : public Daemon +{ +public: + ServerStream() + { + } + ~ServerStream() + { + DeleteSockets(); + } +private: + ServerStream(const ServerStream &rToCopy) + { + } + + std::string mConnectionDetails; + +protected: + const std::string& GetConnectionDetails() + { + return mConnectionDetails; + } + +public: + + virtual const char *DaemonName() const + { + return "generic-stream-server"; + } + + virtual void OnIdle() { } + + virtual void Run() + { + // Set process title as appropriate + SetProcessTitle(ForkToHandleRequests?"server":"idle"); + + // Handle exceptions and child task quitting gracefully. + bool childExit = false; + try + { + Run2(childExit); + } + catch(BoxException &e) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what() << "(" << e.GetType() << + "/" << e.GetSubType() << ")"); + _exit(1); + } + else throw; + } + catch(std::exception &e) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: exception " << + e.what()); + _exit(1); + } + else throw; + } + catch(...) + { + if(childExit) + { + BOX_ERROR("Error in child process, " + "terminating connection: " + "unknown exception"); + _exit(1); + } + else throw; + } + + // if it's a child fork, exit the process now + if(childExit) + { + // Child task, dump leaks to trace, which we make sure is on + #ifdef BOX_MEMORY_LEAK_TESTING + #ifndef BOX_RELEASE_BUILD + TRACE_TO_SYSLOG(true); + TRACE_TO_STDOUT(true); + #endif + memleakfinder_traceblocksinsection(); + #endif + + // If this is a child quitting, exit now to stop bad things happening + _exit(0); + } + } + +protected: + virtual void NotifyListenerIsReady() { } + virtual void LogConnectionDetails(std::string details) + { + BOX_NOTICE("Handling incoming connection from " << details); + } + +public: + virtual void Run2(bool &rChildExit) + { + try + { + // Wait object with a timeout of 1 second, which + // is a reasonable time to wait before cleaning up + // finished child processes, and allows the daemon + // to terminate reasonably quickly on request. + WaitForEvent connectionWait(1000); + + // BLOCK + { + // Get the address we need to bind to + // this-> in next line required to build under some gcc versions + const Configuration &config(this->GetConfiguration()); + const Configuration &server(config.GetSubConfiguration("Server")); + std::string addrs = server.GetKeyValue("ListenAddresses"); + + // split up the list of addresses + std::vector addrlist; + SplitString(addrs, ',', addrlist); + + for(unsigned int a = 0; a < addrlist.size(); ++a) + { + // split the address up into components + std::vector c; + SplitString(addrlist[a], ':', c); + + // listen! + SocketListen *psocket = new SocketListen; + try + { + if(c[0] == "inet") + { + // Check arguments + if(c.size() != 2 && c.size() != 3) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + // Which port? + int port = Port; + + if(c.size() == 3) + { + // Convert to number + port = ::atol(c[2].c_str()); + if(port <= 0 || port > ((64*1024)-1)) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + } + + // Listen + psocket->Listen(Socket::TypeINET, c[1].c_str(), port); + } + else if(c[0] == "unix") + { + #ifdef WIN32 + BOX_WARNING("Ignoring request to listen on a Unix socket on Windows: " << addrlist[a]); + delete psocket; + psocket = NULL; + #else + // Check arguments size + if(c.size() != 2) + { + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + // unlink anything there + ::unlink(c[1].c_str()); + + psocket->Listen(Socket::TypeUNIX, c[1].c_str()); + #endif // WIN32 + } + else + { + delete psocket; + THROW_EXCEPTION(ServerException, ServerStreamBadListenAddrs) + } + + if (psocket != NULL) + { + // Add to list of sockets + mSockets.push_back(psocket); + } + } + catch(...) + { + delete psocket; + throw; + } + + if (psocket != NULL) + { + // Add to the list of things to wait on + connectionWait.Add(psocket); + } + } + } + + NotifyListenerIsReady(); + + while(!StopRun()) + { + // Wait for a connection, or timeout + SocketListen *psocket + = (SocketListen *)connectionWait.Wait(); + + if(psocket) + { + // Get the incoming connection + // (with zero wait time) + std::auto_ptr connection( + psocket->Accept(0, + &mConnectionDetails)); + + // Was there one (there should be...) + if(connection.get()) + { + // Since this is a template parameter, the if() will be optimised out by the compiler + #ifndef WIN32 // no fork on Win32 + if(ForkToHandleRequests && !IsSingleProcess()) + { + pid_t pid = ::fork(); + switch(pid) + { + case -1: + // Error! + THROW_EXCEPTION(ServerException, ServerForkError) + break; + + case 0: + // Child process + rChildExit = true; + // Close listening sockets + DeleteSockets(); + + // Set up daemon + EnterChild(); + SetProcessTitle("transaction"); + LogConnectionDetails(mConnectionDetails); + + // Memory leak test the forked process + #ifdef BOX_MEMORY_LEAK_TESTING + memleakfinder_startsectionmonitor(); + #endif + + // The derived class does some server magic with the connection + HandleConnection(connection); + // Since rChildExit == true, the forked process will call _exit() on return from this fn + return; + + default: + // parent daemon process + break; + } + + // Log it + BOX_TRACE("Forked child process " << pid << + " to handle connection from " << + mConnectionDetails); + } + else + { + #endif // !WIN32 + // Just handle in this process + SetProcessTitle("handling"); + HandleConnection(connection); + SetProcessTitle("idle"); + #ifndef WIN32 + } + #endif // !WIN32 + } + } + + OnIdle(); + + #ifndef WIN32 + // Clean up child processes (if forking daemon) + if(ForkToHandleRequests && !IsSingleProcess()) + { + WaitForChildren(); + } + #endif // !WIN32 + } + } + catch(...) + { + DeleteSockets(); + throw; + } + + // Delete the sockets + DeleteSockets(); + } + + #ifndef WIN32 // no waitpid() on Windows + void WaitForChildren() + { + int p = 0; + do + { + int status = 0; + p = ::waitpid(0 /* any child in process group */, + &status, WNOHANG); + + if(p == -1) + { + if (errno == ECHILD || errno == EINTR) + { + // Nothing actually happened, so there's no reason + // to wait again. + break; + } + else + { + THROW_SYS_ERROR("Failed to wait for daemon child " + "process", ServerException, + ServerWaitOnChildError); + } + } + else if(p == 0) + { + // no children exited, will return from + // function + } + else if(WIFEXITED(status)) + { + BOX_INFO("child process " << p << " " + "terminated normally"); + } + else if(WIFSIGNALED(status)) + { + int sig = WTERMSIG(status); + BOX_ERROR("child process " << p << " " + "terminated abnormally with " + "signal " << sig); + } + else + { + BOX_WARNING("something unknown happened " + "to child process " << p << ": " + "status = " << status); + } + } + while(p > 0); + } + #endif + + virtual void HandleConnection(std::auto_ptr apStream) + { + Connection(apStream); + } + + virtual void Connection(std::auto_ptr apStream) = 0; + +protected: + // For checking code in derived classes -- use if you have an algorithm which + // depends on the forking model in case someone changes it later. + bool WillForkToHandleRequests() + { + #ifdef WIN32 + return false; + #else + return ForkToHandleRequests && !IsSingleProcess(); + #endif // WIN32 + } + +private: + // -------------------------------------------------------------------------- + // + // Function + // Name: ServerStream::DeleteSockets() + // Purpose: Delete sockets + // Created: 9/3/04 + // + // -------------------------------------------------------------------------- + void DeleteSockets() + { + for(unsigned int l = 0; l < mSockets.size(); ++l) + { + if(mSockets[l]) + { + mSockets[l]->Close(); + delete mSockets[l]; + } + mSockets[l] = 0; + } + mSockets.clear(); + } + +private: + std::vector *> mSockets; +}; + +#define SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + ConfigurationVerifyKey("ListenAddresses", 0, DEFAULT_ADDRESSES), \ + DAEMON_VERIFY_SERVER_KEYS + +#include "MemLeakFindOff.h" + +#endif // SERVERSTREAM__H + + + diff --git a/lib/server/ServerTLS.h b/lib/server/ServerTLS.h new file mode 100644 index 00000000..f748f4b2 --- /dev/null +++ b/lib/server/ServerTLS.h @@ -0,0 +1,81 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: ServerTLS.h +// Purpose: Implementation of a server using TLS streams +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SERVERTLS__H +#define SERVERTLS__H + +#include "ServerStream.h" +#include "SocketStreamTLS.h" +#include "SSLLib.h" +#include "TLSContext.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: ServerTLS +// Purpose: Implementation of a server using TLS streams +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +template +class ServerTLS : public ServerStream +{ +public: + ServerTLS() + { + // Safe to call this here, as the Daemon class makes sure there is only one instance every of a Daemon. + SSLLib::Initialise(); + } + + ~ServerTLS() + { + } +private: + ServerTLS(const ServerTLS &) + { + } +public: + + virtual void Run2(bool &rChildExit) + { + // First, set up the SSL context. + // Get parameters from the configuration + // this-> in next line required to build under some gcc versions + const Configuration &conf(this->GetConfiguration()); + const Configuration &serverconf(conf.GetSubConfiguration("Server")); + std::string certFile(serverconf.GetKeyValue("CertificateFile")); + std::string keyFile(serverconf.GetKeyValue("PrivateKeyFile")); + std::string caFile(serverconf.GetKeyValue("TrustedCAsFile")); + mContext.Initialise(true /* as server */, certFile.c_str(), + keyFile.c_str(), caFile.c_str()); + + // Then do normal stream server stuff + ServerStream::Run2(rChildExit); + } + + virtual void HandleConnection(std::auto_ptr apStream) + { + apStream->Handshake(mContext, true /* is server */); + // this-> in next line required to build under some gcc versions + this->Connection(apStream); + } + +private: + TLSContext mContext; +}; + +#define SERVERTLS_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) \ + ConfigurationVerifyKey("CertificateFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("PrivateKeyFile", ConfigTest_Exists), \ + ConfigurationVerifyKey("TrustedCAsFile", ConfigTest_Exists), \ + SERVERSTREAM_VERIFY_SERVER_KEYS(DEFAULT_ADDRESSES) + +#endif // SERVERTLS__H + diff --git a/lib/server/Socket.cpp b/lib/server/Socket.cpp new file mode 100644 index 00000000..c9c1773d --- /dev/null +++ b/lib/server/Socket.cpp @@ -0,0 +1,171 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Socket.cpp +// Purpose: Socket related stuff +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#ifndef WIN32 +#include +#include +#include +#include +#endif + +#include +#include + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "Socket.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::NameLookupToSockAddr(SocketAllAddr &, int, +// char *, int) +// Purpose: Sets up a sockaddr structure given a name and type +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void Socket::NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, + enum Type Type, const std::string& rName, int Port, + int &rSockAddrLenOut) +{ + int sockAddrLen = 0; + + switch(Type) + { + case TypeINET: + sockDomain = AF_INET; + { + // Lookup hostname + struct hostent *phost = ::gethostbyname(rName.c_str()); + if(phost != NULL) + { + if(phost->h_addr_list[0] != 0) + { + sockAddrLen = sizeof(addr.sa_inet); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + addr.sa_inet.sin_len = sizeof(addr.sa_inet); +#endif + addr.sa_inet.sin_family = PF_INET; + addr.sa_inet.sin_port = htons(Port); + addr.sa_inet.sin_addr = *((in_addr*)phost->h_addr_list[0]); + for(unsigned int l = 0; l < sizeof(addr.sa_inet.sin_zero); ++l) + { + addr.sa_inet.sin_zero[l] = 0; + } + } + else + { + THROW_EXCEPTION(ConnectionException, SocketNameLookupError); + } + } + else + { + THROW_EXCEPTION(ConnectionException, SocketNameLookupError); + } + } + break; + +#ifndef WIN32 + case TypeUNIX: + sockDomain = AF_UNIX; + { + // Check length of name is OK + unsigned int nameLen = rName.length(); + if(nameLen >= (sizeof(addr.sa_unix.sun_path) - 1)) + { + THROW_EXCEPTION(ServerException, SocketNameUNIXPathTooLong); + } + sockAddrLen = nameLen + (((char*)(&(addr.sa_unix.sun_path[0]))) - ((char*)(&addr.sa_unix))); +#ifdef HAVE_STRUCT_SOCKADDR_IN_SIN_LEN + addr.sa_unix.sun_len = sockAddrLen; +#endif + addr.sa_unix.sun_family = PF_UNIX; + ::strncpy(addr.sa_unix.sun_path, rName.c_str(), + sizeof(addr.sa_unix.sun_path) - 1); + addr.sa_unix.sun_path[sizeof(addr.sa_unix.sun_path)-1] = 0; + } + break; +#endif + + default: + THROW_EXCEPTION(CommonException, BadArguments) + break; + } + + // Return size of structure to caller + rSockAddrLenOut = sockAddrLen; +} + + + + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::LogIncomingConnection(const struct sockaddr *, socklen_t) +// Purpose: Writes a message logging the connection to syslog +// Created: 2003/08/01 +// +// -------------------------------------------------------------------------- +void Socket::LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen) +{ + BOX_INFO("Incoming connection from " << + IncomingConnectionLogMessage(addr, addrlen)); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: Socket::IncomingConnectionLogMessage(const struct sockaddr *, socklen_t) +// Purpose: Returns a string for use in log messages +// Created: 2003/08/01 +// +// -------------------------------------------------------------------------- +std::string Socket::IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen) +{ + if(addr == NULL) {THROW_EXCEPTION(CommonException, BadArguments)} + + switch(addr->sa_family) + { + case AF_UNIX: + return std::string("local (UNIX socket)"); + break; + + case AF_INET: + { + sockaddr_in *a = (sockaddr_in*)addr; + std::ostringstream oss; + oss << inet_ntoa(a->sin_addr) << " port " << + ntohs(a->sin_port); + return oss.str(); + } + break; + + default: + { + std::ostringstream oss; + oss << "unknown socket type " << addr->sa_family; + return oss.str(); + } + break; + } + + // Dummy. + return std::string(); +} + diff --git a/lib/server/Socket.h b/lib/server/Socket.h new file mode 100644 index 00000000..5034dbd8 --- /dev/null +++ b/lib/server/Socket.h @@ -0,0 +1,56 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: Socket.h +// Purpose: Socket related stuff +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKET__H +#define SOCKET__H + +#ifdef WIN32 +#include "emu.h" +typedef int socklen_t; +#else +#include +#include +#include +#endif + +#include + +typedef union { + struct sockaddr sa_generic; + struct sockaddr_in sa_inet; +#ifndef WIN32 + struct sockaddr_un sa_unix; +#endif +} SocketAllAddr; + +// -------------------------------------------------------------------------- +// +// Namespace +// Name: Socket +// Purpose: Socket utilities +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +namespace Socket +{ + enum Type + { + TypeINET = 1, + TypeUNIX = 2 + }; + + void NameLookupToSockAddr(SocketAllAddr &addr, int &sockDomain, + enum Type type, const std::string& rName, int Port, + int &rSockAddrLenOut); + void LogIncomingConnection(const struct sockaddr *addr, socklen_t addrlen); + std::string IncomingConnectionLogMessage(const struct sockaddr *addr, socklen_t addrlen); +}; + +#endif // SOCKET__H + diff --git a/lib/server/SocketListen.h b/lib/server/SocketListen.h new file mode 100644 index 00000000..39fe7e24 --- /dev/null +++ b/lib/server/SocketListen.h @@ -0,0 +1,332 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketListen.h +// Purpose: Stream based sockets for servers +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETLISTEN__H +#define SOCKETLISTEN__H + +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#ifdef HAVE_KQUEUE + #include + #include +#endif + +#ifndef WIN32 + #include +#endif + +#include +#include +#include + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "Socket.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: _NoSocketLocking +// Purpose: Default locking class for SocketListen +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class _NoSocketLocking +{ +public: + _NoSocketLocking(int sock) + { + } + + ~_NoSocketLocking() + { + } + + bool HaveLock() + { + return true; + } + +private: + _NoSocketLocking(const _NoSocketLocking &rToCopy) + { + } +}; + + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketListen +// Purpose: +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +template +class SocketListen +{ +public: + // Initialise + SocketListen() + : mSocketHandle(-1) + { + } + // Close socket nicely + ~SocketListen() + { + Close(); + } + +private: + SocketListen(const SocketListen &rToCopy) + { + } + + int mType, mPort; + std::string mName; + +public: + enum + { + MaxMultipleListenSockets = MaxMultiListenSockets + }; + + void Close() + { + if(mSocketHandle != -1) + { +#ifdef WIN32 + if(::closesocket(mSocketHandle) == -1) +#else + if(::close(mSocketHandle) == -1) +#endif + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketCloseError, + BOX_SOCKET_ERROR_MESSAGE(mType, mName, mPort, + "Failed to close network socket")); + } + } + mSocketHandle = -1; + } + + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::Listen(int, char*, int) + // Purpose: Initialises, starts the socket listening. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + void Listen(Socket::Type Type, const char *Name, int Port = 0) + { + mType = Type; + mName = Name; + mPort = Port; + + if(mSocketHandle != -1) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen); + } + + // Setup parameters based on type, looking up names if required + int sockDomain = 0; + SocketAllAddr addr; + int addrLen = 0; + Socket::NameLookupToSockAddr(addr, sockDomain, Type, Name, + Port, addrLen); + + // Create the socket + mSocketHandle = ::socket(sockDomain, SOCK_STREAM, + 0 /* let OS choose protocol */); + if(mSocketHandle == -1) + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketOpenError, + BOX_SOCKET_ERROR_MESSAGE(Type, Name, Port, + "Failed to create a network socket")); + } + + // Set an option to allow reuse (useful for -HUP situations!) +#ifdef WIN32 + if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, "", + 0) == -1) +#else + int option = true; + if(::setsockopt(mSocketHandle, SOL_SOCKET, SO_REUSEADDR, + &option, sizeof(option)) == -1) +#endif + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketOpenError, + BOX_SOCKET_ERROR_MESSAGE(Type, Name, Port, + "Failed to set socket options")); + } + + // Bind it to the right port, and start listening + if(::bind(mSocketHandle, &addr.sa_generic, addrLen) == -1 + || ::listen(mSocketHandle, ListenBacklog) == -1) + { + try + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketOpenError, + BOX_SOCKET_ERROR_MESSAGE(Type, Name, Port, + "Failed to bind socket to name/port")); + } + catch(ServerException &e) // finally + { + // Dispose of the socket + Close(); + throw; + } + } + } + + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::Accept(int) + // Purpose: Accepts a connection, returning a pointer to + // a class of the specified type. May return a + // null pointer if a signal happens, or there's + // a timeout. Timeout specified in + // milliseconds, defaults to infinite time. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + std::auto_ptr Accept(int Timeout = INFTIM, + std::string *pLogMsg = 0) + { + if(mSocketHandle == -1) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + // Do the accept, using the supplied locking type + int sock; + struct sockaddr addr; + socklen_t addrlen = sizeof(addr); + // BLOCK + { + SocketLockingType socklock(mSocketHandle); + + if(!socklock.HaveLock()) + { + // Didn't get the lock for some reason. + // Wait a while, then return nothing. + BOX_ERROR("Failed to get a lock on incoming " + "connection"); + ::sleep(1); + return std::auto_ptr(); + } + + // poll this socket + struct pollfd p; + p.fd = mSocketHandle; + p.events = POLLIN; + p.revents = 0; + switch(::poll(&p, 1, Timeout)) + { + case -1: + // signal? + if(errno == EINTR) + { + BOX_INFO("Failed to accept " + "connection: interrupted by " + "signal"); + // return nothing + return std::auto_ptr(); + } + else + { + THROW_EXCEPTION_MESSAGE(ServerException, + SocketPollError, + BOX_SOCKET_ERROR_MESSAGE(mType, mName, + mPort, "Failed to poll connection")); + } + break; + case 0: // timed out + return std::auto_ptr(); + break; + default: // got some thing... + // control flows on... + break; + } + + sock = ::accept(mSocketHandle, &addr, &addrlen); + } + + // Got socket (or error), unlock (implicit in destruction) + if(sock == -1) + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketAcceptError, + BOX_SOCKET_ERROR_MESSAGE(mType, mName, + mPort, "Failed to accept connection")); + } + + // Log it + if(pLogMsg) + { + *pLogMsg = Socket::IncomingConnectionLogMessage(&addr, + addrlen); + } + else + { + // Do logging ourselves + Socket::LogIncomingConnection(&addr, addrlen); + } + + return std::auto_ptr(new SocketType(sock)); + } + + // Functions to allow adding to WaitForEvent class, for efficient waiting + // on multiple sockets. +#ifdef HAVE_KQUEUE + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::FillInKEevent + // Purpose: Fills in a kevent structure for this socket + // Created: 9/3/04 + // + // ------------------------------------------------------------------ + void FillInKEvent(struct kevent &rEvent, int Flags = 0) const + { + EV_SET(&rEvent, mSocketHandle, EVFILT_READ, 0, 0, 0, + (void*)this); + } +#else + // ------------------------------------------------------------------ + // + // Function + // Name: SocketListen::FillInPoll + // Purpose: Fills in the data necessary for a poll + // operation + // Created: 9/3/04 + // + // ------------------------------------------------------------------ + void FillInPoll(int &fd, short &events, int Flags = 0) const + { + fd = mSocketHandle; + events = POLLIN; + } +#endif + +private: + int mSocketHandle; +}; + +#include "MemLeakFindOff.h" + +#endif // SOCKETLISTEN__H + diff --git a/lib/server/SocketStream.cpp b/lib/server/SocketStream.cpp new file mode 100644 index 00000000..edb5e5b8 --- /dev/null +++ b/lib/server/SocketStream.cpp @@ -0,0 +1,571 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStream.cpp +// Purpose: I/O stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#ifndef WIN32 + #include +#endif + +#ifdef HAVE_UCRED_H + #include +#endif + +#ifdef HAVE_BSD_UNISTD_H + #include +#endif + +#ifdef HAVE_SYS_PARAM_H + #include +#endif + +#ifdef HAVE_SYS_UCRED_H + #include +#endif + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "SocketStream.h" +#include "CommonException.h" +#include "Socket.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream() +// Purpose: Constructor (create stream ready for Open() call) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream() + : mSocketHandle(INVALID_SOCKET_VALUE), + mReadClosed(false), + mWriteClosed(false), + mBytesRead(0), + mBytesWritten(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream(int) +// Purpose: Create stream from existing socket handle +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream(int socket) + : mSocketHandle(socket), + mReadClosed(false), + mWriteClosed(false), + mBytesRead(0), + mBytesWritten(0) +{ + if(socket < 0) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::SocketStream(const SocketStream &) +// Purpose: Copy constructor (dup()s socket) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::SocketStream(const SocketStream &rToCopy) + : mSocketHandle(::dup(rToCopy.mSocketHandle)), + mReadClosed(rToCopy.mReadClosed), + mWriteClosed(rToCopy.mWriteClosed), + mBytesRead(rToCopy.mBytesRead), + mBytesWritten(rToCopy.mBytesWritten) + +{ + if(rToCopy.mSocketHandle < 0) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, DupError); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::~SocketStream() +// Purpose: Destructor, closes stream if open +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +SocketStream::~SocketStream() +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + Close(); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Attach(int) +// Purpose: Attach a socket handle to this stream +// Created: 11/12/03 +// +// -------------------------------------------------------------------------- +void SocketStream::Attach(int socket) +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + ResetCounters(); + + mSocketHandle = socket; + mReadClosed = false; + mWriteClosed = false; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Open(Socket::Type, char *, int) +// Purpose: Opens a connection to a listening socket (INET or UNIX) +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Open(Socket::Type Type, const std::string& rName, int Port) +{ + if(mSocketHandle != INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + // Setup parameters based on type, looking up names if required + int sockDomain = 0; + SocketAllAddr addr; + int addrLen = 0; + Socket::NameLookupToSockAddr(addr, sockDomain, Type, rName, Port, + addrLen); + + // Create the socket + mSocketHandle = ::socket(sockDomain, SOCK_STREAM, + 0 /* let OS choose protocol */); + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketOpenError, + BOX_SOCKET_ERROR_MESSAGE(Type, rName, Port, + "Failed to create a network socket")); + } + + // Connect it + if(::connect(mSocketHandle, &addr.sa_generic, addrLen) == -1) + { + // Dispose of the socket + try + { + THROW_EXCEPTION_MESSAGE(ServerException, SocketOpenError, + BOX_SOCKET_ERROR_MESSAGE(Type, rName, Port, + "Failed to connect to socket")); + } + catch(ServerException &e) + { +#ifdef WIN32 + ::closesocket(mSocketHandle); +#else // !WIN32 + ::close(mSocketHandle); +#endif // WIN32 + mSocketHandle = INVALID_SOCKET_VALUE; + throw; + } + } + + ResetCounters(); + + mReadClosed = false; + mWriteClosed = false; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Read(void *pBuffer, int NBytes) +// Purpose: Reads data from stream. Maybe returns less than asked for. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int SocketStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + CheckForMissingTimeout(Timeout); + + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if(Timeout != IOStream::TimeOutInfinite) + { + struct pollfd p; + p.fd = mSocketHandle; + p.events = POLLIN; + p.revents = 0; + switch(::poll(&p, 1, PollTimeout(Timeout, 0))) + { + case -1: + // error + if(errno == EINTR) + { + // Signal. Just return 0 bytes + return 0; + } + else + { + // Bad! + BOX_LOG_SYS_ERROR("Failed to poll socket"); + THROW_EXCEPTION(ServerException, + SocketPollError) + } + break; + + case 0: + // no data + return 0; + break; + + default: + // good to go! + break; + } + } + +#ifdef WIN32 + int r = ::recv(mSocketHandle, (char*)pBuffer, NBytes, 0); +#else + int r = ::read(mSocketHandle, pBuffer, NBytes); +#endif + if(r == -1) + { + if(errno == EINTR) + { + // Nothing could be read + return 0; + } + else + { + // Other error + BOX_LOG_SYS_ERROR("Failed to read from socket"); + THROW_EXCEPTION(ConnectionException, + SocketReadError); + } + } + + // Closed for reading? + if(r == 0) + { + mReadClosed = true; + } + + mBytesRead += r; + return r; +} + +bool SocketStream::Poll(short Events, int Timeout) +{ + // Wait for data to send. + struct pollfd p; + p.fd = GetSocketHandle(); + p.events = Events; + p.revents = 0; + + box_time_t start = GetCurrentBoxTime(); + int result; + + do + { + result = ::poll(&p, 1, PollTimeout(Timeout, start)); + } + while(result == -1 && errno == EINTR); + + switch(result) + { + case -1: + // error - Bad! + THROW_SYS_ERROR("Failed to poll socket", ServerException, + SocketPollError); + break; + + case 0: + // Condition not met, timed out + return false; + + default: + // good to go! + return true; + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Write(void *pBuffer, int NBytes) +// Purpose: Writes data, blocking until it's all done. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Buffer in byte sized type. + ASSERT(sizeof(char) == 1); + const char *buffer = (char *)pBuffer; + + // Bytes left to send + int bytesLeft = NBytes; + box_time_t start = GetCurrentBoxTime(); + + while(bytesLeft > 0) + { + // Try to send. +#ifdef WIN32 + int sent = ::send(mSocketHandle, buffer, bytesLeft, 0); +#else + int sent = ::write(mSocketHandle, buffer, bytesLeft); +#endif + if(sent == -1) + { + // Error. + mWriteClosed = true; // assume can't write again + THROW_SYS_ERROR("Failed to write to socket", + ConnectionException, SocketWriteError); + } + + // Knock off bytes sent + bytesLeft -= sent; + // Move buffer pointer + buffer += sent; + + mBytesWritten += sent; + + // Need to wait until it can send again? + if(bytesLeft > 0) + { + BOX_TRACE("Waiting to send data on socket " << + mSocketHandle << " (" << bytesLeft << + " of " << NBytes << " bytes left)"); + + if(!Poll(POLLOUT, PollTimeout(Timeout, start))) + { + THROW_EXCEPTION_MESSAGE(ConnectionException, + Protocol_Timeout, "Timed out waiting " + "to send " << bytesLeft << " of " << + NBytes << " bytes"); + } + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Close() +// Purpose: Closes connection to remote socket +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Close() +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } +#ifdef WIN32 + if(::closesocket(mSocketHandle) == -1) +#else + if(::close(mSocketHandle) == -1) +#endif + { + BOX_LOG_SYS_ERROR("Failed to close socket"); + // don't throw an exception here, assume that the socket was + // already closed or closing. + } + mSocketHandle = INVALID_SOCKET_VALUE; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::Shutdown(bool, bool) +// Purpose: Shuts down a socket for further reading and/or writing +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void SocketStream::Shutdown(bool Read, bool Write) +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Do anything? + if(!Read && !Write) return; + + int how = SHUT_RDWR; + if(Read && !Write) how = SHUT_RD; + if(!Read && Write) how = SHUT_WR; + + // Shut it down! + if(::shutdown(mSocketHandle, how) == -1) + { + BOX_LOG_SYS_ERROR("Failed to shutdown socket"); + THROW_EXCEPTION(ConnectionException, SocketShutdownError) + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::StreamDataLeft() +// Purpose: Still capable of reading data? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool SocketStream::StreamDataLeft() +{ + return !mReadClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::StreamClosed() +// Purpose: Connection been closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool SocketStream::StreamClosed() +{ + return mWriteClosed; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::GetSocketHandle() +// Purpose: Returns socket handle for this stream (derived classes only). +// Will exception if there's no valid socket. +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +tOSSocketHandle SocketStream::GetSocketHandle() +{ + if(mSocketHandle == INVALID_SOCKET_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + return mSocketHandle; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStream::GetPeerCredentials(uid_t &, gid_t &) +// Purpose: Returns true if the peer credientials are available. +// (will work on UNIX domain sockets only) +// Created: 19/2/04 +// +// -------------------------------------------------------------------------- +bool SocketStream::GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut) +{ +#ifdef HAVE_GETPEEREID + uid_t remoteEUID = 0xffff; + gid_t remoteEGID = 0xffff; + + if(::getpeereid(mSocketHandle, &remoteEUID, &remoteEGID) == 0) + { + rUidOut = remoteEUID; + rGidOut = remoteEGID; + return true; + } +#endif + +#if HAVE_DECL_SO_PEERCRED + struct ucred cred; + socklen_t credLen = sizeof(cred); + + if(::getsockopt(mSocketHandle, SOL_SOCKET, SO_PEERCRED, &cred, + &credLen) == 0) + { +#ifdef HAVE_STRUCT_UCRED_UID + rUidOut = cred.uid; + rGidOut = cred.gid; +#else // HAVE_STRUCT_UCRED_CR_UID + rUidOut = cred.cr_uid; + rGidOut = cred.cr_gid; +#endif + return true; + } + + BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); +#endif + +#if defined HAVE_UCRED_H && HAVE_GETPEERUCRED + ucred_t *pucred = NULL; + if(::getpeerucred(mSocketHandle, &pucred) == 0) + { + rUidOut = ucred_geteuid(pucred); + rGidOut = ucred_getegid(pucred); + ucred_free(pucred); + if (rUidOut == -1 || rGidOut == -1) + { + BOX_ERROR("Failed to get peer credentials on " + "socket: insufficient information"); + return false; + } + return true; + } + + BOX_LOG_SYS_ERROR("Failed to get peer credentials on socket"); +#endif + + // Not available + return false; +} + +void SocketStream::CheckForMissingTimeout(int Timeout) +{ + if (Timeout == IOStream::TimeOutInfinite) + { + BOX_WARNING("Network operation started with no timeout!"); + DumpStackBacktrace(); + } +} diff --git a/lib/server/SocketStream.h b/lib/server/SocketStream.h new file mode 100644 index 00000000..fd57af8f --- /dev/null +++ b/lib/server/SocketStream.h @@ -0,0 +1,131 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStream.h +// Purpose: I/O stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETSTREAM__H +#define SOCKETSTREAM__H + +#include + +#ifdef HAVE_SYS_POLL_H +# include +#endif + +#include "BoxTime.h" +#include "IOStream.h" +#include "Socket.h" + +#ifdef WIN32 + typedef SOCKET tOSSocketHandle; + #define INVALID_SOCKET_VALUE (tOSSocketHandle)(-1) +#else + typedef int tOSSocketHandle; + #define INVALID_SOCKET_VALUE -1 +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketStream +// Purpose: Stream interface for sockets +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class SocketStream : public IOStream +{ +public: + SocketStream(); + SocketStream(int socket); + SocketStream(const SocketStream &rToCopy); + ~SocketStream(); + + void Open(Socket::Type Type, const std::string& rName, int Port = 0); + void Attach(int socket); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + + // Why not inherited from IOStream? Never mind, we want to enforce + // supplying a timeout for network operations anyway. + virtual void Write(const std::string& rBuffer, int Timeout) + { + IOStream::Write(rBuffer, Timeout); + } + + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + virtual void Shutdown(bool Read = true, bool Write = true); + + virtual bool GetPeerCredentials(uid_t &rUidOut, gid_t &rGidOut); + +protected: + void MarkAsReadClosed() {mReadClosed = true;} + void MarkAsWriteClosed() {mWriteClosed = true;} + void CheckForMissingTimeout(int Timeout); + + // Converts a timeout in milliseconds (or IOStream::TimeOutInfinite) + // into one that can be passed to poll() (also in milliseconds), also + // compensating for time elapsed since the wait should have started, + // if known. + int PollTimeout(int timeout, box_time_t start_time) + { + if (timeout == IOStream::TimeOutInfinite) + { + return INFTIM; + } + + if (start_time == 0) + { + return timeout; // no adjustment possible + } + + box_time_t end_time = start_time + MilliSecondsToBoxTime(timeout); + box_time_t now = GetCurrentBoxTime(); + box_time_t remaining = end_time - now; + + if (remaining < 0) + { + return 0; // no delay + } + else if (BoxTimeToMilliSeconds(remaining) > INT_MAX) + { + return INT_MAX; + } + else + { + return (int) BoxTimeToMilliSeconds(remaining); + } + } + bool Poll(short Events, int Timeout); + +private: + tOSSocketHandle mSocketHandle; + bool mReadClosed; + bool mWriteClosed; + +protected: + off_t mBytesRead; + off_t mBytesWritten; + +public: + off_t GetBytesRead() const {return mBytesRead;} + off_t GetBytesWritten() const {return mBytesWritten;} + void ResetCounters() {mBytesRead = mBytesWritten = 0;} + bool IsOpened() { return mSocketHandle != INVALID_SOCKET_VALUE; } + + /** + * Only for use by NiceSocketStream! + */ + tOSSocketHandle GetSocketHandle(); +}; + +#endif // SOCKETSTREAM__H + diff --git a/lib/server/SocketStreamTLS.cpp b/lib/server/SocketStreamTLS.cpp new file mode 100644 index 00000000..e6299bfa --- /dev/null +++ b/lib/server/SocketStreamTLS.cpp @@ -0,0 +1,460 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStreamTLS.cpp +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include +#include +#include +#include + +#ifndef WIN32 +#include +#endif + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "BoxTime.h" +#include "CryptoUtils.h" +#include "SocketStreamTLS.h" +#include "SSLLib.h" +#include "TLSContext.h" + +#include "MemLeakFindOn.h" + +// Allow 5 minutes to handshake (in milliseconds) +#define TLS_HANDSHAKE_TIMEOUT (5*60*1000) + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::SocketStreamTLS() +// Purpose: Constructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::SocketStreamTLS() + : mpSSL(0), mpBIO(0) +{ + ResetCounters(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::SocketStreamTLS(int) +// Purpose: Constructor, taking previously connected socket +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::SocketStreamTLS(int socket) + : SocketStream(socket), + mpSSL(0), mpBIO(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::~SocketStreamTLS() +// Purpose: Destructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SocketStreamTLS::~SocketStreamTLS() +{ + if(mpSSL) + { + // Attempt to close to avoid problems + Close(); + + // And if that didn't work... + if(mpSSL) + { + ::SSL_free(mpSSL); + mpSSL = 0; + mpBIO = 0; // implicity freed by the SSL_free call + } + } + + // If we only got to creating that BIO. + if(mpBIO) + { + ::BIO_free(mpBIO); + mpBIO = 0; + } +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Open(const TLSContext &, int, const char *, int) +// Purpose: Open connection, and perform TLS handshake +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Open(const TLSContext &rContext, Socket::Type Type, + const std::string& rName, int Port) +{ + SocketStream::Open(Type, rName, Port); + Handshake(rContext); + ResetCounters(); +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Handshake(const TLSContext &, bool) +// Purpose: Perform TLS handshake +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Handshake(const TLSContext &rContext, bool IsServer) +{ + if(mpBIO || mpSSL) {THROW_EXCEPTION(ServerException, TLSAlreadyHandshaked)} + + // Create a BIO for this socket + mpBIO = ::BIO_new(::BIO_s_socket()); + if(mpBIO == 0) + { + CryptoUtils::LogError("creating socket bio"); + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + tOSSocketHandle socket = GetSocketHandle(); + BIO_set_fd(mpBIO, socket, BIO_NOCLOSE); + + // Then the SSL object + mpSSL = ::SSL_new(rContext.GetRawContext()); + if(mpSSL == 0) + { + CryptoUtils::LogError("creating SSL object"); + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + // Make the socket non-blocking so timeouts on Read work + +#ifdef WIN32 + u_long nonblocking = 1; + ioctlsocket(socket, FIONBIO, &nonblocking); +#else // !WIN32 + // This is more portable than using ioctl with FIONBIO + int statusFlags = 0; + if(::fcntl(socket, F_GETFL, &statusFlags) < 0 + || ::fcntl(socket, F_SETFL, statusFlags | O_NONBLOCK) == -1) + { + THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) + } +#endif + + // FIXME: This is less portable than the above. However, it MAY be needed + // for cygwin, which has/had bugs with fcntl + // + // int nonblocking = true; + // if(::ioctl(socket, FIONBIO, &nonblocking) == -1) + // { + // THROW_EXCEPTION(ServerException, SocketSetNonBlockingFailed) + // } + + // Set the two to know about each other + ::SSL_set_bio(mpSSL, mpBIO, mpBIO); + + bool waitingForHandshake = true; + while(waitingForHandshake) + { + // Attempt to do the handshake + int r = 0; + if(IsServer) + { + r = ::SSL_accept(mpSSL); + } + else + { + r = ::SSL_connect(mpSSL); + } + + // check return code + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, handshake succeeded + waitingForHandshake = false; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the requried data + if(WaitWhenRetryRequired(se, TLS_HANDSHAKE_TIMEOUT) == false) + { + // timed out + THROW_EXCEPTION(ConnectionException, TLSHandshakeTimedOut) + } + break; + + default: // (and SSL_ERROR_ZERO_RETURN) + // Error occured + if(IsServer) + { + CryptoUtils::LogError("accepting connection"); + THROW_EXCEPTION(ConnectionException, TLSHandshakeFailed) + } + else + { + CryptoUtils::LogError("connecting"); + THROW_EXCEPTION(ConnectionException, TLSHandshakeFailed) + } + } + } + + // And that's it +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WaitWhenRetryRequired(int, int) +// Purpose: Waits until the condition required by the TLS layer +// is met. Returns true if the condition is met, false +// if timed out. +// Created: 2003/08/15 +// +// -------------------------------------------------------------------------- +bool SocketStreamTLS::WaitWhenRetryRequired(int SSLErrorCode, int Timeout) +{ + CheckForMissingTimeout(Timeout); + + short events; + switch(SSLErrorCode) + { + case SSL_ERROR_WANT_READ: + events = POLLIN; + break; + + case SSL_ERROR_WANT_WRITE: + events = POLLOUT; + break; + + default: + // Not good! + THROW_EXCEPTION(ServerException, Internal) + break; + } + + return Poll(events, Timeout); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Read(void *, int, int Timeout) +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +int SocketStreamTLS::Read(void *pBuffer, int NBytes, int Timeout) +{ + CheckForMissingTimeout(Timeout); + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Make sure zero byte reads work as expected + if(NBytes == 0) + { + return 0; + } + + while(true) + { + int r = ::SSL_read(mpSSL, pBuffer, NBytes); + + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, return number of bytes read + mBytesRead += r; + return r; + break; + + case SSL_ERROR_ZERO_RETURN: + // Connection closed + MarkAsReadClosed(); + return 0; + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the required data + // Will only get once around this loop, so don't need to calculate timeout values + if(WaitWhenRetryRequired(se, Timeout) == false) + { + // timed out + return 0; + } + break; + + default: + CryptoUtils::LogError("reading"); + THROW_EXCEPTION(ConnectionException, TLSReadFailed) + break; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Write(const void *, int) +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Write(const void *pBuffer, int NBytes, int Timeout) +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Make sure zero byte writes work as expected + if(NBytes == 0) + { + return; + } + + // from man SSL_write + // + // SSL_write() will only return with success, when the + // complete contents of buf of length num has been written. + // + // So no worries about partial writes and moving the buffer around + + while(true) + { + // try the write + int r = ::SSL_write(mpSSL, pBuffer, NBytes); + + int se; + switch((se = ::SSL_get_error(mpSSL, r))) + { + case SSL_ERROR_NONE: + // No error, data sent, return success + mBytesWritten += r; + return; + break; + + case SSL_ERROR_ZERO_RETURN: + // Connection closed + MarkAsWriteClosed(); + THROW_EXCEPTION(ConnectionException, TLSClosedWhenWriting) + break; + + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + // wait for the required data + { + #ifndef BOX_RELEASE_BUILD + bool conditionmet = + #endif + WaitWhenRetryRequired(se, Timeout); + ASSERT(conditionmet); + } + break; + + default: + CryptoUtils::LogError("writing"); + THROW_EXCEPTION(ConnectionException, TLSWriteFailed) + break; + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Close() +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Close() +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Base class to close + SocketStream::Close(); + + // Free resources + ::SSL_free(mpSSL); + mpSSL = 0; + mpBIO = 0; // implicitly freed by SSL_free +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::Shutdown() +// Purpose: See base class +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void SocketStreamTLS::Shutdown(bool Read, bool Write) +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + if(::SSL_shutdown(mpSSL) < 0) + { + CryptoUtils::LogError("shutting down"); + THROW_EXCEPTION(ConnectionException, TLSShutdownFailed) + } + + // Don't ask the base class to shutdown -- BIO does this, apparently. +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: SocketStreamTLS::GetPeerCommonName() +// Purpose: Returns the common name of the other end of the connection +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +std::string SocketStreamTLS::GetPeerCommonName() +{ + if(!mpSSL) {THROW_EXCEPTION(ServerException, TLSNoSSLObject)} + + // Get certificate + X509 *cert = ::SSL_get_peer_certificate(mpSSL); + if(cert == 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, TLSNoPeerCertificate) + } + + // Subject details + X509_NAME *subject = ::X509_get_subject_name(cert); + if(subject == 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, TLSPeerCertificateInvalid) + } + + // Common name + char commonName[256]; + if(::X509_NAME_get_text_by_NID(subject, NID_commonName, commonName, sizeof(commonName)) <= 0) + { + ::X509_free(cert); + THROW_EXCEPTION(ConnectionException, TLSPeerCertificateInvalid) + } + // Terminate just in case + commonName[sizeof(commonName)-1] = '\0'; + + // Done. + return std::string(commonName); +} diff --git a/lib/server/SocketStreamTLS.h b/lib/server/SocketStreamTLS.h new file mode 100644 index 00000000..3fda98c1 --- /dev/null +++ b/lib/server/SocketStreamTLS.h @@ -0,0 +1,62 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: SocketStreamTLS.h +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef SOCKETSTREAMTLS__H +#define SOCKETSTREAMTLS__H + +#include + +#include "SocketStream.h" + +class TLSContext; +#ifndef TLS_CLASS_IMPLEMENTATION_CPP + class SSL; + class BIO; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: SocketStreamTLS +// Purpose: Socket stream encrpyted and authenticated by TLS +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +class SocketStreamTLS : public SocketStream +{ +public: + SocketStreamTLS(); + SocketStreamTLS(int socket); + ~SocketStreamTLS(); +private: + SocketStreamTLS(const SocketStreamTLS &rToCopy); +public: + + void Open(const TLSContext &rContext, Socket::Type Type, + const std::string& rName, int Port = 0); + void Handshake(const TLSContext &rContext, bool IsServer = false); + + virtual int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void Close(); + virtual void Shutdown(bool Read = true, bool Write = true); + + std::string GetPeerCommonName(); + +private: + bool WaitWhenRetryRequired(int SSLErrorCode, int Timeout); + +private: + SSL *mpSSL; + BIO *mpBIO; +}; + +#endif // SOCKETSTREAMTLS__H + diff --git a/lib/server/TLSContext.cpp b/lib/server/TLSContext.cpp new file mode 100644 index 00000000..35e254fd --- /dev/null +++ b/lib/server/TLSContext.cpp @@ -0,0 +1,132 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: TLSContext.h +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#define TLS_CLASS_IMPLEMENTATION_CPP +#include + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "CryptoUtils.h" +#include "SSLLib.h" +#include "TLSContext.h" + +#include "MemLeakFindOn.h" + +#define MAX_VERIFICATION_DEPTH 2 +#define CIPHER_LIST "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH" + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::TLSContext() +// Purpose: Constructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +TLSContext::TLSContext() + : mpContext(0) +{ +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::~TLSContext() +// Purpose: Destructor +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +TLSContext::~TLSContext() +{ + if(mpContext != 0) + { + ::SSL_CTX_free(mpContext); + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::Initialise(bool, const char *, const char *, const char *) +// Purpose: Initialise the context, loading in the specified certificate and private key files +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +void TLSContext::Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile) +{ + if(mpContext != 0) + { + ::SSL_CTX_free(mpContext); + } + + mpContext = ::SSL_CTX_new(AsServer?TLSv1_server_method():TLSv1_client_method()); + if(mpContext == NULL) + { + THROW_EXCEPTION(ServerException, TLSAllocationFailed) + } + + // Setup our identity + if(::SSL_CTX_use_certificate_chain_file(mpContext, CertificatesFile) != 1) + { + std::string msg = "loading certificates from "; + msg += CertificatesFile; + CryptoUtils::LogError(msg); + 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; + CryptoUtils::LogError(msg); + THROW_EXCEPTION(ServerException, TLSLoadPrivateKeyFailed) + } + + // Setup the identify of CAs we trust + if(::SSL_CTX_load_verify_locations(mpContext, TrustedCAsFile, NULL) != 1) + { + std::string msg = "loading CA cert from "; + msg += TrustedCAsFile; + CryptoUtils::LogError(msg); + THROW_EXCEPTION(ServerException, TLSLoadTrustedCAsFailed) + } + + // Setup options to require these certificates + ::SSL_CTX_set_verify(mpContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); + // and a sensible maximum depth + ::SSL_CTX_set_verify_depth(mpContext, MAX_VERIFICATION_DEPTH); + + // Setup allowed ciphers + if(::SSL_CTX_set_cipher_list(mpContext, CIPHER_LIST) != 1) + { + CryptoUtils::LogError("setting cipher list to " CIPHER_LIST); + THROW_EXCEPTION(ServerException, TLSSetCiphersFailed) + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: TLSContext::GetRawContext() +// Purpose: Get the raw context for OpenSSL API +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +SSL_CTX *TLSContext::GetRawContext() const +{ + if(mpContext == 0) + { + THROW_EXCEPTION(ServerException, TLSContextNotInitialised) + } + return mpContext; +} + + + diff --git a/lib/server/TLSContext.h b/lib/server/TLSContext.h new file mode 100644 index 00000000..f52f5457 --- /dev/null +++ b/lib/server/TLSContext.h @@ -0,0 +1,41 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: TLSContext.h +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- + +#ifndef TLSCONTEXT__H +#define TLSCONTEXT__H + +#ifndef TLS_CLASS_IMPLEMENTATION_CPP + class SSL_CTX; +#endif + +// -------------------------------------------------------------------------- +// +// Class +// Name: TLSContext +// Purpose: TLS (SSL) context for connections +// Created: 2003/08/06 +// +// -------------------------------------------------------------------------- +class TLSContext +{ +public: + TLSContext(); + ~TLSContext(); +private: + TLSContext(const TLSContext &); +public: + void Initialise(bool AsServer, const char *CertificatesFile, const char *PrivateKeyFile, const char *TrustedCAsFile); + SSL_CTX *GetRawContext() const; + +private: + SSL_CTX *mpContext; +}; + +#endif // TLSCONTEXT__H + diff --git a/lib/server/TcpNice.cpp b/lib/server/TcpNice.cpp new file mode 100644 index 00000000..40e7a6b5 --- /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 +#endif + +#ifdef HAVE_WINSOCK2_H +# include +#endif + +#ifdef HAVE_SYS_SOCKET_H +# include +#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 apSocket) +// Purpose: Initialise state of the socket wrapper +// Created: 11/02/2012 +// +// -------------------------------------------------------------------------- + +NiceSocketStream::NiceSocketStream(std::auto_ptr 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..4381df42 --- /dev/null +++ b/lib/server/TcpNice.h @@ -0,0 +1,178 @@ +// -------------------------------------------------------------------------- +// +// 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 + +#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 SocketStream +{ +private: + std::auto_ptr mapSocket; + TcpNice mTcpNice; + std::auto_ptr 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 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); + + off_t GetBytesRead() const { return mapSocket->GetBytesRead(); } + off_t GetBytesWritten() const { return mapSocket->GetBytesWritten(); } + void ResetCounters() { mapSocket->ResetCounters(); } + +private: + NiceSocketStream(const NiceSocketStream &rToCopy) + { /* do not call */ } +}; + +#endif // TCPNICE__H diff --git a/lib/server/WinNamedPipeListener.h b/lib/server/WinNamedPipeListener.h new file mode 100644 index 00000000..956a7b5a --- /dev/null +++ b/lib/server/WinNamedPipeListener.h @@ -0,0 +1,232 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeListener.h +// Purpose: Windows named pipe socket connection listener +// for server +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- + +#ifndef WINNAMEDPIPELISTENER__H +#define WINNAMEDPIPELISTENER__H + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "OverlappedIO.h" +#include "WinNamedPipeStream.h" + +#include "MemLeakFindOn.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: WinNamedPipeListener +// Purpose: +// Created: 2008/09/30 +// +// -------------------------------------------------------------------------- +template +class WinNamedPipeListener +{ +private: + std::auto_ptr mapPipeName; + std::auto_ptr mapOverlapConnect; + HANDLE mPipeHandle; + +public: + // Initialise + WinNamedPipeListener() + : mPipeHandle(INVALID_HANDLE_VALUE) + { } + +private: + WinNamedPipeListener(const WinNamedPipeListener &rToCopy) + { /* forbidden */ } + + HANDLE CreatePipeHandle(const std::string& rName) + { + std::string socket = WinNamedPipeStream::sPipeNamePrefix + + rName; + + HANDLE handle = CreateNamedPipeA( + socket.c_str(), // pipe name + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, // enabled overlapped I/O + PIPE_TYPE_BYTE | + PIPE_READMODE_BYTE | + PIPE_WAIT, // blocking mode + ListenBacklog + 1, // max. instances + 4096, // output buffer size + 4096, // input buffer size + NMPWAIT_USE_DEFAULT_WAIT, // client time-out + NULL); // default security attribute + + if (handle == INVALID_HANDLE_VALUE) + { + THROW_WIN_FILE_ERRNO("Failed to create named pipe", + socket, GetLastError(), ServerException, + SocketOpenError); + } + + return handle; + } + +public: + ~WinNamedPipeListener() + { + Close(); + } + + void Close() + { + if (mPipeHandle != INVALID_HANDLE_VALUE) + { + if (mapOverlapConnect.get()) + { + // outstanding connect in progress + if (CancelIo(mPipeHandle) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to cancel " + "outstanding connect request " + "on named pipe"); + } + + mapOverlapConnect.reset(); + } + + if (CloseHandle(mPipeHandle) != TRUE) + { + BOX_LOG_WIN_ERROR("Failed to close named pipe " + "handle"); + } + + mPipeHandle = INVALID_HANDLE_VALUE; + } + } + + // ------------------------------------------------------------------ + // + // Function + // Name: WinNamedPipeListener::Listen(std::string name) + // Purpose: Initialises socket name + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + void Listen(const std::string& rName) + { + Close(); + mapPipeName.reset(new std::string(rName)); + mPipeHandle = CreatePipeHandle(rName); + } + + // ------------------------------------------------------------------ + // + // Function + // Name: WinNamedPipeListener::Accept(int) + // Purpose: Accepts a connection, returning a pointer to + // a class of the specified type. May return a + // null pointer if a signal happens, or there's + // a timeout. Timeout specified in + // milliseconds, defaults to infinite time. + // Created: 2003/07/31 + // + // ------------------------------------------------------------------ + std::auto_ptr Accept(int Timeout = INFTIM, + const char* pLogMsgOut = NULL) + { + if(!mapPipeName.get()) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + BOOL connected = FALSE; + std::auto_ptr mapStream; + + if (!mapOverlapConnect.get()) + { + // start a new connect operation + mapOverlapConnect.reset(new OverlappedIO()); + connected = ConnectNamedPipe(mPipeHandle, + &mapOverlapConnect->mOverlapped); + + if (connected == FALSE) + { + if (GetLastError() == ERROR_PIPE_CONNECTED) + { + connected = TRUE; + } + else if (GetLastError() != ERROR_IO_PENDING) + { + BOX_LOG_WIN_ERROR("Failed to connect " + "named pipe"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + } + } + + if (connected == FALSE) + { + // wait for connection + DWORD result = WaitForSingleObject( + mapOverlapConnect->mOverlapped.hEvent, + (Timeout == INFTIM) ? INFINITE : Timeout); + + if (result == WAIT_OBJECT_0) + { + DWORD dummy; + + if (!GetOverlappedResult(mPipeHandle, + &mapOverlapConnect->mOverlapped, + &dummy, TRUE)) + { + BOX_LOG_WIN_ERROR("Failed to get " + "overlapped connect result"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + + connected = TRUE; + } + else if (result == WAIT_TIMEOUT) + { + return mapStream; // contains NULL + } + else if (result == WAIT_ABANDONED) + { + BOX_ERROR("Wait for named pipe connection " + "was abandoned by the system"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + else if (result == WAIT_FAILED) + { + BOX_LOG_WIN_ERROR("Failed to wait for named " + "pipe connection"); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + else + { + BOX_ERROR("Failed to wait for named pipe " + "connection: unknown return code " << + result); + THROW_EXCEPTION(ServerException, + SocketAcceptError); + } + } + + ASSERT(connected == TRUE); + + mapStream.reset(new WinNamedPipeStream(mPipeHandle)); + mPipeHandle = CreatePipeHandle(*mapPipeName); + mapOverlapConnect.reset(); + + return mapStream; + } +}; + +#include "MemLeakFindOff.h" + +#endif // WINNAMEDPIPELISTENER__H diff --git a/lib/server/WinNamedPipeStream.cpp b/lib/server/WinNamedPipeStream.cpp new file mode 100644 index 00000000..448a3c9d --- /dev/null +++ b/lib/server/WinNamedPipeStream.cpp @@ -0,0 +1,599 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.cpp +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifdef WIN32 + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +#include "autogen_ConnectionException.h" +#include "autogen_ServerException.h" +#include "BoxTime.h" +#include "CommonException.h" +#include "Socket.h" +#include "WinNamedPipeStream.h" + +#include "MemLeakFindOn.h" + +std::string WinNamedPipeStream::sPipeNamePrefix = "\\\\.\\pipe\\"; + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::WinNamedPipeStream() +// Purpose: Constructor (create stream ready for Open() call) +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::WinNamedPipeStream() +: mSocketHandle(INVALID_HANDLE_VALUE), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(false), + mIsConnected(false), + mNeedAnotherRead(false) +{ } + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::WinNamedPipeStream(HANDLE) +// Purpose: Constructor (with already-connected pipe handle) +// Created: 2008/10/01 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::WinNamedPipeStream(HANDLE hNamedPipe) +: mSocketHandle(hNamedPipe), + mReadableEvent(INVALID_HANDLE_VALUE), + mBytesInBuffer(0), + mReadClosed(false), + mWriteClosed(false), + mIsServer(true), + mIsConnected(true), + mNeedAnotherRead(false) +{ + StartFirstRead(); +} + +// Start the first overlapped read +void WinNamedPipeStream::StartFirstRead() +{ + // create the Readable event + mReadableEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to create the Readable event: " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(CommonException, Internal) + } + + StartOverlappedRead(); +} + +void WinNamedPipeStream::StartOverlappedRead() +{ + // We should only do this when the buffer is empty. We don't want + // to start an overlapped read anywhere else than the start of the + // buffer, because it could complete at any time and we don't want + // to mess about with interrupting the read already in progress. + ASSERT(mBytesInBuffer == 0); + + // Initialise the OVERLAPPED structure + memset(&mReadOverlap, 0, sizeof(mReadOverlap)); + mReadOverlap.hEvent = mReadableEvent; + + if (!ReadFile(mSocketHandle, mReadBuffer, sizeof(mReadBuffer), + NULL, &mReadOverlap)) + { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) + { + // Don't reset yet, there might be data + // in the buffer waiting to be read, + // will check below. + // ResetEvent(mReadableEvent); + } + else if (err == ERROR_HANDLE_EOF) + { + BOX_INFO("Control client disconnected"); + mReadClosed = true; + } + else if (err == ERROR_BROKEN_PIPE || + err == ERROR_PIPE_NOT_CONNECTED) + { + BOX_NOTICE("Control client disconnected"); + mReadClosed = true; + mIsConnected = false; + } + else + { + Close(); + THROW_WIN_ERROR_NUMBER("Failed to start overlapped " + "read", err, ConnectionException, + SocketReadError) + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::~WinNamedPipeStream() +// Purpose: Destructor, closes stream if open +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +WinNamedPipeStream::~WinNamedPipeStream() +{ + for(std::list::iterator i = mWritesInProgress.begin(); + i != mWritesInProgress.end(); i++) + { + delete *i; + } + + if (mSocketHandle != INVALID_HANDLE_VALUE) + { + try + { + Close(); + } + catch (std::exception &e) + { + BOX_ERROR("Caught exception while destroying " + "named pipe, ignored: " << e.what()); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Accept(const std::string& rName) +// Purpose: Creates a new named pipe with the given name, +// and wait for a connection on it +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +/* +void WinNamedPipeStream::Accept() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle); + } + + if (mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen); + } + + bool connected = ConnectNamedPipe(mSocketHandle, (LPOVERLAPPED) NULL); + + if (!connected) + { + BOX_ERROR("Failed to ConnectNamedPipe(" << socket << "): " << + GetErrorMessage(GetLastError())); + Close(); + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mBytesInBuffer = 0; + mReadClosed = false; + mWriteClosed = false; + mIsServer = true; // must flush and disconnect before closing + mIsConnected = true; + + StartFirstRead(); +} +*/ + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Connect(const std::string& rName) +// Purpose: Opens a connection to a listening named pipe +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Connect(const std::string& rName) +{ + if (mSocketHandle != INVALID_HANDLE_VALUE || mIsConnected) + { + THROW_EXCEPTION(ServerException, SocketAlreadyOpen) + } + + std::string socket = sPipeNamePrefix + rName; + + mSocketHandle = CreateFileA( + socket.c_str(), // pipe name + GENERIC_READ | // read and write access + GENERIC_WRITE, + 0, // no sharing + NULL, // default security attributes + OPEN_EXISTING, + 0, // FILE_FLAG_OVERLAPPED, // dwFlagsAndAttributes + NULL); // no template file + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + DWORD err = GetLastError(); + if (err == ERROR_PIPE_BUSY) + { + BOX_ERROR("Failed to connect to backup daemon: " + "it is busy with another connection"); + } + else + { + BOX_ERROR("Failed to connect to backup daemon: " << + GetErrorMessage(err)); + } + THROW_EXCEPTION(ServerException, SocketOpenError) + } + + mReadClosed = false; + mWriteClosed = false; + mIsServer = false; // just close the socket + mIsConnected = true; + + StartFirstRead(); +} + +// Returns true if the operation is complete (and you will need to start +// another one), or false otherwise (you can wait again). +bool WinNamedPipeStream::WaitForOverlappedOperation(OVERLAPPED& Overlapped, + int Timeout, int64_t* pBytesTransferred) +{ + if (Timeout == IOStream::TimeOutInfinite) + { + Timeout = INFINITE; + } + + // overlapped I/O completed successfully? (wait if needed) + DWORD waitResult = WaitForSingleObject(Overlapped.hEvent, Timeout); + DWORD NumBytesTransferred = -1; + + if (waitResult == WAIT_FAILED) + { + THROW_WIN_ERROR_NUMBER("Failed to wait for overlapped I/O", + GetLastError(), ServerException, Internal); + } + + if (waitResult == WAIT_ABANDONED) + { + THROW_EXCEPTION_MESSAGE(ServerException, Internal, + "Wait for overlapped I/O abandoned by system"); + } + + if (waitResult == WAIT_TIMEOUT) + { + // wait timed out, nothing to read + *pBytesTransferred = 0; + return false; + } + + if (waitResult != WAIT_OBJECT_0) + { + THROW_EXCEPTION_MESSAGE(ServerException, BadSocketHandle, + "Failed to wait for overlapped I/O: unknown " + "result code: " << waitResult); + } + + // Overlapped operation completed successfully. Return the number + // of bytes transferred. + if (GetOverlappedResult(mSocketHandle, &Overlapped, + &NumBytesTransferred, TRUE)) + { + *pBytesTransferred = NumBytesTransferred; + return true; + } + + // We are here because GetOverlappedResult() informed us that the + // overlapped operation encountered an error, so what was it? + DWORD err = GetLastError(); + + if (err == ERROR_HANDLE_EOF) + { + Close(); + *pBytesTransferred = 0; + return true; + } + + // ERROR_NO_DATA is a strange name for + // "The pipe is being closed". No exception wanted. + + if (err == ERROR_NO_DATA || + err == ERROR_PIPE_NOT_CONNECTED || + err == ERROR_BROKEN_PIPE) + { + BOX_INFO(BOX_WIN_ERRNO_MESSAGE(err, + "Named pipe peer disconnected")); + Close(); + *pBytesTransferred = 0; + return true; + } + + THROW_WIN_ERROR_NUMBER("Failed to wait for overlapped I/O " + "to complete", err, ConnectionException, SocketReadError); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Read(void *pBuffer, int NBytes) +// Purpose: Reads data from stream. Maybe returns less than asked for. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +int WinNamedPipeStream::Read(void *pBuffer, int NBytes, int Timeout) +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION_MESSAGE(ServerException, BadSocketHandle, + "Tried to read from closed pipe"); + } + + if (mReadClosed) + { + THROW_EXCEPTION_MESSAGE(ConnectionException, + SocketShutdownError, "Tried to read from closing pipe"); + } + + // ensure safe to cast NBytes to unsigned + if (NBytes < 0) + { + THROW_EXCEPTION(CommonException, AssertFailed); + } + + int64_t NumBytesRead; + + // Satisfy from buffer if possible, to avoid blocking on read. + if (mBytesInBuffer == 0) + { + if (mNeedAnotherRead) + { + // Start the next overlapped read + StartOverlappedRead(); + } + + mNeedAnotherRead = WaitForOverlappedOperation(mReadOverlap, + Timeout, &NumBytesRead); + } + else + { + // Just return the existing data from the buffer + // this time around. The caller should call again, + // and then the buffer will be empty. + NumBytesRead = 0; + } + + int BytesToCopy = NumBytesRead + mBytesInBuffer; + + if (NBytes < BytesToCopy) + { + BytesToCopy = NBytes; + } + + memcpy(pBuffer, mReadBuffer, BytesToCopy); + + size_t BytesRemaining = mBytesInBuffer + NumBytesRead - BytesToCopy; + ASSERT(BytesToCopy + BytesRemaining <= sizeof(mReadBuffer)); + memmove(mReadBuffer, mReadBuffer + BytesToCopy, BytesRemaining); + mBytesInBuffer = BytesRemaining; + + return BytesToCopy; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Write(void *pBuffer, int NBytes) +// Purpose: Writes data, blocking until it's all done. +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Write(const void *pBuffer, int NBytes, int Timeout) +{ + // Calculate the deadline at the beginning. Not valid if Timeout is + // IOStream::TimeOutInfinite! + ASSERT(Timeout != IOStream::TimeOutInfinite); + + box_time_t deadline = GetCurrentBoxTime() + + MilliSecondsToBoxTime(Timeout); + + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + // Buffer in byte sized type. + ASSERT(sizeof(char) == 1); + WriteInProgress* new_write = new WriteInProgress( + std::string((char *)pBuffer, NBytes)); + + // Start the WriteFile operation, and add to queue if pending. + BOOL Success = WriteFile( + mSocketHandle, // pipe handle + new_write->mBuffer.c_str(), // message + NBytes, // message length + NULL, // bytes written this time + &(new_write->mOverlap)); + + if (Success == TRUE) + { + // Unfortunately this does happen. We should still call + // GetOverlappedResult() to get the number of bytes written, + // so we can treat it just the same. + // BOX_NOTICE("Write claimed success while overlapped?"); + mWritesInProgress.push_back(new_write); + } + else + { + DWORD err = GetLastError(); + + if (err == ERROR_IO_PENDING) + { + BOX_TRACE("WriteFile is pending, adding to queue"); + mWritesInProgress.push_back(new_write); + } + else + { + // Not in progress any more, pop it + Close(); + THROW_WIN_ERROR_NUMBER("Failed to start overlapped " + "write", err, ConnectionException, + SocketWriteError); + } + } + + // Wait for previous WriteFile operations to complete, one at a time, + // until the deadline expires or the pipe becomes disconnected. + for(box_time_t remaining = deadline - GetCurrentBoxTime(); + remaining > 0 && !mWritesInProgress.empty() && mIsConnected; + remaining = deadline - GetCurrentBoxTime()) + { + int new_timeout = BoxTimeToMilliSeconds(remaining); + WriteInProgress* oldest_write = + *(mWritesInProgress.begin()); + + int64_t bytes_written = 0; + if(WaitForOverlappedOperation(oldest_write->mOverlap, + new_timeout, &bytes_written)) + { + // This one is complete, pop it and start a new one + delete oldest_write; + mWritesInProgress.pop_front(); + } + } +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::Close() +// Purpose: Closes connection to remote socket +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::Close() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE && mIsConnected) + { + BOX_ERROR("Named pipe: inconsistent connected state"); + mIsConnected = false; + } + + if (mSocketHandle == INVALID_HANDLE_VALUE) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (!CancelIo(mSocketHandle)) + { + BOX_LOG_WIN_ERROR("Failed to cancel outstanding I/O"); + } + + if (mReadableEvent == INVALID_HANDLE_VALUE) + { + BOX_ERROR("Failed to destroy Readable event: " + "invalid handle"); + } + else if (!CloseHandle(mReadableEvent)) + { + BOX_LOG_WIN_ERROR("Failed to destroy Readable event"); + } + + mReadableEvent = INVALID_HANDLE_VALUE; + + if (mIsConnected && !FlushFileBuffers(mSocketHandle)) + { + BOX_LOG_WIN_ERROR("Failed to FlushFileBuffers"); + } + + if (mIsServer && mIsConnected && !DisconnectNamedPipe(mSocketHandle)) + { + DWORD err = GetLastError(); + if (err != ERROR_PIPE_NOT_CONNECTED) + { + BOX_LOG_WIN_ERROR("Failed to DisconnectNamedPipe"); + } + } + + if (!CloseHandle(mSocketHandle)) + { + THROW_WIN_ERROR_NUMBER("Failed to CloseHandle", + GetLastError(), ServerException, SocketCloseError); + } + + mSocketHandle = INVALID_HANDLE_VALUE; + mIsConnected = false; + mReadClosed = true; + mWriteClosed = true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamDataLeft() +// Purpose: Still capable of reading data? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamDataLeft() +{ + return !mReadClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: WinNamedPipeStream::StreamClosed() +// Purpose: Connection been closed? +// Created: 2003/08/02 +// +// -------------------------------------------------------------------------- +bool WinNamedPipeStream::StreamClosed() +{ + return mWriteClosed; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: IOStream::WriteAllBuffered() +// Purpose: Ensures that any data which has been buffered is written to the stream +// Created: 2003/08/26 +// +// -------------------------------------------------------------------------- +void WinNamedPipeStream::WriteAllBuffered() +{ + if (mSocketHandle == INVALID_HANDLE_VALUE || !mIsConnected) + { + THROW_EXCEPTION(ServerException, BadSocketHandle) + } + + if (!FlushFileBuffers(mSocketHandle)) + { + BOX_ERROR("Failed to FlushFileBuffers: " << + GetErrorMessage(GetLastError())); + } +} + + +#endif // WIN32 diff --git a/lib/server/WinNamedPipeStream.h b/lib/server/WinNamedPipeStream.h new file mode 100644 index 00000000..5473c690 --- /dev/null +++ b/lib/server/WinNamedPipeStream.h @@ -0,0 +1,112 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: WinNamedPipeStream.h +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2005/12/07 +// +// -------------------------------------------------------------------------- + +#if ! defined WINNAMEDPIPESTREAM__H && defined WIN32 +#define WINNAMEDPIPESTREAM__H + +#include + +#include "IOStream.h" + +// -------------------------------------------------------------------------- +// +// Class +// Name: WinNamedPipeStream +// Purpose: I/O stream interface for Win32 named pipes +// Created: 2003/07/31 +// +// -------------------------------------------------------------------------- +class WinNamedPipeStream : public IOStream +{ +public: + WinNamedPipeStream(); + WinNamedPipeStream(HANDLE hNamedPipe); + ~WinNamedPipeStream(); + + // server side - create the named pipe and listen for connections + // use WinNamedPipeListener to do this instead. + + // client side - connect to a waiting server + void Connect(const std::string& rName); + + // both sides + virtual int Read(void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite); + virtual void WriteAllBuffered(); + virtual void Close(); + virtual bool StreamDataLeft(); + virtual bool StreamClosed(); + + // Why not inherited from IOStream? Never mind, we want to enforce + // supplying a timeout for network operations anyway. + virtual void Write(const std::string& rBuffer, int Timeout) + { + IOStream::Write(rBuffer, Timeout); + } + +protected: + void MarkAsReadClosed() {mReadClosed = true;} + void MarkAsWriteClosed() {mWriteClosed = true;} + bool WaitForOverlappedOperation(OVERLAPPED& Overlapped, + int Timeout, int64_t* pBytesTransferred); + void StartFirstRead(); + void StartOverlappedRead(); + +private: + WinNamedPipeStream(const WinNamedPipeStream &rToCopy) + { /* do not call */ } + + HANDLE mSocketHandle; + HANDLE mReadableEvent; + OVERLAPPED mReadOverlap; + uint8_t mReadBuffer[4096]; + size_t mBytesInBuffer; + bool mReadClosed; + bool mWriteClosed; + bool mIsServer; + bool mIsConnected; + bool mNeedAnotherRead; + + class WriteInProgress { + private: + friend class WinNamedPipeStream; + std::string mBuffer; + OVERLAPPED mOverlap; + WriteInProgress(const WriteInProgress& other); // do not call + public: + WriteInProgress(const std::string& dataToWrite) + : mBuffer(dataToWrite) + { + // create the Writable event + HANDLE writable_event = CreateEvent(NULL, TRUE, FALSE, + NULL); + if (writable_event == INVALID_HANDLE_VALUE) + { + BOX_LOG_WIN_ERROR("Failed to create the " + "Writable event"); + THROW_EXCEPTION(CommonException, Internal) + } + + memset(&mOverlap, 0, sizeof(mOverlap)); + mOverlap.hEvent = writable_event; + } + ~WriteInProgress() + { + CloseHandle(mOverlap.hEvent); + } + }; + std::list mWritesInProgress; + +public: + static std::string sPipeNamePrefix; +}; + +#endif // WINNAMEDPIPESTREAM__H diff --git a/lib/server/makeprotocol.pl.in b/lib/server/makeprotocol.pl.in new file mode 100755 index 00000000..d6c0e216 --- /dev/null +++ b/lib/server/makeprotocol.pl.in @@ -0,0 +1,1352 @@ +#!@PERL@ +use strict; + +use lib "../../infrastructure"; +use BoxPlatform; + +# Make protocol C++ classes from a protocol description file + +# built in type info (values are is basic type, C++ typename) +# may get stuff added to it later if protocol uses extra types +my %translate_type_info = +( + 'int64' => [1, 'int64_t'], + 'int32' => [1, 'int32_t'], + 'int16' => [1, 'int16_t'], + 'int8' => [1, 'int8_t'], + 'bool' => [1, 'bool'], + 'string' => [0, 'std::string'] +); + +# built in instructions for logging various types +# may be added to +my %log_display_types = +( + 'int64' => ['0x%llx', 'VAR'], + 'int32' => ['0x%x', 'VAR'], + 'int16' => ['0x%x', 'VAR'], + 'int8' => ['0x%x', 'VAR'], + 'bool' => ['%s', '((VAR)?"true":"false")'], + 'string' => ['%s', 'VAR.c_str()'] +); + +if (@ARGV != 1) +{ + die "Usage: $0 \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; + +# read attributes +my %attr; +while() +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\A\s+//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + last if $l eq 'BEGIN_OBJECTS'; + + my ($k,$v) = split /\s+/,$l,2; + + 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] + } + else + { + $attr{$k} = $v; + } +} + +sub add_type +{ + my ($protocol_name, $cpp_name, $header_file) = split /\s+/,$_[0]; + + $translate_type_info{$protocol_name} = [0, $cpp_name]; + push @extra_header_files, $header_file if $header_file; +} + +# check attributes +for(qw/Name ServerContextClass IdentString/) +{ + if(!exists $attr{$_}) + { + die "Attribute $_ is required, but not specified\n"; + } +} + +my $protocol_name = $attr{'Name'}; +my ($context_class, $context_class_inc) = split /\s+/,$attr{'ServerContextClass'}; +my $ident_string = $attr{'IdentString'}; + +my $current_cmd = ''; +my %cmd_contents; +my %cmd_attributes; +my %cmd_constants; +my %cmd_id; +my @cmd_list; + +# read in the command definitions +while() +{ + # get and clean line + my $l = $_; $l =~ s/#.*\Z//; $l =~ s/\s+\Z//; next unless $l =~ m/\S/; + + # definitions or new command thing? + if($l =~ m/\A\s+/) + { + die "No command defined yet" if $current_cmd eq ''; + + # definition of component + $l =~ s/\A\s+//; + + my ($type,$name,$value) = split /\s+/,$l; + if($type eq 'CONSTANT') + { + push @{$cmd_constants{$current_cmd}},"$name = $value" + } + else + { + push @{$cmd_contents{$current_cmd}},$type,$name; + } + } + else + { + # new command + my ($name,$id,@attributes) = split /\s+/,$l; + $cmd_attributes{$name} = [@attributes]; + $cmd_id{$name} = int($id); + $current_cmd = $name; + push @cmd_list,$name; + } +} + +close IN; + + + +# open files +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'; + +print CPP <<__E; + +// Auto-generated file -- do not edit + +#include "Box.h" + +#include + +#include "$filename_base.h" +#include "CollectInBufferStream.h" +#include "MemBlockStream.h" +#include "SelfFlushingStream.h" +#include "SocketStream.h" +__E + +print H <<__E; +// Auto-generated file -- do not edit + +#ifndef $guardname +#define $guardname + +#include +#include + +#ifndef WIN32 +#include +#endif + +#include "autogen_ConnectionException.h" +#include "Protocol.h" +#include "Message.h" +#include "SocketStream.h" + +__E + +# extra headers +for(@extra_header_files) +{ + 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 $custom_protocol_subclass = $protocol_name."Protocol"; +my $client_server_base_class = $protocol_name."ProtocolClientServer"; +my $replyable_base_class = $protocol_name."ProtocolReplyable"; +my $callable_base_class = $protocol_name."ProtocolCallable"; +my $send_receive_class = $protocol_name."ProtocolSendReceive"; + +print H <<__E; +class $custom_protocol_subclass; +class $client_server_base_class; +class $callable_base_class; +class $replyable_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; + virtual std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, + $context_class &rContext, IOStream& rDataStream) const; + virtual bool HasStreamWithCommand() const = 0; +}; + +class $reply_base_class +{ +}; + +class $request_base_class +{ +}; + +class $send_receive_class { +public: + virtual void Send(const $message_base_class &rObject) = 0; + virtual std::auto_ptr<$message_base_class> Receive() = 0; +}; + +class $custom_protocol_subclass : public Protocol +{ +public: + $custom_protocol_subclass(std::auto_ptr apConn) + : Protocol(apConn) + { } + virtual ~$custom_protocol_subclass() { } + virtual std::auto_ptr MakeMessage(int ObjType); + virtual const char *GetProtocolIdentString(); + +private: + $custom_protocol_subclass(const $custom_protocol_subclass &rToCopy); +}; + +__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, Protocol_TriedToExecuteReplyCommand) +} + +std::auto_ptr<$message_base_class> $message_base_class\::DoCommand($replyable_base_class &rProtocol, + $context_class &rContext, IOStream& rDataStream) const +{ + THROW_EXCEPTION(ConnectionException, Protocol_TriedToExecuteReplyCommand) +} +__E + +my %cmd_classes; +my $error_message = undef; + +# 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_classes{$cmd} = $cmd_class; + + print H <<__E; +class $cmd_class : $cmd_base_class +{ +public: + $cmd_class(); + $cmd_class(const $cmd_class &rToCopy); + ~$cmd_class(); + int GetType() const; + enum + { + TypeID = $cmd_id{$cmd} + }; +__E + + # constants + if(exists $cmd_constants{$cmd}) + { + print H "\tenum\n\t{\n\t\t"; + print H join(",\n\t\t",@{$cmd_constants{$cmd}}); + print H "\n\t};\n"; + } + + # flags + if(obj_is_type($cmd,'EndsConversation')) + { + print H "\tbool IsConversationEnd() const;\n"; + } + + if(obj_is_type($cmd,'IsError')) + { + $error_message = $cmd; + my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError'); + my $error_type = $cmd_constants{"ErrorType"}; + print H <<__E; + $cmd_class(int SubType) : m$mem_type($error_type), m$mem_subtype(SubType) { } + bool IsError(int &rTypeOut, int &rSubTypeOut) const; + std::string GetMessage() const { return GetMessage(m$mem_subtype); }; + static std::string GetMessage(int subtype); +__E + } + + my $has_stream = obj_is_type($cmd, 'StreamWithCommand'); + + if(obj_is_type($cmd, 'Command') && $has_stream) + { + print H <<__E; + std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, + $context_class &rContext, IOStream& rDataStream) const; // IMPLEMENT THIS\n + std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, + $context_class &rContext) const + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "This command requires a stream parameter"); + } +__E + } + elsif(obj_is_type($cmd, 'Command') && !$has_stream) + { + print H <<__E; + std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, + $context_class &rContext) const; // IMPLEMENT THIS\n + std::auto_ptr<$message_base_class> DoCommand($replyable_base_class &rProtocol, + $context_class &rContext, IOStream& rDataStream) const + { + THROW_EXCEPTION_MESSAGE(CommonException, NotSupported, + "This command requires no stream parameter"); + } +__E + } + + print H <<__E; + bool HasStreamWithCommand() const { return $has_stream; } +__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"; + + # write member variables and setup for cpp file + my @def_constructor_list; + my @copy_constructor_list; + my @param_constructor_list; + + print H "private:\n"; + 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_member_type($ty)." m$nm;\n"; + + my ($basic,$typename) = translate_type($ty); + if($basic) + { + push @def_constructor_list, "m$nm(0)"; + } + push @copy_constructor_list, "m$nm(rToCopy.m$nm)"; + push @param_constructor_list, "m$nm($nm)"; + } + + # finish off + print H "};\n\n"; + + # now the cpp file... + my $def_con_vars = join(",\n\t ",@def_constructor_list); + $def_con_vars = "\n\t: ".$def_con_vars if $def_con_vars ne ''; + my $copy_con_vars = join(",\n\t ",@copy_constructor_list); + $copy_con_vars = "\n\t: ".$copy_con_vars if $copy_con_vars ne ''; + my $param_con_vars = join(",\n\t ",@param_constructor_list); + $param_con_vars = "\n\t: ".$param_con_vars if $param_con_vars ne ''; + + 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 +{ + return $cmd_id{$cmd}; +} +__E + 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"; + } + + if(obj_is_type($cmd,'IsError')) + { + # get parameters + my ($mem_type,$mem_subtype) = split /,/,obj_get_type_params($cmd,'IsError'); + print CPP <<__E; +bool $cmd_class\::IsError(int &rTypeOut, int &rSubTypeOut) const +{ + rTypeOut = m$mem_type; + rSubTypeOut = m$mem_subtype; + return true; +} +std::string $cmd_class\::GetMessage(int subtype) +{ + switch(subtype) + { +__E + foreach my $const (@{$cmd_constants{$cmd}}) + { + next unless $const =~ /^Err_(.*)/; + my $shortname = $1; + $const =~ s/ = .*//; + print CPP <<__E; + case $const: return "$shortname"; +__E + } + print CPP <<__E; + default: + std::ostringstream out; + out << "Unknown subtype " << subtype; + return out.str(); + } +} +__E + } + + 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 $client_server_base_class +{ +public: + $client_server_base_class(); + virtual ~$client_server_base_class(); + virtual std::auto_ptr ReceiveStream() = 0; + bool GetLastError(int &rTypeOut, int &rSubTypeOut); + int GetLastErrorType() { return mLastErrorSubType; } + +protected: + void SetLastError(int Type, int SubType) + { + mLastErrorType = Type; + mLastErrorSubType = SubType; + } + std::string mPreviousCommand; + std::string mPreviousReply; + +private: + $client_server_base_class(const $client_server_base_class &rToCopy); /* do not call */ + int mLastErrorType; + int mLastErrorSubType; +}; + +class $replyable_base_class : public virtual $client_server_base_class +{ +public: + $replyable_base_class() { } + virtual ~$replyable_base_class(); + + virtual int GetTimeout() = 0; + void SendStreamAfterCommand(std::auto_ptr apStream); + +protected: + std::list mStreamsToSend; + void DeleteStreamsToSend(); + virtual std::auto_ptr<$message_base_class> HandleException(BoxException& e) const; + +private: + $replyable_base_class(const $replyable_base_class &rToCopy); /* do not call */ +}; + +__E + +print CPP <<__E; +$client_server_base_class\::$client_server_base_class() +: mLastErrorType(Protocol::NoError), + mLastErrorSubType(Protocol::NoError) +{ } + +$client_server_base_class\::~$client_server_base_class() +{ } + +const char *$custom_protocol_subclass\::GetProtocolIdentString() +{ + return "$ident_string"; +} + +std::auto_ptr $custom_protocol_subclass\::MakeMessage(int ObjType) +{ + switch(ObjType) + { +__E + +# do objects within this +for my $cmd (@cmd_list) +{ + print CPP <<__E; + case $cmd_id{$cmd}: + return std::auto_ptr(new $cmd_classes{$cmd}()); + break; +__E +} + +print CPP <<__E; + default: + THROW_EXCEPTION(ConnectionException, Protocol_UnknownCommandRecieved) + } +} + +$replyable_base_class\::~$replyable_base_class() +{ + // If there were any streams left over, there's no longer any way to + // access them, and we're responsible for them, so we'd better delete them. + DeleteStreamsToSend(); +} + +void $replyable_base_class\::SendStreamAfterCommand(std::auto_ptr apStream) +{ + ASSERT(apStream.get() != NULL); + mStreamsToSend.push_back(apStream.release()); +} + +void $replyable_base_class\::DeleteStreamsToSend() +{ + for(std::list::iterator i(mStreamsToSend.begin()); i != mStreamsToSend.end(); ++i) + { + delete (*i); + } + + mStreamsToSend.clear(); +} + +void $callable_base_class\::CheckReply(const std::string& requestCommandName, + const $message_base_class &rCommand, const $message_base_class &rReply, + int expectedType) +{ + if(rReply.GetType() == expectedType) + { + // Correct response, do nothing + SetLastError(Protocol::NoError, Protocol::NoError); + } + else + { + // Set protocol error + int type, subType; + + if(rReply.IsError(type, subType)) + { + SetLastError(type, subType); + THROW_EXCEPTION_MESSAGE(ConnectionException, + Protocol_UnexpectedReply, + requestCommandName << " command failed: " + "received error " << + (($error_class&)rReply).GetMessage()); + } + else + { + SetLastError(Protocol::UnknownError, Protocol::UnknownError); + THROW_EXCEPTION_MESSAGE(ConnectionException, + Protocol_UnexpectedReply, + requestCommandName << " command failed: " + "received unexpected response type " << + rReply.GetType()); + } + } + + // As a client, if we get an unexpected reply later, we'll want to know + // the last command that we executed, and the reply, to help debug the + // server. + mPreviousCommand = rCommand.ToString(); + mPreviousReply = rReply.ToString(); +} + +// -------------------------------------------------------------------------- +// +// 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 $client_server_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 +print H <<__E; +class $callable_base_class : public virtual $client_server_base_class, + public $send_receive_class +{ +public: + virtual int GetTimeout() = 0; + +protected: + void CheckReply(const std::string& requestCommandName, + const $message_base_class &rCommand, + const $message_base_class &rReply, int expectedType); + +public: +__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 apStream':''; + my $queryextra = $has_stream?', apStream':''; + my $request_class = $cmd_classes{$cmd}; + my $reply_class = $cmd_classes{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, $custom_protocol_subclass; + } + + my $base_classes_str = join(", ", map {"public $_"} @base_classes); + + print H <<__E; +class $server_or_client_class : $base_classes_str +{ +public: + virtual ~$server_or_client_class(); +__E + + if($writing_local) + { + print H <<__E; + $server_or_client_class($context_class &rContext); +__E + } + + print H <<__E; + $server_or_client_class(std::auto_ptr apConn); + std::auto_ptr<$message_base_class> Receive(); + void Send(const $message_base_class &rObject); +__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 apStream':''; + my $queryextra = $has_stream?', apStream':''; + my $request_class = $cmd_classes{$cmd}; + my $reply_class = $cmd_classes{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; + std::auto_ptr<$message_base_class> mapLastReply; +public: + virtual std::auto_ptr ReceiveStream() + { + if(mStreamsToSend.empty()) + { + THROW_EXCEPTION_MESSAGE(CommonException, Internal, + "Tried to ReceiveStream when none was sent or " + "made available"); + } + + std::auto_ptr apStream(mStreamsToSend.front()); + mStreamsToSend.pop_front(); + return apStream; + } +__E + } + else + { + print H <<__E; + virtual std::auto_ptr ReceiveStream(); +__E + + print CPP <<__E; +std::auto_ptr $server_or_client_class\::ReceiveStream() +{ + try + { + return $custom_protocol_subclass\::ReceiveStream(); + } + catch(ConnectionException &e) + { + if(e.GetSubType() == ConnectionException::Protocol_ObjWhenStreamExpected) + { + THROW_EXCEPTION_MESSAGE(ConnectionException, + Protocol_ObjWhenStreamExpected, + "Last exchange was " << mPreviousCommand << + " => " << mPreviousReply); + } + else + { + throw; + } + } +} +__E + } + + if($writing_local) + { + print H <<__E; + virtual int GetTimeout() + { + return IOStream::TimeOutInfinite; + } +__E + } + else + { + print H <<__E; + virtual int GetTimeout() + { + return $custom_protocol_subclass\::GetTimeout(); + } +__E + } + + print H <<__E; + +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(std::auto_ptr apConn) +: $custom_protocol_subclass(apConn) +{ } +__E + } + + print CPP <<__E; +$server_or_client_class\::~$server_or_client_class() +{$destructor_extra +} +__E + + # write receive and send functions + if($writing_local) + { + print CPP <<__E; +std::auto_ptr<$message_base_class> $server_or_client_class\::Receive() +{ + return mapLastReply; +} +void $server_or_client_class\::Send(const $message_base_class &rObject) +{ + mapLastReply = rObject.DoCommand(*this, mrContext); +} +__E + } + else + { + print CPP <<__E; +std::auto_ptr<$message_base_class> $server_or_client_class\::Receive() +{ + std::auto_ptr<$message_base_class> apReply; + + try + { + apReply = std::auto_ptr<$message_base_class>( + static_cast<$message_base_class *> + ($custom_protocol_subclass\::ReceiveInternal().release())); + } + catch(ConnectionException &e) + { + if(e.GetSubType() == ConnectionException::Protocol_StreamWhenObjExpected) + { + THROW_EXCEPTION_MESSAGE(ConnectionException, + Protocol_StreamWhenObjExpected, + "Last exchange was " << mPreviousCommand << + " => " << mPreviousReply); + } + else + { + throw; + } + } + + if(GetLogToSysLog()) + { + apReply->LogSysLog("Receive"); + } + + if(GetLogToFile() != 0) + { + apReply->LogFile("Receive", GetLogToFile()); + } + + return apReply; +} + +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) +{ + // Handshake with client + Handshake(); + + // Command processing loop + bool inProgress = true; + while(inProgress) + { + // Get an object from the conversation + std::auto_ptr<$message_base_class> pobj = Receive(); + std::auto_ptr<$message_base_class> preply; + + // Run the command + try + { + try + { + if(pobj->HasStreamWithCommand()) + { + std::auto_ptr apDataStream = ReceiveStream(); + SelfFlushingStream autoflush(*apDataStream); + preply = pobj->DoCommand(*this, rContext, *apDataStream); + } + else + { + preply = pobj->DoCommand(*this, rContext); + } + } + catch(BoxException &e) + { + // First try a the built-in exception handler + preply = HandleException(e); + } + } + catch (...) + { + // Fallback in case the exception isn't a BoxException + // or the exception handler fails as well. This path + // throws the exception upwards, killing the process + // that handles the current client. + Send($cmd_classes{$error_message}(-1)); + throw; + } + + // Send the reply + Send(*preply); + + // Send any streams + for(std::list::iterator + i = mStreamsToSend.begin(); + i != mStreamsToSend.end(); ++i) + { + SendStream(**i); + } + + // As a server, if we get an unexpected message later, we'll + // want to know the last command that we received, and the + // reply, to help debug our response to it. + mPreviousCommand = pobj->ToString(); + std::ostringstream reply; + reply << preply->ToString() << " and " << + mStreamsToSend.size() << " streams"; + mPreviousReply = reply.str(); + + // Delete these streams + DeleteStreamsToSend(); + + // Does this end the conversation? + if(pobj->IsConversationEnd()) + { + inProgress = false; + } + } +} + +__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_classes{$cmd}; + my $reply_msg = obj_get_type_params($cmd,'Command'); + my $reply_class = $cmd_classes{$reply_msg}; + my $reply_id = $cmd_id{$reply_msg}; + my $has_stream = obj_is_type($cmd,'StreamWithCommand'); + my $argextra = $has_stream?', std::auto_ptr apDataStream':''; + my $send_stream_extra = ''; + + print CPP <<__E; +std::auto_ptr<$reply_class> $server_or_client_class\::Query(const $request_class &rQuery$argextra) +{ +__E + + if($writing_client) + { + if($has_stream) + { + $send_stream_extra = <<__E; + // Send stream after the command + try + { + SendStream(*apDataStream); + } + catch (BoxException &e) + { + BOX_WARNING("Failed to send stream after command: " << + rQuery.ToString() << ": " << e.what()); + throw; + } +__E + } + + print CPP <<__E; + // Send query + Send(rQuery); +$send_stream_extra + + // Wait for the reply + std::auto_ptr<$message_base_class> apReply = Receive(); +__E + } + elsif($writing_local) + { + print CPP <<__E; + std::auto_ptr<$message_base_class> apReply; + try + { +__E + if($has_stream) + { + print CPP <<__E; + apReply = rQuery.DoCommand(*this, mrContext, *apDataStream); +__E + } + else + { + print CPP <<__E; + apReply = rQuery.DoCommand(*this, mrContext); +__E + } + + print CPP <<__E; + } + catch(BoxException &e) + { + // First try a the built-in exception handler + apReply = HandleException(e); + } +__E + } + + # Common to both client and local + print CPP <<__E; + CheckReply("$cmd", rQuery, *apReply, $reply_id); + + // Correct response, if no exception thrown by CheckReply + return std::auto_ptr<$reply_class>( + static_cast<$reply_class *>(apReply.release())); +} +__E + } + } + } +} + +print H <<__E; +#endif // $guardname + +__E + +# close files +close H; +close CPP; + +sub obj_is_type ($$) +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return 1 if $_ =~ m/\A$ty/; + } + + return 0; +} + +sub obj_get_type_params +{ + my ($c,$ty) = @_; + for(@{$cmd_attributes{$c}}) + { + return $1 if $_ =~ m/\A$ty\((.+?)\)\Z/; + } + die "Can't find attribute $ty on command $c\n" +} + +# returns (is basic type, typename) +sub translate_type +{ + my $ty = $_[0]; + + if($ty =~ m/\Avector\<(.+?)\>\Z/) + { + my $v_type = $1; + my (undef,$v_ty) = translate_type($v_type); + return (0, 'std::vector<'.$v_ty.'>') + } + else + { + if(!exists $translate_type_info{$ty}) + { + die "Don't know about type name $ty\n"; + } + return @{$translate_type_info{$ty}} + } +} + +sub translate_type_to_arg_type +{ + my ($basic,$typename) = translate_type(@_); + return $basic?$typename:'const '.$typename.'&' +} + +sub translate_type_to_member_type +{ + my ($basic,$typename) = translate_type(@_); + return $typename +} + +sub make_log_strings_framework +{ + my ($cmd) = @_; + + my @args; + + for(my $x = 0; $x < $#{$cmd_contents{$cmd}}; $x+=2) + { + my ($ty,$nm) = (${$cmd_contents{$cmd}}[$x], ${$cmd_contents{$cmd}}[$x+1]); + + if(exists $log_display_types{$ty}) + { + # need to translate it + my ($format,$arg) = @{$log_display_types{$ty}}; + $arg =~ s/VAR/m$nm/g; + + if ($format eq '"%s"') + { + $arg = "\"\\\"\" << $arg << \"\\\"\""; + } + elsif ($format =~ m'x$') + { + # my $width = 0; + # $ty =~ /^int(\d+)$/ and $width = $1 / 4; + $arg = "($arg == 0 ? \"0x\" : \"\") " . + "<< std::hex " . + "<< std::showbase " . + # "<< std::setw($width) " . + # "<< std::setfill('0') " . + # "<< std::internal " . + "<< $arg " . + "<< std::dec"; + } + + push @args, $arg; + } + else + { + # is opaque + push @args, '"OPAQUE"'; + } + } + + my $log_cmd = '"'.$cmd.'(" '; + foreach my $arg (@args) + { + $arg = "<< $arg "; + } + $log_cmd .= join('<< "," ',@args); + $log_cmd .= '<< ")"'; + return $log_cmd; +} + diff --git a/lib/win32/MSG00001.bin b/lib/win32/MSG00001.bin new file mode 100755 index 00000000..b4377b85 Binary files /dev/null and b/lib/win32/MSG00001.bin differ diff --git a/lib/win32/box_getopt.h b/lib/win32/box_getopt.h new file mode 100644 index 00000000..f18446d4 --- /dev/null +++ b/lib/win32/box_getopt.h @@ -0,0 +1,14 @@ +#if defined _MSC_VER || defined __MINGW32__ +#define REPLACE_GETOPT 1 /* use this getopt as the system getopt(3) */ +#else +#define REPLACE_GETOPT 0 // force a conflict if included multiple times +#endif + +#if REPLACE_GETOPT +# include "bsd_getopt.h" +# define BOX_BSD_GETOPT +#else +# include +# undef BOX_BSD_GETOPT +#endif + diff --git a/lib/win32/bsd_getopt.h b/lib/win32/bsd_getopt.h new file mode 100755 index 00000000..9cfdd32e --- /dev/null +++ b/lib/win32/bsd_getopt.h @@ -0,0 +1,105 @@ +/* $OpenBSD: getopt.h,v 1.1 2002/12/03 20:24:29 millert Exp $ */ +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +#ifndef REPLACE_GETOPT +#error You must include box_getopt.h, not bsd_getopt.h +#endif + +#if REPLACE_GETOPT // defined in box_getopt.h; until end of file + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +// copied from: http://www.la.utexas.edu/lab/software/devtool/gnu/libtool/C_header_files.html + +/* __BEGIN_DECLS should be used at the beginning of your declarations, + so that C++ compilers don't mangle their names. Use __END_DECLS at + the end of C declarations. */ +#undef __BEGIN_DECLS +#undef __END_DECLS +#ifdef __cplusplus +# define __BEGIN_DECLS extern "C" { +# define __END_DECLS } +#else +# define __BEGIN_DECLS /* empty */ +# define __END_DECLS /* empty */ +#endif + +/* + * GNU-like getopt_long() and 4.4BSD getsubopt()/optreset extensions + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +__BEGIN_DECLS +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DEFINED_ +#define _GETOPT_DEFINED_ +int getopt(int, char * const *, const char *); +int getsubopt(char **, char * const *, char **); + +extern char *optarg; /* getopt(3) external variables */ +extern int opterr; +extern int optind; +extern int optopt; +extern int optreset; +extern char *suboptarg; /* getsubopt(3) external variable */ +#endif +__END_DECLS + +#endif /* !_GETOPT_H_ */ +#endif // REPLACE_GETOPT diff --git a/lib/win32/emu.cpp b/lib/win32/emu.cpp new file mode 100644 index 00000000..c78fe6b2 --- /dev/null +++ b/lib/win32/emu.cpp @@ -0,0 +1,2033 @@ +// Box Backup Win32 native port by Nick Knight + +#include "emu.h" + +#ifdef WIN32 + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H + #include +#endif + +#include +#include +#include + +// message resource definitions for syslog() +#include "messages.h" + +DWORD winerrno; +struct passwd gTempPasswd; + +bool EnableBackupRights() +{ + HANDLE hToken; + TOKEN_PRIVILEGES token_priv; + + //open current process to adjust privileges + if(!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, + &hToken)) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to open process token: %s", + GetErrorMessage(winerrno).c_str()); + return false; + } + + //let's build the token privilege struct - + //first, look up the LUID for the backup privilege + + if (!LookupPrivilegeValue( + NULL, //this system + SE_BACKUP_NAME, //the name of the privilege + &( token_priv.Privileges[0].Luid ))) //result + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to lookup backup privilege: %s", + GetErrorMessage(winerrno).c_str()); + CloseHandle(hToken); + return false; + } + + token_priv.PrivilegeCount = 1; + token_priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // now set the privilege + // because we're going exit right after dumping the streams, there isn't + // any need to save current state + + if (!AdjustTokenPrivileges( + hToken, //our process token + false, //we're not disabling everything + &token_priv, //address of structure + sizeof(token_priv), //size of structure + NULL, NULL)) //don't save current state + { + //this function is a little tricky - if we were adjusting + //more than one privilege, it could return success but not + //adjust them all - in the general case, you need to trap this + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to enable backup privilege: %s", + GetErrorMessage(winerrno).c_str()); + CloseHandle(hToken); + return false; + + } + + CloseHandle(hToken); + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: GetDefaultConfigFilePath(std::string name) +// Purpose: Calculates the default configuration file name, +// by using the directory location of the currently +// executing program, and appending the provided name. +// In case of fire, returns an empty string. +// Created: 26th May 2007 +// +// -------------------------------------------------------------------------- +std::string GetDefaultConfigFilePath(const std::string& rName) +{ + WCHAR exePathWide[MAX_PATH]; + GetModuleFileNameW(NULL, exePathWide, MAX_PATH-1); + + char* exePathUtf8 = ConvertFromWideString(exePathWide, CP_UTF8); + if (exePathUtf8 == NULL) + { + return ""; + } + + std::string configfile = exePathUtf8; + delete [] exePathUtf8; + + // make the default config file name, + // based on the program path + configfile = configfile.substr(0, + configfile.rfind('\\')); + configfile += "\\"; + configfile += rName; + + return configfile; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertToWideString +// Purpose: Converts a string from specified codepage to +// a wide string (WCHAR*). Returns a buffer which +// MUST be freed by the caller with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +WCHAR* ConvertToWideString(const char* pString, unsigned int codepage, + bool logErrors) +{ + int len = MultiByteToWideChar + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // number of bytes in string - auto detect + NULL, // wide-character buffer + 0 // size of buffer - work out + // how much space we need + ); + + if (len == 0) + { + winerrno = GetLastError(); + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "%s", GetErrorMessage(winerrno).c_str()); + } + errno = EINVAL; + return NULL; + } + + WCHAR* buffer = new WCHAR[len]; + + if (buffer == NULL) + { + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "out of memory"); + } + winerrno = ERROR_OUTOFMEMORY; + errno = ENOMEM; + return NULL; + } + + len = MultiByteToWideChar + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // number of bytes in string - auto detect + buffer, // wide-character buffer + len // size of buffer + ); + + if (len == 0) + { + winerrno = GetLastError(); + if (logErrors) + { + ::syslog(LOG_WARNING, + "Failed to convert string to wide string: " + "%s", GetErrorMessage(winerrno).c_str()); + } + errno = EACCES; + delete [] buffer; + return NULL; + } + + return buffer; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertUtf8ToWideString +// Purpose: Converts a string from UTF-8 to a wide string. +// Returns a buffer which MUST be freed by the caller +// with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +WCHAR* ConvertUtf8ToWideString(const char* pString) +{ + return ConvertToWideString(pString, CP_UTF8, true); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertFromWideString +// Purpose: Converts a wide string to a narrow string in the +// specified code page. Returns a buffer which MUST +// be freed by the caller with delete[]. +// In case of fire, logs the error and returns NULL. +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage) +{ + int len = WideCharToMultiByte + ( + codepage, // destination code page + 0, // character-type options + pString, // string to map + -1, // 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) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "%s", GetErrorMessage(winerrno).c_str()); + errno = EINVAL; + return NULL; + } + + char* buffer = new char[len]; + + if (buffer == NULL) + { + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "out of memory"); + errno = ENOMEM; + return NULL; + } + + len = WideCharToMultiByte + ( + codepage, // source code page + 0, // character-type options + pString, // string to map + -1, // 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) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "%s", GetErrorMessage(winerrno).c_str()); + errno = EACCES; + delete [] buffer; + return NULL; + } + + return buffer; +} + +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) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "%s", GetErrorMessage(winerrno).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) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, + "Failed to convert wide string to narrow: " + "%s", GetErrorMessage(winerrno).c_str()); + errno = EACCES; + delete [] buffer; + return false; + } + + *pOutput = std::string(buffer, len); + delete [] buffer; + return true; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertEncoding(const std::string&, int, +// std::string&, int) +// Purpose: Converts a string from one code page to another. +// On success, replaces contents of rDest and returns +// true. In case of fire, logs the error and returns +// false. +// Created: 15th October 2006 +// +// -------------------------------------------------------------------------- +bool ConvertEncoding(const std::string& rSource, int sourceCodePage, + std::string& rDest, int destCodePage) +{ + WCHAR* pWide = ConvertToWideString(rSource.c_str(), sourceCodePage, + true); + if (pWide == NULL) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to convert string '%s' from " + "current code page %d to wide string: %s", + rSource.c_str(), sourceCodePage, + GetErrorMessage(winerrno).c_str()); + return false; + } + + char* pConsole = ConvertFromWideString(pWide, destCodePage); + delete [] pWide; + + if (!pConsole) + { + // Error should have been logged by ConvertFromWideString + return false; + } + + rDest = pConsole; + delete [] pConsole; + + return true; +} + +bool ConvertToUtf8(const std::string& rSource, std::string& rDest, + int sourceCodePage) +{ + return ConvertEncoding(rSource, sourceCodePage, rDest, CP_UTF8); +} + +bool ConvertFromUtf8(const std::string& rSource, std::string& rDest, + int destCodePage) +{ + return ConvertEncoding(rSource, CP_UTF8, rDest, destCodePage); +} + +bool ConvertConsoleToUtf8(const std::string& rSource, std::string& rDest) +{ + return ConvertToUtf8(rSource, rDest, GetConsoleCP()); +} + +bool ConvertUtf8ToConsole(const std::string& rSource, std::string& rDest) +{ + return ConvertFromUtf8(rSource, rDest, GetConsoleOutputCP()); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: ConvertPathToAbsoluteUnicode +// Purpose: Converts relative paths to absolute (with unicode marker) +// Created: 4th February 2006 +// +// -------------------------------------------------------------------------- +std::string ConvertPathToAbsoluteUnicode(const char *pFileName) +{ + std::string filename; + for (int i = 0; pFileName[i] != 0; i++) + { + if (pFileName[i] == '/') + { + filename += '\\'; + } + else + { + filename += pFileName[i]; + } + } + + std::string tmpStr("\\\\?\\"); + + // Is the path relative or absolute? + // Absolute paths on Windows are always a drive letter + // followed by ':' + + char wd[PATH_MAX]; + if (::getcwd(wd, PATH_MAX) == 0) + { + ::syslog(LOG_WARNING, + "Failed to open '%s': path too long", + pFileName); + errno = ENAMETOOLONG; + winerrno = ERROR_INVALID_NAME; + tmpStr = ""; + return tmpStr; + } + + 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] == '\\' && + filename[1] == '\\') + { + tmpStr += "UNC\\"; + filename.replace(0, 2, ""); + // \\?\UNC\\ + // see http://msdn2.microsoft.com/en-us/library/aa365247.aspx + } + else if (filename.length() >= 1 && filename[0] == '\\') + { + // starts with \, i.e. root directory of current drive. + tmpStr = wd; + tmpStr.resize(2); // drive letter and colon + } + else if (filename.length() >= 2 && filename[1] != ':') + { + // Must be a relative path. We need to get the + // current directory to make it absolute. + tmpStr += wd; + if (tmpStr[tmpStr.length()-1] != '\\') + { + tmpStr += '\\'; + } + } + + tmpStr += filename; + + // We are using direct filename access, which does not support .., + // so we need to implement it ourselves. + + for (std::string::size_type i = 1; i < tmpStr.size() - 3; i++) + { + if (tmpStr.substr(i, 3) == "\\..") + { + std::string::size_type lastSlash = + tmpStr.rfind('\\', i - 1); + + if (lastSlash == std::string::npos) + { + // no previous directory, ignore it, + // CreateFile will fail with error 123 + } + else + { + tmpStr.replace(lastSlash, i + 3 - lastSlash, + ""); + } + + i = lastSlash - 1; + } + } + + return tmpStr; +} + +std::string GetErrorMessage(DWORD errorCode) +{ + char* pMsgBuf = NULL; + + DWORD chars = FormatMessage + ( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char *)(&pMsgBuf), + 0, NULL + ); + + if (chars == 0 || pMsgBuf == NULL) + { + std::ostringstream oss; + oss << "Failed to get error message for error code " << errorCode << ": error " << + GetLastError(); + return oss.str(); + } + + // remove embedded newline + pMsgBuf[chars - 1] = 0; + pMsgBuf[chars - 2] = 0; + + std::ostringstream line; + line << pMsgBuf << " (" << errorCode << ")"; + LocalFree(pMsgBuf); + + return line.str(); +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: openfile +// Purpose: replacement for any open calls - handles unicode +// filenames - supplied in utf8 +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +HANDLE openfile(const char *pFileName, int flags, int mode) +{ + winerrno = ERROR_INVALID_FUNCTION; + + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return INVALID_HANDLE_VALUE; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + return INVALID_HANDLE_VALUE; + } + + // flags could be O_WRONLY | O_CREAT | O_RDONLY + DWORD createDisposition = OPEN_EXISTING; + DWORD shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE + | FILE_SHARE_DELETE; + DWORD accessRights = FILE_READ_ATTRIBUTES | FILE_LIST_DIRECTORY + | FILE_READ_EA; + + if (flags & O_WRONLY) + { + accessRights = FILE_WRITE_DATA; + } + else if (flags & O_RDWR) + { + accessRights |= FILE_WRITE_ATTRIBUTES + | FILE_WRITE_DATA | FILE_WRITE_EA; + } + + if (flags & O_CREAT) + { + createDisposition = OPEN_ALWAYS; + } + + if (flags & O_TRUNC) + { + createDisposition = CREATE_ALWAYS; + } + + if ((flags & O_CREAT) && (flags & O_EXCL)) + { + createDisposition = CREATE_NEW; + } + + if (flags & BOX_OPEN_LOCK) + { + shareMode = 0; + } + + DWORD winFlags = FILE_FLAG_BACKUP_SEMANTICS; + if (flags & O_TEMPORARY) + { + winFlags |= FILE_FLAG_DELETE_ON_CLOSE; + } + + HANDLE hdir = CreateFileW(pBuffer, + accessRights, + shareMode, + NULL, + createDisposition, + winFlags, + NULL); + + delete [] pBuffer; + + if (hdir == INVALID_HANDLE_VALUE) + { + winerrno = GetLastError(); + switch(winerrno) + { + case ERROR_SHARING_VIOLATION: + errno = EBUSY; + break; + + default: + errno = EINVAL; + } + + ::syslog(LOG_WARNING, "Failed to open file '%s': " + "%s", pFileName, + GetErrorMessage(winerrno).c_str()); + + return INVALID_HANDLE_VALUE; + } + + if (flags & O_APPEND) + { + if (SetFilePointer(hdir, 0, NULL, FILE_END) == + INVALID_SET_FILE_POINTER) + { + winerrno = GetLastError(); + errno = EINVAL; + CloseHandle(hdir); + return INVALID_HANDLE_VALUE; + } + } + + winerrno = NO_ERROR; + return hdir; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_fstat +// Purpose: replacement for fstat. Supply a windows handle. +// Returns a struct emu_stat to have room for 64-bit +// file identifier in st_ino (mingw allows only 16!) +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int emu_fstat(HANDLE hdir, struct emu_stat * st) +{ + if (hdir == INVALID_HANDLE_VALUE) + { + ::syslog(LOG_ERR, "Error: invalid file handle in emu_fstat()"); + errno = EBADF; + return -1; + } + + BY_HANDLE_FILE_INFORMATION fi; + if (!GetFileInformationByHandle(hdir, &fi)) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, "Failed to read file information: " + "%s", GetErrorMessage(winerrno).c_str()); + errno = EACCES; + return -1; + } + + if (INVALID_FILE_ATTRIBUTES == fi.dwFileAttributes) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, "Failed to get file attributes: " + "%s", GetErrorMessage(winerrno).c_str()); + errno = EACCES; + return -1; + } + + memset(st, 0, sizeof(*st)); + + // This is how we get our INODE (equivalent) information + ULARGE_INTEGER conv; + conv.HighPart = fi.nFileIndexHigh; + conv.LowPart = fi.nFileIndexLow; + st->st_ino = conv.QuadPart; + + // get the time information + st->st_ctime = ConvertFileTimeToTime_t(&fi.ftCreationTime); + st->st_atime = ConvertFileTimeToTime_t(&fi.ftLastAccessTime); + st->st_mtime = ConvertFileTimeToTime_t(&fi.ftLastWriteTime); + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + st->st_size = 0; + } + else + { + conv.HighPart = fi.nFileSizeHigh; + conv.LowPart = fi.nFileSizeLow; + st->st_size = conv.QuadPart; + } + + // at the mo + st->st_uid = 0; + st->st_gid = 0; + st->st_nlink = 1; + + // the mode of the file + // mode zero will make it impossible to restore on Unix + // (no access to anybody, including the owner). + // we'll fake a sensible mode: + // all objects get user read (0400) + // if it's a directory it gets user execute (0100) + // if it's not read-only it gets user write (0200) + st->st_mode = S_IREAD; + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + st->st_mode |= S_IFDIR | S_IEXEC; + } + else + { + st->st_mode |= S_IFREG; + } + + if (!(fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) + { + st->st_mode |= S_IWRITE; + } + + // st_dev is normally zero, regardless of the drive letter, + // since backup locations can't normally span drives. However, + // a reparse point does allow all kinds of weird stuff to happen. + // We set st_dev to 1 for a reparse point, so that Box will detect + // a change of device number (from 0) and refuse to recurse down + // the reparse point (which could lead to havoc). + + if (fi.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) + { + st->st_dev = 1; + } + else + { + st->st_dev = 0; + } + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: OpenFileByNameUtf8 +// Purpose: Converts filename to Unicode and returns +// a handle to it. In case of error, sets errno, +// logs the error and returns NULL. +// Created: 10th December 2004 +// +// -------------------------------------------------------------------------- +HANDLE OpenFileByNameUtf8(const char* pFileName, DWORD flags) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return NULL; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + return NULL; + } + + HANDLE handle = CreateFileW(pBuffer, + flags, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + + if (handle == INVALID_HANDLE_VALUE) + { + // if our open fails we should always be able to + // open in this mode - to get the inode information + // at least one process must have the file open - + // in this case someone else does. + handle = CreateFileW(pBuffer, + READ_CONTROL, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + } + + delete [] pBuffer; + + if (handle == INVALID_HANDLE_VALUE) + { + winerrno = GetLastError(); + + if (winerrno == ERROR_FILE_NOT_FOUND || + winerrno == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else + { + ::syslog(LOG_WARNING, "Failed to open '%s': " + "%s", pFileName, + GetErrorMessage(winerrno).c_str()); + errno = EACCES; + } + + return NULL; + } + + return handle; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_stat +// Purpose: replacement for the lstat and stat functions. +// Works with unicode filenames supplied in utf8. +// Returns a struct emu_stat to have room for 64-bit +// file identifier in st_ino (mingw allows only 16!) +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int emu_stat(const char * pName, struct emu_stat * st) +{ + HANDLE handle = OpenFileByNameUtf8(pName, + FILE_READ_ATTRIBUTES | FILE_READ_EA); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + int retVal = emu_fstat(handle, st); + if (retVal != 0) + { + // error logged, but without filename + ::syslog(LOG_WARNING, "Failed to get file information " + "for '%s'", pName); + } + + // close the handle + CloseHandle(handle); + + return retVal; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: statfs +// Purpose: returns the mount point of where a file is located - +// in this case the volume serial number +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int statfs(const char * pName, struct statfs * s) +{ + HANDLE handle = OpenFileByNameUtf8(pName, + FILE_READ_ATTRIBUTES | FILE_READ_EA); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + BY_HANDLE_FILE_INFORMATION fi; + if (!GetFileInformationByHandle(handle, &fi)) + { + winerrno = GetLastError(); + ::syslog(LOG_WARNING, "Failed to get file information " + "for '%s': %s", pName, + GetErrorMessage(winerrno).c_str()); + CloseHandle(handle); + errno = EACCES; + return -1; + } + + // convert volume serial number to a string + _ui64toa(fi.dwVolumeSerialNumber, s->f_mntonname + 1, 16); + + // pseudo unix mount point + s->f_mntonname[0] = '\\'; + + CloseHandle(handle); // close the handle + + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_utimes +// Purpose: replacement for the POSIX utimes() function, +// works with unicode filenames supplied in utf8 format, +// sets creation time instead of last access time. +// Created: 25th July 2006 +// +// -------------------------------------------------------------------------- +int emu_utimes(const char * pName, const struct timeval times[]) +{ + FILETIME creationTime; + if (!ConvertTime_tToFileTime(times[0].tv_sec, &creationTime)) + { + errno = EINVAL; + return -1; + } + + FILETIME modificationTime; + if (!ConvertTime_tToFileTime(times[1].tv_sec, &modificationTime)) + { + errno = EINVAL; + return -1; + } + + HANDLE handle = OpenFileByNameUtf8(pName, FILE_WRITE_ATTRIBUTES); + + if (handle == NULL) + { + // errno already set and error logged by OpenFileByNameUtf8() + return -1; + } + + if (!SetFileTime(handle, &creationTime, NULL, &modificationTime)) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to set times on '%s': %s", pName, + GetErrorMessage(winerrno).c_str()); + CloseHandle(handle); + return 1; + } + + CloseHandle(handle); + return 0; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: emu_chmod +// Purpose: replacement for the POSIX chmod function, +// works with unicode filenames supplied in utf8 format +// Created: 26th July 2006 +// +// -------------------------------------------------------------------------- +int emu_chmod(const char * pName, mode_t mode) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + // We are responsible for freeing pBuffer + + if (pBuffer == NULL) + { + // error already logged by ConvertUtf8ToWideString() + free(pBuffer); + return -1; + } + + DWORD attribs = GetFileAttributesW(pBuffer); + if (attribs == INVALID_FILE_ATTRIBUTES) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to get file attributes of '%s': %s", + pName, GetErrorMessage(winerrno).c_str()); + errno = EACCES; + free(pBuffer); + return -1; + } + + if (mode & S_IWRITE) + { + attribs &= ~FILE_ATTRIBUTE_READONLY; + } + else + { + attribs |= FILE_ATTRIBUTE_READONLY; + } + + if (!SetFileAttributesW(pBuffer, attribs)) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to set file attributes of '%s': %s", + pName, GetErrorMessage(winerrno).c_str()); + errno = EACCES; + free(pBuffer); + return -1; + } + + delete [] pBuffer; + return 0; +} + + +// -------------------------------------------------------------------------- +// +// Function +// Name: opendir +// Purpose: replacement for unix function, uses win32 findfirst routines +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +DIR *opendir(const char *name) +{ + if (!name || !name[0]) + { + errno = EINVAL; + return NULL; + } + + std::string dirName(name); + + //append a '\' win32 findfirst is sensitive to this + if (dirName[dirName.size()-1] != '\\' || dirName[dirName.size()-1] != '/') + { + dirName += '\\'; + } + + // what is the search string? - everything + dirName += '*'; + + DIR *pDir = new DIR; + if (pDir == NULL) + { + errno = ENOMEM; + return NULL; + } + + pDir->name = ConvertUtf8ToWideString(dirName.c_str()); + // We are responsible for freeing dir->name with delete[] + + if (pDir->name == NULL) + { + delete pDir; + return NULL; + } + + pDir->fd = FindFirstFileW(pDir->name, &pDir->info); + + if (pDir->fd == INVALID_HANDLE_VALUE) + { + delete [] pDir->name; + delete pDir; + return NULL; + } + + pDir->result.d_name = 0; + return pDir; +} + +// this kinda makes it not thread friendly! +// but I don't think it needs to be. +char tempbuff[MAX_PATH]; + +// -------------------------------------------------------------------------- +// +// Function +// Name: readdir +// Purpose: as function above +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +struct dirent *readdir(DIR *dp) +{ + try + { + struct dirent *den = NULL; + + 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, + -1, &tempbuff[0], sizeof (tempbuff), + NULL, NULL); + //den->d_name = (char *)dp->info.name; + den->d_name = &tempbuff[0]; + 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; + } + } + } + else + { + errno = EBADF; + } + + return den; + } + catch (...) + { + printf("Caught readdir"); + } + return NULL; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: closedir +// Purpose: as function above +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int closedir(DIR *dp) +{ + try + { + BOOL finres = false; + + if (dp) + { + if(dp->fd != INVALID_HANDLE_VALUE) + { + finres = FindClose(dp->fd); + } + + delete [] dp->name; + delete dp; + } + + if (finres == FALSE) // errors go to EBADF + { + winerrno = GetLastError(); + errno = EBADF; + } + + return (finres == TRUE) ? 0 : -1; + } + catch (...) + { + printf("Caught closedir"); + } + return -1; +} + +// -------------------------------------------------------------------------- +// +// Function +// Name: poll +// Purpose: a weak implimentation (just enough for box) +// of the unix poll for winsock2 +// Created: 25th October 2004 +// +// -------------------------------------------------------------------------- +int poll (struct pollfd *ufds, unsigned long nfds, int timeout) +{ + try + { + fd_set readfd; + fd_set writefd; + + FD_ZERO(&readfd); + FD_ZERO(&writefd); + + // struct pollfd *ufdsTmp = ufds; + + timeval timOut; + timeval *tmpptr; + + if (timeout == INFTIM) + tmpptr = NULL; + else + tmpptr = &timOut; + + timOut.tv_sec = timeout / 1000; + timOut.tv_usec = timeout * 1000; + + for (unsigned long i = 0; i < nfds; i++) + { + struct pollfd* ufd = &(ufds[i]); + + if (ufd->events & POLLIN) + { + FD_SET(ufd->fd, &readfd); + } + + if (ufd->events & POLLOUT) + { + FD_SET(ufd->fd, &writefd); + } + + if (ufd->events & ~(POLLIN | POLLOUT)) + { + printf("Unsupported poll bits %d", + ufd->events); + return -1; + } + } + + int nready = select(0, &readfd, &writefd, 0, tmpptr); + + if (nready == SOCKET_ERROR) + { + // int errval = WSAGetLastError(); + + struct pollfd* pufd = ufds; + for (unsigned long i = 0; i < nfds; i++) + { + pufd->revents = POLLERR; + pufd++; + } + return (-1); + } + else if (nready > 0) + { + for (unsigned long i = 0; i < nfds; i++) + { + struct pollfd *ufd = &(ufds[i]); + + if (FD_ISSET(ufd->fd, &readfd)) + { + ufd->revents |= POLLIN; + } + + if (FD_ISSET(ufd->fd, &writefd)) + { + ufd->revents |= POLLOUT; + } + } + } + + return nready; + } + catch (...) + { + printf("Caught poll"); + } + + return -1; +} + +// copied from MSDN: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/eventlog/base/adding_a_source_to_the_registry.asp + +BOOL AddEventSource +( + LPTSTR pszSrcName, // event source name + DWORD dwNum // number of categories +) +{ + // Work out the executable file name, to register ourselves + // as the event source + + WCHAR cmd[MAX_PATH]; + DWORD len = GetModuleFileNameW(NULL, cmd, MAX_PATH); + + if (len == 0) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to get the program file name: %s", + GetErrorMessage(winerrno).c_str()); + return FALSE; + } + + // Create the event source as a subkey of the log. + + std::string regkey("SYSTEM\\CurrentControlSet\\Services\\EventLog\\" + "Application\\"); + regkey += pszSrcName; + + HKEY hk; + DWORD dwDisp; + + winerrno = RegCreateKeyEx(HKEY_LOCAL_MACHINE, regkey.c_str(), + 0, NULL, REG_OPTION_NON_VOLATILE, + KEY_WRITE, NULL, &hk, &dwDisp); + if (winerrno == ERROR_ACCESS_DENIED) + { + ::syslog(LOG_ERR, "Failed to create the registry key: access denied. You must " + "be an Administrator to register new event sources in %s", regkey.c_str()); + return FALSE; + } + else if (winerrno != ERROR_SUCCESS) + { + ::syslog(LOG_ERR, "Failed to create the registry key: %s: %s", + GetErrorMessage(winerrno).c_str(), regkey.c_str()); + return FALSE; + } + + // Set the name of the message file. + + winerrno = RegSetValueExW(hk, // subkey handle + L"EventMessageFile", // value name + 0, // must be zero + REG_EXPAND_SZ, // value type + (LPBYTE)cmd, // pointer to value data + len*sizeof(WCHAR)); // data size + if (winerrno != ERROR_SUCCESS) + { + ::syslog(LOG_ERR, "Failed to set the event message file: %s", + GetErrorMessage(winerrno).c_str()); + RegCloseKey(hk); + return FALSE; + } + + // Set the supported event types. + + DWORD dwData = EVENTLOG_ERROR_TYPE | EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + + winerrno = RegSetValueEx(hk, // subkey handle + "TypesSupported", // value name + 0, // must be zero + REG_DWORD, // value type + (LPBYTE) &dwData, // pointer to value data + sizeof(DWORD)); // length of value data + if (winerrno != ERROR_SUCCESS) + { + ::syslog(LOG_ERR, "Failed to set the supported types: %s", + GetErrorMessage(winerrno).c_str()); + RegCloseKey(hk); + return FALSE; + } + + // Set the category message file and number of categories. + + winerrno = RegSetValueExW(hk, // subkey handle + L"CategoryMessageFile", // value name + 0, // must be zero + REG_EXPAND_SZ, // value type + (LPBYTE)cmd, // pointer to value data + len*sizeof(WCHAR)); // data size + if (winerrno != ERROR_SUCCESS) + { + ::syslog(LOG_ERR, "Failed to set the category message file: " + "%s", GetErrorMessage(winerrno).c_str()); + RegCloseKey(hk); + return FALSE; + } + + winerrno = RegSetValueEx(hk, // subkey handle + "CategoryCount", // value name + 0, // must be zero + REG_DWORD, // value type + (LPBYTE) &dwNum, // pointer to value data + sizeof(DWORD)); // length of value data + if (winerrno != ERROR_SUCCESS) + { + ::syslog(LOG_ERR, "Failed to set the category count: %s", + GetErrorMessage(winerrno).c_str()); + RegCloseKey(hk); + return FALSE; + } + + RegCloseKey(hk); + return TRUE; +} + +static HANDLE gSyslogH = INVALID_HANDLE_VALUE; +static bool sHaveWarnedEventLogFull = false; + +void openlog(const char * daemonName, int, int) +{ + std::string nameStr = "Box Backup ("; + nameStr += daemonName; + nameStr += ")"; + + // Don't try to open a new handle when one is already open. It will leak handles. + assert(gSyslogH == INVALID_HANDLE_VALUE); + + // Register a default event source, so that we can log errors with the process of + // adding or registering our own, which follows. If this fails, there's not much we + // can do about it, certainly not send anything to the event log! + gSyslogH = RegisterEventSource( + NULL, // uses local computer + nameStr.c_str()); // source name + if (gSyslogH == NULL) + { + gSyslogH = INVALID_HANDLE_VALUE; + } + + char* name = strdup(nameStr.c_str()); + BOOL success = AddEventSource(name, 0); + free(name); + + if (!success) + { + ::syslog(LOG_ERR, "Failed to add our own event source"); + return; + } + + HANDLE newSyslogH = RegisterEventSource(NULL, nameStr.c_str()); + if (newSyslogH == NULL) + { + winerrno = GetLastError(); + ::syslog(LOG_ERR, "Failed to register our own event source: " + "%s", GetErrorMessage(winerrno).c_str()); + return; + } + + DeregisterEventSource(gSyslogH); + gSyslogH = newSyslogH; +} + +void closelog(void) +{ + if(gSyslogH != INVALID_HANDLE_VALUE) + { + DeregisterEventSource(gSyslogH); + gSyslogH = INVALID_HANDLE_VALUE; + } +} + +void syslog(int loglevel, const char *frmt, ...) +{ + WORD errinfo; + char buffer[4096]; + std::string sixfour(frmt); + + switch (loglevel) + { + case LOG_INFO: + errinfo = EVENTLOG_INFORMATION_TYPE; + break; + case LOG_ERR: + errinfo = EVENTLOG_ERROR_TYPE; + break; + case LOG_WARNING: + errinfo = EVENTLOG_WARNING_TYPE; + break; + default: + errinfo = EVENTLOG_WARNING_TYPE; + break; + } + + // taken from MSDN + int sixfourpos; + while ( (sixfourpos = (int)sixfour.find("%ll")) != -1 ) + { + // maintain portability - change the 64 bit formater... + std::string temp = sixfour.substr(0,sixfourpos); + temp += "%I64"; + temp += sixfour.substr(sixfourpos+3, sixfour.length()); + sixfour = temp; + } + + // printf("parsed string is:%s\r\n", sixfour.c_str()); + + va_list args; + va_start(args, frmt); + + int len = vsnprintf(buffer, sizeof(buffer)-1, sixfour.c_str(), args); + assert(len >= 0); + if (len < 0) + { + printf("%s\r\n", buffer); + fflush(stdout); + return; + } + + assert((size_t)len < sizeof(buffer)); + buffer[sizeof(buffer)-1] = 0; + + va_end(args); + + if (gSyslogH == INVALID_HANDLE_VALUE) + { + printf("%s\r\n", buffer); + fflush(stdout); + return; + } + + WCHAR* pWide = ConvertToWideString(buffer, CP_UTF8, false); + // must delete[] pWide + + DWORD result; + + if (pWide == NULL) + { + std::string buffer2 = buffer; + buffer2 += " (failed to convert string encoding)"; + LPCSTR strings[] = { buffer2.c_str(), NULL }; + + result = ReportEventA(gSyslogH, // event log handle + errinfo, // event type + 0, // category zero + MSG_ERR, // event identifier - + // we will call them all the same + NULL, // no user security identifier + 1, // one substitution string + 0, // no data + strings, // pointer to string array + NULL); // pointer to data + } + else + { + LPCWSTR strings[] = { pWide, NULL }; + result = ReportEventW(gSyslogH, // event log handle + errinfo, // event type + 0, // category zero + MSG_ERR, // event identifier - + // we will call them all the same + NULL, // no user security identifier + 1, // one substitution string + 0, // no data + strings, // pointer to string array + NULL); // pointer to data + delete [] pWide; + } + + if (result == 0) + { + winerrno = GetLastError(); + if (winerrno == ERROR_LOG_FILE_FULL) + { + if (!sHaveWarnedEventLogFull) + { + printf("Unable to send message to Event Log " + "(Event Log is full): %s\r\n", buffer); + sHaveWarnedEventLogFull = TRUE; + } + } + else + { + printf("Unable to send message to Event Log: %s: %s\r\n", + GetErrorMessage(winerrno).c_str(), buffer); + } + } + else + { + sHaveWarnedEventLogFull = false; + } +} + +int emu_chdir(const char* pDirName) +{ + /* + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pDirName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + */ + + WCHAR* pBuffer = ConvertUtf8ToWideString(pDirName); + if (!pBuffer) return -1; + + int result = SetCurrentDirectoryW(pBuffer); + delete [] pBuffer; + + if (result != 0) return 0; + + errno = EACCES; + winerrno = GetLastError(); + fprintf(stderr, "Failed to change directory to '%s': %s\n", + pDirName, GetErrorMessage(winerrno).c_str()); + return -1; +} + +char* emu_getcwd(char* pBuffer, int BufSize) +{ + DWORD len = GetCurrentDirectoryW(0, NULL); + if (len == 0) + { + errno = EINVAL; + return NULL; + } + + if ((int)len > BufSize) + { + errno = ENAMETOOLONG; + return NULL; + } + + WCHAR* pWide = new WCHAR [len]; + if (!pWide) + { + errno = ENOMEM; + return NULL; + } + + DWORD result = GetCurrentDirectoryW(len, pWide); + if (result <= 0 || result >= len) + { + errno = EACCES; + delete [] pWide; + return NULL; + } + + char* pUtf8 = ConvertFromWideString(pWide, CP_UTF8); + delete [] pWide; + + if (!pUtf8) + { + return NULL; + } + + strncpy(pBuffer, pUtf8, BufSize - 1); + pBuffer[BufSize - 1] = 0; + delete [] pUtf8; + + return pBuffer; +} + +int emu_mkdir(const char* pPathName) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pPathName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + if (!pBuffer) + { + return -1; + } + + BOOL result = CreateDirectoryW(pBuffer, NULL); + delete [] pBuffer; + + if (!result) + { + errno = EACCES; + return -1; + } + + return 0; +} + +int emu_link(const char* pOldPath, const char* pNewPath) +{ + std::string AbsOldPathWithUnicode = + ConvertPathToAbsoluteUnicode(pOldPath); + + if (AbsOldPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + std::string AbsNewPathWithUnicode = + ConvertPathToAbsoluteUnicode(pNewPath); + + if (AbsNewPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pOldBuffer = ConvertUtf8ToWideString(AbsOldPathWithUnicode.c_str()); + if (!pOldBuffer) + { + return -1; + } + + WCHAR* pNewBuffer = ConvertUtf8ToWideString(AbsNewPathWithUnicode.c_str()); + if (!pNewBuffer) + { + delete [] pOldBuffer; + return -1; + } + + BOOL result = CreateHardLinkW(pNewBuffer, pOldBuffer, NULL); + winerrno = GetLastError(); + delete [] pOldBuffer; + delete [] pNewBuffer; + + if (!result) + { + if (winerrno == ERROR_FILE_NOT_FOUND || + winerrno == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else if (winerrno == ERROR_SHARING_VIOLATION) + { + errno = EBUSY; + } + else if (winerrno == ERROR_ACCESS_DENIED) + { + errno = EACCES; + } + else + { + ::syslog(LOG_WARNING, "Failed to hardlink file " + "'%s' to '%s': %s", pOldPath, pNewPath, + GetErrorMessage(winerrno).c_str()); + errno = ENOSYS; + } + + return -1; + } + + return 0; + +} + +int emu_unlink(const char* pFileName) +{ + std::string AbsPathWithUnicode = + ConvertPathToAbsoluteUnicode(pFileName); + + if (AbsPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pBuffer = ConvertUtf8ToWideString(AbsPathWithUnicode.c_str()); + if (!pBuffer) + { + return -1; + } + + BOOL result = DeleteFileW(pBuffer); + winerrno = GetLastError(); + delete [] pBuffer; + + if (!result) + { + if (winerrno == ERROR_FILE_NOT_FOUND || + winerrno == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else if (winerrno == ERROR_SHARING_VIOLATION) + { + errno = EBUSY; + } + else if (winerrno == ERROR_ACCESS_DENIED) + { + errno = EACCES; + } + else + { + ::syslog(LOG_WARNING, "Failed to delete file " + "'%s': %s", pFileName, + GetErrorMessage(winerrno).c_str()); + errno = ENOSYS; + } + + return -1; + } + + return 0; +} + +int emu_rename(const char* pOldFileName, const char* pNewFileName) +{ + std::string OldPathWithUnicode = + ConvertPathToAbsoluteUnicode(pOldFileName); + + if (OldPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + return -1; + } + + WCHAR* pOldBuffer = ConvertUtf8ToWideString(OldPathWithUnicode.c_str()); + if (!pOldBuffer) + { + return -1; + } + + std::string NewPathWithUnicode = + ConvertPathToAbsoluteUnicode(pNewFileName); + + if (NewPathWithUnicode.size() == 0) + { + // error already logged by ConvertPathToAbsoluteUnicode() + delete [] pOldBuffer; + return -1; + } + + WCHAR* pNewBuffer = ConvertUtf8ToWideString(NewPathWithUnicode.c_str()); + if (!pNewBuffer) + { + delete [] pOldBuffer; + return -1; + } + + BOOL result = MoveFileW(pOldBuffer, pNewBuffer); + winerrno = GetLastError(); + delete [] pOldBuffer; + delete [] pNewBuffer; + + if (!result) + { + if (winerrno == ERROR_FILE_NOT_FOUND || + winerrno == ERROR_PATH_NOT_FOUND) + { + errno = ENOENT; + } + else if (winerrno == ERROR_SHARING_VIOLATION) + { + errno = EBUSY; + } + else if (winerrno == ERROR_ACCESS_DENIED) + { + errno = EACCES; + } + else + { + ::syslog(LOG_WARNING, "Failed to rename file " + "'%s' to '%s': %s", pOldFileName, pNewFileName, + GetErrorMessage(winerrno).c_str()); + errno = ENOSYS; + } + return -1; + } + + return 0; +} + +int console_read(char* pBuffer, size_t BufferSize) +{ + HANDLE hConsole = GetStdHandle(STD_INPUT_HANDLE); + + if (hConsole == INVALID_HANDLE_VALUE) + { + winerrno = GetLastError(); + ::fprintf(stderr, "Failed to get a handle on standard input: " + "%s", GetErrorMessage(winerrno).c_str()); + return -1; + } + + size_t WideSize = BufferSize / 5; + WCHAR* pWideBuffer = new WCHAR [WideSize + 1]; + + if (!pWideBuffer) + { + ::perror("Failed to allocate wide character buffer"); + return -1; + } + + DWORD numCharsRead = 0; + + if (!ReadConsoleW( + hConsole, + pWideBuffer, + WideSize, // will not be null terminated by ReadConsole + &numCharsRead, + NULL // reserved + )) + { + winerrno = GetLastError(); + ::fprintf(stderr, "Failed to read from console: %s\n", + GetErrorMessage(winerrno).c_str()); + return -1; + } + + pWideBuffer[numCharsRead] = 0; + + char* pUtf8 = ConvertFromWideString(pWideBuffer, GetConsoleCP()); + delete [] pWideBuffer; + + strncpy(pBuffer, pUtf8, BufferSize); + delete [] pUtf8; + + return strlen(pBuffer); +} + +int readv (int filedes, const struct iovec *vector, size_t count) +{ + int bytes = 0; + + for (size_t i = 0; i < count; i++) + { + int result = read(filedes, vector[i].iov_base, + vector[i].iov_len); + if (result < 0) + { + return result; + } + bytes += result; + } + + return bytes; +} + +int writev(int filedes, const struct iovec *vector, size_t count) +{ + int bytes = 0; + + for (size_t i = 0; i < count; i++) + { + int result = write(filedes, vector[i].iov_base, + vector[i].iov_len); + if (result < 0) + { + return result; + } + bytes += result; + } + + return bytes; +} + +// Need this for conversions. Works in UTC. +time_t ConvertFileTimeToTime_t(FILETIME *fileTime) +{ + SYSTEMTIME stUTC; + struct tm timeinfo; + + // Convert the last-write time to local time. + FileTimeToSystemTime(fileTime, &stUTC); + + memset(&timeinfo, 0, sizeof(timeinfo)); + timeinfo.tm_sec = stUTC.wSecond; + timeinfo.tm_min = stUTC.wMinute; + timeinfo.tm_hour = stUTC.wHour; + timeinfo.tm_mday = stUTC.wDay; + timeinfo.tm_wday = stUTC.wDayOfWeek; + timeinfo.tm_mon = stUTC.wMonth - 1; + // timeinfo.tm_yday = ...; + timeinfo.tm_year = stUTC.wYear - 1900; + + time_t retVal = _mkgmtime(&timeinfo); + return retVal; +} + +bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo) +{ + struct tm *time_breakdown = gmtime(&from); + if (time_breakdown == NULL) + { + ::syslog(LOG_ERR, "Error: failed to convert time format: " + "%d is not a valid time\n", from); + return false; + } + + SYSTEMTIME stUTC; + stUTC.wSecond = time_breakdown->tm_sec; + stUTC.wMinute = time_breakdown->tm_min; + stUTC.wHour = time_breakdown->tm_hour; + stUTC.wDay = time_breakdown->tm_mday; + stUTC.wDayOfWeek = time_breakdown->tm_wday; + stUTC.wMonth = time_breakdown->tm_mon + 1; + stUTC.wYear = time_breakdown->tm_year + 1900; + stUTC.wMilliseconds = 0; + + // Convert the last-write time to local time. + if (!SystemTimeToFileTime(&stUTC, pTo)) + { + winerrno = GetLastError(); + syslog(LOG_ERR, "Failed to convert between time formats: %s", + GetErrorMessage(winerrno).c_str()); + return false; + } + + return true; +} + +#endif // WIN32 + diff --git a/lib/win32/emu.h b/lib/win32/emu.h new file mode 100644 index 00000000..b8539bb6 --- /dev/null +++ b/lib/win32/emu.h @@ -0,0 +1,433 @@ +// emulates unix syscalls to win32 functions + +#include "emu_winver.h" + +#ifdef WIN32 + #define EMU_STRUCT_STAT struct emu_stat + #define EMU_STAT emu_stat + #define EMU_FSTAT emu_fstat + #define EMU_LSTAT emu_stat +#else + #define EMU_STRUCT_STAT struct stat + #define EMU_STAT ::stat + #define EMU_FSTAT ::fstat + #define EMU_LSTAT ::lstat +#endif + +#if ! defined EMU_INCLUDE && defined WIN32 +#define EMU_INCLUDE + +// Need feature detection macros below +#ifdef _MSC_VER +# include "../common/BoxConfig-MSVC.h" +# define NEED_BOX_VERSION_H +#else +# include "../common/BoxConfig.h" +#endif + +// Shut up stupid new warnings. Thanks MinGW! Ever heard of "compatibility"? +#ifdef __MINGW32__ +# define __MINGW_FEATURES__ 0 +#endif + +// basic types, may be required by other headers since we +// don't include sys/types.h +#include + +// emulated types, present on MinGW but not MSVC or vice versa + +#ifndef __MINGW32__ + typedef unsigned int mode_t; + typedef unsigned int pid_t; +#endif + +// Windows headers + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// emulated functions + +#define gmtime_r( _clock, _result ) \ + ( *(_result) = *gmtime( (_clock) ), \ + (_result) ) + +#define ITIMER_REAL 0 + +struct passwd { + char *pw_name; + char *pw_passwd; + int pw_uid; + int pw_gid; + time_t pw_change; + char *pw_class; + char *pw_gecos; + char *pw_dir; + char *pw_shell; + time_t pw_expire; +}; + +extern passwd gTempPasswd; +inline struct passwd * getpwnam(const char * name) +{ + //for the mo pretend to be root + gTempPasswd.pw_uid = 0; + gTempPasswd.pw_gid = 0; + + return &gTempPasswd; +} + +#ifndef S_IRGRP + // these constants are only defined in MinGW64, not the original MinGW headers, + // nor MSVC, so use poor man's feature detection to define them only if needed. + //not sure if these are correct + //S_IWRITE - writing permitted + //_S_IREAD - reading permitted + //_S_IREAD | _S_IWRITE - +# define S_IRUSR S_IWRITE +# define S_IWUSR S_IREAD +# define S_IRGRP S_IWRITE +# define S_IWGRP S_IREAD +# define S_IROTH S_IWRITE | S_IREAD +# define S_IWOTH S_IREAD | S_IREAD +# define S_IRWXU (S_IREAD|S_IWRITE|S_IEXEC) +# define S_IRWXG 1 +# define S_IRWXO 2 +#endif + +#define S_ISUID 4 +#define S_ISGID 8 +#define S_ISVTX 16 + +#ifndef __MINGW32__ + #define S_ISREG(x) (S_IFREG & x) + #define S_ISDIR(x) (S_IFDIR & x) +#endif + +inline int chown(const char * Filename, uint32_t uid, uint32_t gid) +{ + //important - this needs implementing + //If a large restore is required then + //it needs to restore files AND permissions + //reference AdjustTokenPrivileges + //GetAccountSid + //InitializeSecurityDescriptor + //SetSecurityDescriptorOwner + //The next function looks like the guy to use... + //SetFileSecurity + + //indicate success + return 0; +} + +// Windows and Unix owners and groups are pretty fundamentally different. +// Ben prefers that we kludge here rather than litter the code with #ifdefs. +// Pretend to be root, and pretend that set...() operations succeed. +inline int setegid(int) +{ + return true; +} +inline int seteuid(int) +{ + return true; +} +inline int setgid(int) +{ + return true; +} +inline int setuid(int) +{ + return true; +} +inline int getgid(void) +{ + return 0; +} +inline int getuid(void) +{ + return 0; +} +inline int geteuid(void) +{ + return 0; +} + +#ifndef PATH_MAX +#define PATH_MAX MAX_PATH +#endif + +// MinGW provides a getopt implementation +#ifndef __MINGW32__ +#include "box_getopt.h" +#endif // !__MINGW32__ + +#define timespec timeval + +//win32 deals in usec not nsec - so need to ensure this follows through +#define tv_nsec tv_usec + +#ifndef __MINGW32__ + typedef int socklen_t; +#endif + +//again need to verify these +#define S_IFLNK 1 +#define S_IFSOCK 0 + +#define S_ISLNK(x) ( false ) + +#define vsnprintf _vsnprintf + +#ifndef __MINGW32__ +#define snprintf _snprintf +inline int strcasecmp(const char *s1, const char *s2) +{ + return _stricmp(s1, s2); +} +inline int strncasecmp(const char *s1, const char *s2, size_t count) +{ + return _strnicmp(s1, s2, count); +} +#endif + +#ifdef _DIRENT_H_ +#error You must not include the MinGW dirent.h! +#endif + +struct dirent +{ + char *d_name; + DWORD d_type; // file attributes +}; + +struct DIR +{ + 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 +}; + +DIR *opendir(const char *name); +struct dirent *readdir(DIR *dp); +int closedir(DIR *dp); + +// local constant to open file exclusively without shared access +#define BOX_OPEN_LOCK 0x10000 + +extern DWORD winerrno; /* used to report errors from openfile() */ +HANDLE openfile(const char *filename, int flags, int mode); +inline int closefile(HANDLE handle) +{ + if (CloseHandle(handle) != TRUE) + { + errno = EINVAL; + return -1; + } + return 0; +} + +#define LOG_DEBUG LOG_INFO +#define LOG_INFO 6 +#define LOG_NOTICE LOG_INFO +#define LOG_WARNING 4 +#define LOG_ERR 3 +#define LOG_CRIT LOG_ERR +#define LOG_PID 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 + +void openlog (const char * daemonName, int, int); +void closelog(void); +void syslog (int loglevel, const char *fmt, ...); + +#define LOG_LOCAL0 0 +#define LOG_LOCAL1 0 +#define LOG_LOCAL2 0 +#define LOG_LOCAL3 0 +#define LOG_LOCAL4 0 +#define LOG_LOCAL5 0 +#define LOG_LOCAL6 0 +#define LOG_DAEMON 0 + +#ifndef __MINGW32__ +#define strtoll _strtoi64 +#endif + +extern "C" inline unsigned int sleep(unsigned int secs) +{ + Sleep(secs*1000); + return(ERROR_SUCCESS); +} + +#define INFTIM -1 + +#ifndef POLLIN +# define POLLIN 0x1 +#endif + +#ifndef POLLERR +# define POLLERR 0x8 +#endif + +#ifndef POLLOUT +# define POLLOUT 0x4 +#endif + +#define SHUT_RDWR SD_BOTH +#define SHUT_RD SD_RECEIVE +#define SHUT_WR SD_SEND + +struct pollfd +{ + SOCKET fd; + short int events; + short int revents; +}; + +inline int ioctl(SOCKET sock, int flag, int * something) +{ + //indicate success + return 0; +} + +inline int waitpid(pid_t pid, int *status, int) +{ + return 0; +} + +//this shouldn't be needed. +struct statfs +{ + TCHAR f_mntonname[MAX_PATH]; +}; + +struct emu_stat { + int st_dev; + uint64_t st_ino; + DWORD st_mode; + short st_nlink; + short st_uid; + short st_gid; + //_dev_t st_rdev; + uint64_t st_size; + time_t st_atime; + time_t st_mtime; + time_t st_ctime; +}; + +// need this for conversions +time_t ConvertFileTimeToTime_t(FILETIME *fileTime); +bool ConvertTime_tToFileTime(const time_t from, FILETIME *pTo); + +int emu_chdir (const char* pDirName); +int emu_mkdir (const char* pPathName); +int emu_link (const char* pOldPath, const char* pNewPath); +int emu_unlink (const char* pFileName); +int emu_fstat (HANDLE file, struct emu_stat* st); +int emu_stat (const char* pName, struct emu_stat* st); +int emu_utimes (const char* pName, const struct timeval[]); +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); + +#define chdir(directory) emu_chdir (directory) +#define mkdir(path, mode) emu_mkdir (path) +#define link(oldpath, newpath) emu_link (oldpath, newpath) +#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 rename(oldname, newname) emu_rename (oldname, newname) + +// Not safe to replace stat/fstat/lstat on mingw at least, as struct stat +// has a 16-bit st_ino and we need a 64-bit one. +// +// #define stat(filename, struct) emu_stat (filename, struct) +// #define lstat(filename, struct) emu_stat (filename, struct) +// #define fstat(handle, struct) emu_fstat (handle, struct) +// +// But lstat doesn't exist on Windows, so we have to provide something: + +#define lstat(filename, struct) stat(filename, struct) + +int statfs(const char * name, struct statfs * s); + +int poll(struct pollfd *ufds, unsigned long nfds, int timeout); + +struct iovec { + void *iov_base; /* Starting address */ + size_t iov_len; /* Number of bytes */ +}; + +int readv (int filedes, const struct iovec *vector, size_t count); +int writev(int filedes, const struct iovec *vector, size_t count); + +// The following functions are not emulations, but utilities for other +// parts of the code where Windows API is used or windows-specific stuff +// is needed, like codepage conversion. + +bool EnableBackupRights( void ); + +bool ConvertEncoding (const std::string& rSource, int sourceCodePage, + std::string& rDest, int destCodePage); +bool ConvertToUtf8 (const std::string& rSource, std::string& rDest, + int sourceCodePage); +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); +char* ConvertFromWideString(const WCHAR* pString, unsigned int codepage); +bool ConvertFromWideString(const std::wstring& rInput, + std::string* pOutput, unsigned int codepage); +WCHAR* ConvertUtf8ToWideString(const char* pString); +std::string ConvertPathToAbsoluteUnicode(const char *pFileName); + +// Utility function which returns a default config file name, +// based on the path of the current executable. +std::string GetDefaultConfigFilePath(const std::string& rName); + +// GetErrorMessage() returns a system error message, like strerror() +// but for Windows error codes. +std::string GetErrorMessage(DWORD errorCode); + +// console_read() is a replacement for _cgetws which requires a +// relatively recent C runtime lib +int console_read(char* pBuffer, size_t BufferSize); + +// 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 + +#ifdef _MSC_VER + /* disable certain compiler warnings to be able to actually see the show-stopper ones */ + #pragma warning(disable:4101) // unreferenced local variable + #pragma warning(disable:4244) // conversion, possible loss of data + #pragma warning(disable:4267) // conversion, possible loss of data + #pragma warning(disable:4311) // pointer truncation + #pragma warning(disable:4700) // uninitialized local variable used (hmmmmm...) + #pragma warning(disable:4805) // unsafe mix of type and type 'bool' in operation + #pragma warning(disable:4800) // forcing value to bool 'true' or 'false' (performance warning) + #pragma warning(disable:4996) // POSIX name for this item is deprecated +#endif // _MSC_VER + +#endif // !EMU_INCLUDE && WIN32 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 new file mode 100755 index 00000000..af2833a1 --- /dev/null +++ b/lib/win32/getopt_long.cpp @@ -0,0 +1,546 @@ +/* $OpenBSD: getopt_long.c,v 1.20 2005/10/25 15:49:37 jmc Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ +// Adapted for Box Backup by Chris Wilson + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``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 FOUNDATION OR CONTRIBUTORS + * 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. + */ + +// #include "Box.h" +#include "emu.h" + +#include +#include +#include +#include +#include + +#include "box_getopt.h" + +#ifdef REPLACE_GETOPT // until end of file + +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static void warnx(const char* fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + fprintf(stderr, "\n"); +} + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + const char * oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; + + if (options == NULL) + return (-1); + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + if (posixly_correct == -1) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); + if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + optchar == (int)'-' && *place != '\0' || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + /* XXX: disable test for :: if PC? (GNU doesn't) */ + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } else if (!(flags & FLAG_PERMUTE)) { + /* + * If permutation is disabled, we can accept an + * optional arg separated by whitespace so long + * as it does not start with a dash (-). + */ + if (optind + 1 < nargc && *nargv[optind + 1] != '-') + optarg = nargv[++optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} + +#endif // REPLACE_GETOPT diff --git a/lib/win32/messages.h b/lib/win32/messages.h new file mode 100755 index 00000000..6959591b --- /dev/null +++ b/lib/win32/messages.h @@ -0,0 +1,57 @@ + // Message source file, to be compiled to a resource file with + // Microsoft Message Compiler (MC), to an object file with a Resource + // Compiler, and linked into the application. + + // The main reason for this file is to work around Windows' stupid + // messages in the Event Log, which say: + + // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) + // cannot be found. The local computer may not have the necessary + // registry information or message DLL files to display messages from a + // remote computer. The following information is part of the event: + // Message definitions follow +// +// Values are 32 bit values layed out as follows: +// +// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 +// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 +// +---+-+-+-----------------------+-------------------------------+ +// |Sev|C|R| Facility | Code | +// +---+-+-+-----------------------+-------------------------------+ +// +// where +// +// Sev - is the severity code +// +// 00 - Success +// 01 - Informational +// 10 - Warning +// 11 - Error +// +// C - is the Customer code flag +// +// R - is a reserved bit +// +// Facility - is the facility code +// +// Code - is the facility's status code +// +// +// Define the facility codes +// + + +// +// Define the severity codes +// + + +// +// MessageId: MSG_ERR +// +// MessageText: +// +// %1 +// +#define MSG_ERR ((DWORD)0x40000001L) + diff --git a/lib/win32/messages.mc b/lib/win32/messages.mc new file mode 100644 index 00000000..75f17b0f --- /dev/null +++ b/lib/win32/messages.mc @@ -0,0 +1,22 @@ +; // Message source file, to be compiled to a resource file with +; // Microsoft Message Compiler (MC), to an object file with a Resource +; // Compiler, and linked into the application. +; +; // The main reason for this file is to work around Windows' stupid +; // messages in the Event Log, which say: +; +; // The description for Event ID ( 4 ) in Source ( Box Backup (bbackupd) ) +; // cannot be found. The local computer may not have the necessary +; // registry information or message DLL files to display messages from a +; // remote computer. The following information is part of the event: + +MessageIdTypedef = DWORD + +; // Message definitions follow + +MessageId = 0x1 +Severity = Informational +SymbolicName = MSG_ERR +Language = English +%1 +. diff --git a/lib/win32/messages.rc b/lib/win32/messages.rc new file mode 100755 index 00000000..116522b7 --- /dev/null +++ b/lib/win32/messages.rc @@ -0,0 +1,2 @@ +LANGUAGE 0x9,0x1 +1 11 MSG00001.bin diff --git a/modules.txt b/modules.txt new file mode 100644 index 00000000..71ae16fd --- /dev/null +++ b/modules.txt @@ -0,0 +1,54 @@ + +# first entry is module name, next entries are dependencies or -l library includes + +# put !, after a module / library to exclude it from a particular platform +# put !+,... to include it only on those platforms + +# -l libaries must be in the order they should appear on the command line. +# Note that order is important on platforms which do not have shared libraries. + +# Generic support code and modules + +lib/raidfile +lib/crypto +lib/server qdbm lib/crypto +lib/compress +lib/intercept + +test/common qdbm +test/crypto qdbm lib/crypto +test/compress qdbm lib/compress +test/raidfile qdbm lib/raidfile lib/intercept +test/basicserver qdbm lib/server + +# IF_DISTRIBUTION(boxbackup) + +# Backup system + +lib/backupstore lib/server lib/raidfile lib/crypto lib/compress lib/httpserver +lib/backupclient lib/backupstore +lib/bbackupd lib/backupclient qdbm +lib/bbackupquery lib/backupclient +lib/bbstored lib/backupstore + +bin/bbackupobjdump lib/backupclient +bin/bbstored lib/bbstored +bin/bbstoreaccounts lib/backupclient +bin/bbackupd lib/bbackupd +bin/bbackupquery lib/bbackupquery +bin/bbackupctl lib/backupclient qdbm lib/bbackupd + +test/backupstore bin/bbstored bin/bbstoreaccounts lib/server lib/backupstore lib/backupclient lib/raidfile +test/backupstorefix bin/bbstored bin/bbstoreaccounts lib/backupclient bin/bbackupquery bin/bbackupd bin/bbackupctl +test/backupstorepatch bin/bbstored bin/bbstoreaccounts lib/backupclient +test/backupdiff lib/backupclient +test/bbackupd bin/bbackupd bin/bbstored bin/bbstoreaccounts bin/bbackupquery bin/bbackupctl lib/bbackupquery lib/bbackupd lib/server lib/backupstore lib/backupclient lib/intercept lib/bbstored +bin/s3simulator lib/httpserver +test/s3store lib/backupclient lib/httpserver bin/s3simulator bin/bbstoreaccounts + +# HTTP server system +lib/httpserver lib/server +test/httpserver lib/httpserver + +# END_IF_DISTRIBUTION + diff --git a/parcels.txt b/parcels.txt new file mode 100644 index 00000000..474c576b --- /dev/null +++ b/parcels.txt @@ -0,0 +1,90 @@ + +# this file describes which binaries and scripts go to make +# up a parcel -- a group of files and scripts needed to perform +# a particular task + +backup-client + bin bbackupd + bin bbackupquery + bin bbackupctl + script bin/bbackupd/bbackupd-config + noinstall script COPYING.txt + noinstall script LICENSE-GPL.txt + noinstall script LICENSE-DUAL.txt + + html bbackupd + html bbackupquery + html bbackupctl + html bbackupd-config + html bbackupd.conf + + subdir qdbm libqdbm.a + +EXCEPT:mingw32,mingw32msvc + man bbackupd.8 + man bbackupquery.8 + man bbackupctl.8 + man bbackupd-config.8 + man bbackupd.conf.5 +END-EXCEPT + +ONLY:mingw32,mingw32msvc + script bin/bbackupd/win32/installer.iss + script bin/bbackupd/win32/bbackupd.conf + script bin/bbackupd/win32/NotifySysAdmin.vbs +END-ONLY + +ONLY:mingw32 + script /usr/$ac_target/sys-root/mingw/bin/zlib1.dll + script /usr/$ac_target/sys-root/mingw/bin/libgcc_s_seh-1.dll +END-ONLY + +ONLY:i686-w64-mingw32 + script /usr/$ac_target/sys-root/mingw/bin/libgcc_s_sjlj-1.dll +END-ONLY + +ONLY:SunOS + script contrib/solaris/bbackupd-manifest.xml lib/svc/manifest + script contrib/solaris/bbackupd-smf-method lib/svc/method +END-ONLY + +ONLY:Darwin + script contrib/mac_osx/org.boxbackup.bbackupd.plist /Library/LaunchDaemons +END-ONLY + +backup-server + bin bbstored + bin bbstoreaccounts + script bin/bbstored/bbstored-certs + script bin/bbstored/bbstored-config + script lib/raidfile/raidfile-config + noinstall script COPYING.txt + noinstall script LICENSE-GPL.txt + noinstall script LICENSE-DUAL.txt + + html bbstored + html bbstoreaccounts + html bbstored-certs + html bbstored-config + html raidfile-config + html bbstored.conf + html raidfile.conf + +EXCEPT:mingw32,mingw32msvc + man bbstored.8 + man bbstoreaccounts.8 + man bbstored-certs.8 + man bbstored-config.8 + man raidfile-config.8 + man bbstored.conf.5 + man raidfile.conf.5 +END-EXCEPT + +ONLY:SunOS + script contrib/solaris/bbstored-manifest.xml lib/svc/manifest + script contrib/solaris/bbstored-smf-method lib/svc/method +END-ONLY + +ONLY:Darwin + script contrib/mac_osx/org.boxbackup.bbstored.plist /Library/LaunchDaemons +END-ONLY 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. + + + Copyright (C) + + 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. + + , 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 + + - A potential defect related to leaf division of B+ tree was cleared. + - Release: 1.8.77 + +2007-03-07 Mikio Hirabayashi + + - 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 + + - 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 + + - A bug related to optimization on Intel Mac series was escaped. + - Release: 1.8.74 + +2006-10-20 Mikio Hirabayashi + + - A bug related to BZIP2 encoding was fixed. + - Release: 1.8.73 + +2006-10-06 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.8.72 + +2006-08-24 Mikio Hirabayashi + + - 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 + + - 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 + + - Mutex controll in C++ API became refined. + - Release: 1.8.69 + +2006-08-08 Mikio Hirabayashi + + - 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 + + - A bug about memory alignment was fixed. + - A bug about parsing MIME was fixed. + - Release: 1.8.67 + +2006-08-05 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.8.66 + +2006-08-03 Mikio Hirabayashi + + - The extended API was enhanced. + - The extended advanced API was enhanced. + - Release: 1.8.65 + +2006-07-28 Mikio Hirabayashi + + - A bug of Makefile ralated to optimization was fixed. + - Release: 1.8.64 + +2006-07-24 Mikio Hirabayashi + + - A lot of functions were replaced by macros. + - The utility API was enhanced. + - Release: 1.8.63 + +2006-07-20 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - The basic API was enhanced. + - The advanced API was enhanced. + - Release: 1.8.58 + +2006-05-20 Mikio Hirabayashi + + - The basic API was enhanced. + - The utility API was enhanced. + - Release: 1.8.57 + +2006-05-17 Mikio Hirabayashi + + - A bug of URL decoder was fixed. + - Release: 1.8.56 + +2006-05-15 Mikio Hirabayashi + + - The extended API was enhanced. + - The utility API was enhanced. + - Release: 1.8.55 + +2006-05-15 Mikio Hirabayashi + + - The basic API was enhanced. + - Release: 1.8.54 + +2006-05-10 Mikio Hirabayashi + + - AIX is now supported. + - The utility API was enhanced. + - Release: 1.8.53 + +2006-05-04 Mikio Hirabayashi + + - A bug about evaluating RFC822 date format was fixed. + - Release: 1.8.52 + +2006-05-02 Mikio Hirabayashi + + - 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 + + - The utility API was enhanced. + - Release: 1.8.50 + +2006-04-19 Mikio Hirabayashi + + - 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 + + - The utility API was enhanced. + - Release: 1.8.48 + +2006-03-10 Mikio Hirabayashi + + - LTmakefile was modified. + - The utility API was enhanced. + - Release: 1.8.47 + +2006-02-20 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.8.46 + +2006-01-28 Mikio Hirabayashi + + - Alignment algorithm was improved. + - A bug of mmap emulation on Windows was fixed. + - Release: 1.8.45 + +2006-01-24 Mikio Hirabayashi + + - 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 + + - A bug of mmap emulation on Windows was fixed. + - Release: 1.8.43 + +2006-01-22 Mikio Hirabayashi + + - mmap emulation on Windows was enhanced. + - Release: 1.8.42 + +2006-01-13 Mikio Hirabayashi + + - Compression of pages of B+ tree with LZO and BZIP was added. + - Release: 1.8.41 + +2006-01-08 Mikio Hirabayashi + + - Configuration for VC++ was to build DLL. + - Release: 1.8.40 + +2006-01-04 Mikio Hirabayashi + + - The advanced API was enhanced. + - Release: 1.8.39 + +2006-01-01 Mikio Hirabayashi + + - The advanced API was enhanced. + - Release: 1.8.38 + +2005-12-26 Mikio Hirabayashi + + - 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 + + - Free block pool was enhanced. + - Commands line tools were enhanced. + - Release: 1.8.36 + +2005-11-30 Mikio Hirabayashi + + - 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 + + - A bug of i-node duplication on MinGW was fixed. + - Release: 1.8.34 + +2005-09-09 Mikio Hirabayashi + + - Compressing options of ZLIB was changed. + - The utility API was enhanced. + - Release: 1.8.33 + +2005-08-30 Mikio Hirabayashi + + - A bug of backward string matching was fixed. + - The utility API was enhanced. + - Release: 1.8.32 + +2005-06-19 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - Non-blocking lock was featured. + - Release: 1.8.27 + +2005-05-12 Mikio Hirabayashi + + - The inverted API was enhanced. + - The utility API was enhanced. + - Release: 1.8.26 + +2005-05-10 Mikio Hirabayashi + + - The inverted API was enhanced. + - The utility API was enhanced. + - Release: 1.8.25 + +2005-04-25 Mikio Hirabayashi + + - The utility API was enhanced. + - A bug about database repair on Win32 was fixed. + - Release: 1.8.24 + +2005-04-01 Mikio Hirabayashi + + - The utility API was enhanced. + - The extended advanced API was enhanced. + - Release: 1.8.23 + +2005-03-24 Mikio Hirabayashi + + - 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 + + - Functions to dump endian independent data was added. + - Release: 1.8.21 + +2005-01-05 Mikio Hirabayashi + + - Sparse file support was added. + - Release: 1.8.20 + +2005-01-02 Mikio Hirabayashi + + - The utility API was enhanced. + - Building configurations were enhanced. + - Release: 1.8.19 + +2004-11-10 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - The utility API was enhanced. + - The inverted API was enhanced. + - Release: 1.8.13 + +2004-05-18 Mikio Hirabayashi + + - The utility API was enhanced. + - The inverted API was enhanced. + - Building configurations were enhanced. + - Release: 1.8.12 + +2004-05-12 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - The configuration of C++ API escaped from a bug of pthread. + - Release: 1.8.7 + +2004-03-21 Mikio Hirabayashi + + - The extended API was enhanced. + - Bugs in the utility API about memory management were fixed. + - Release: 1.8.6 + +2004-03-12 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - The utility API was enhanced. + - Release: 1.8.2 + +2004-02-22 Mikio Hirabayashi + + - Processing speed of multi-thread support was improved. + - Release: 1.8.1 + +2004-02-21 Mikio Hirabayashi + + - Switch to make API for C thread-safe was added. + - Release: 1.8.0 + +2004-02-18 Mikio Hirabayashi + + - The utility API was enhanced. + - Potential bugs in APIs for C++ were fixed. + - Release: 1.7.34 + +2004-01-28 Mikio Hirabayashi + + - The extended API was enhanced. + - The inverted API was enhanced. + - Release: 1.7.33 + +2004-01-17 Mikio Hirabayashi + + - The inverted API was enhanced. + - Release: 1.7.32 + +2004-01-16 Mikio Hirabayashi + + - The inverted API was enhanced. + - Release: 1.7.31 + +2004-01-12 Mikio Hirabayashi + + - The inverted API was enhanced. + - Release: 1.7.30 + +2004-01-11 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.7.29 + +2004-01-09 Mikio Hirabayashi + + - A bug in the utility API was fixed. + - Release: 1.7.28 + +2004-01-06 Mikio Hirabayashi + + - A bug in the advanced API was fixed. + - The utility API was enhanced. + - Release: 1.7.27 + +2004-01-04 Mikio Hirabayashi + + - The inverted API was enhanced. + - Release: 1.7.26 + +2004-01-01 Mikio Hirabayashi + + - The inverted API was enhanced. + - The utility API was enhanced. + - Release: 1.7.25 + +2003-12-26 Mikio Hirabayashi + + - C++ export was supported in header files. + - The utility API was enhanced. + - Release: 1.7.24 + +2003-12-21 Mikio Hirabayashi + + - 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 + + - Repairing utility was added to the extended API. + - The utility API was enhanced. + - Release: 1.7.22 + +2003-12-14 Mikio Hirabayashi + + - 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 + + - Character encoding converter was added. + - Release: 1.7.20 + +2003-12-10 Mikio Hirabayashi + + - 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 + + - The utility API was enhanced. + - The GDBM-compatible API was enhanced. + - Release: 1.7.18 + +2003-12-05 Mikio Hirabayashi + + - The utility API was enhanced. + - A CGI script for full-text search was enhanced. + - Release: 1.7.17 + +2003-12-01 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - A utility class of Java API was enhanced. + - Building configuration for CGI scripts was modified. + - Release: 1.7.10 + +2003-11-20 Mikio Hirabayashi + + - 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 + + - Optional features of compressing with ZLIB were added. + - Release: 1.7.8 + +2003-11-05 Mikio Hirabayashi + + - The extended advanced API, Vista was added. + - Release: 1.7.7 + +2003-11-03 Mikio Hirabayashi + + - C API compilation using Visual C++ was supported. + - Odeum and its commands were enhanced. + - Release: 1.7.6 + +2003-10-25 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - The license was changed to LGPL. + - An indexing command for the inverted API was added. + - Release: 1.7.2 + +2003-10-14 Mikio Hirabayashi + + - 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 + + - The inverted API, Odeum was added. + - Comments in source codes increased. + - Release: 1.7.0 + +2003-10-05 Mikio Hirabayashi + + - Executable commands were modified for RISC OS. + - Release: 1.6.22 + +2003-10-05 Mikio Hirabayashi + + - Executable commands were modified for RISC OS. + - Release: 1.6.21 + +2003-09-30 Mikio Hirabayashi + + - Naming file in RISC OS style was supported. + - Hash functions were enhanced. + - Release: 1.6.20 + +2003-09-26 Mikio Hirabayashi + + - The utility API was enhanced. + - Test commands were added. + - Release: 1.6.19 + +2003-09-22 Mikio Hirabayashi + + - Makefile for RISC OS was added. + - C++ API was enhanced. + - Release: 1.6.18 + +2003-09-14 Mikio Hirabayashi + + - 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 + + - The CGI script for administration was added. + - C++ API was enhanced. + - Release: 1.6.16 + +2003-08-28 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.6.15 + +2003-08-16 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.6.14 + +2003-08-11 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.6.13 + +2003-08-05 Mikio Hirabayashi + + - A bug in the utility API was fixed. + - Test commands were enhanced. + - Release: 1.6.12 + +2003-07-31 Mikio Hirabayashi + + - C and Java APIs support compilation using MinGW. + - Release: 1.6.11 + +2003-07-27 Mikio Hirabayashi + + - Perl and Ruby APIs support transaction. + - Release: 1.6.10 + +2003-07-26 Mikio Hirabayashi + + - Speed of transaction was improved. + - Release: 1.6.9 + +2003-07-24 Mikio Hirabayashi + + - C++ and Java APIs of B+ tree support transaction. + - Release: 1.6.8 + +2003-07-21 Mikio Hirabayashi + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - 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 + + - The advanced API was enhanced to improve its speed. + - Makefiles were enhanced for HP-UX. + - Release: 1.6.1 + +2003-06-28 Mikio Hirabayashi + + - The advanced API for B+ tree database is added. + - Release: 1.6.0 + +2003-06-23 Mikio Hirabayashi + + - Makefiles were enhanced for Mac OS X. + - Release: 1.5.13 + +2003-06-18 Mikio Hirabayashi + + - Makefile was enhanced for Solaris and Mac OS X. + - Release: 1.5.12 + +2003-06-06 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.5.11 + +2003-05-31 Mikio Hirabayashi + + - The utility API was enhanced. + - Release: 1.5.10 + +2003-05-29 Mikio Hirabayashi + + - The utility API was changed. + - Release: 1.5.9 + +2003-05-25 Mikio Hirabayashi + + - Alignment mechanism was simplified. + - Release: 1.5.8 + +2003-05-17 Mikio Hirabayashi + + - Makefile was updated + - Release: 1.5.7 + +2003-05-16 Mikio Hirabayashi + + - Test tools were enhanced. + - Release: 1.5.6 + +2003-05-14 Mikio Hirabayashi + + - Makefile using libtool was added. + - Release: 1.5.5 + +2003-05-12 Mikio Hirabayashi + + - API for Ruby was enhanced. + - Release: 1.5.4 + +2003-05-08 Mikio Hirabayashi + + - API for Ruby was added. + - Release: 1.5.3 + +2003-05-04 Mikio Hirabayashi + + - API for Perl was enhanced. + - Release: 1.5.2 + +2003-04-30 Mikio Hirabayashi + + - API for Perl was enhanced. + - Release: 1.5.1 + +2003-04-29 Mikio Hirabayashi + + - API for Perl was added. + - Release: 1.5.0 + +2003-04-25 Mikio Hirabayashi + + - The package was reorganized to be a GNU package. + - Release: 1.4.11 + +2003-04-21 Mikio Hirabayashi + + - The utility API was added. + - Release: 1.4.10 + +2003-04-15 Mikio Hirabayashi + + - Endian converters were added. + - Release: 1.4.9 + +2003-04-12 Mikio Hirabayashi + + - The GDBM-compatible API was enhanced. + - Release: 1.4.8 + +2003-04-10 Mikio Hirabayashi + + - Some bugs were fixed and C++ API was enhanced. + - Release: 1.4.7 + +2003-04-09 Mikio Hirabayashi + + - Coalescence of dead regions and reuse of them were featured. + - Release: 1.4.6 + +2003-04-06 Mikio Hirabayashi + + - The GDBM-compatible API and the C++ API were enhanced. + - Release: 1.4.5 + +2003-04-04 Mikio Hirabayashi + + - The API for C++ and the API for Java were enhanced. + - Release: 1.4.4 + +2003-04-02 Mikio Hirabayashi + + - Documents for C++ was enhanced. + - Release: 1.4.3 + +2003-04-01 Mikio Hirabayashi + + - The API for Java was enhanced. + - Release: 1.4.2 + +2003-03-23 Mikio Hirabayashi + + - Makefiles were modified. + - Release: 1.4.1 + +2003-03-23 Mikio Hirabayashi + + - APIs for C++ and Java was enhanced. + - Release: 1.4.0 + +2003-03-15 Mikio Hirabayashi + + - The API for C++ was added. + - Release: 1.3.2 + +2003-03-11 Mikio Hirabayashi + + - The API for Java was supported on Solaris and Windows. + - Release: 1.3.1 + +2003-03-04 Mikio Hirabayashi + + - The API for Java was added. + - Release: 1.3.0 + +2003-02-23 Mikio Hirabayashi + + - The GDBM-compatible API was added. + - Release: 1.2.0 + +2003-02-21 Mikio Hirabayashi + + - Compatibility with NDBM was improved. + - Release: 1.1.3 + +2003-02-18 Mikio Hirabayashi + + - Optimizing suppoted on Windows. DLL is supported. + - Release: 1.1.2 + +2003-02-15 Mikio Hirabayashi + + - Windows is now supported. + - The compatible API was enhanced. + - Release: 1.1.1 + +2003-02-11 Mikio Hirabayashi + + - The NDBM-compatible API was added. + - Release: 1.1.0 + +2003-02-08 Mikio Hirabayashi + + - 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..c6d69b93 --- /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 @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 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, "&", 5); break; + case '<': cbdatumcat(datum, "<", 4); break; + case '>': cbdatumcat(datum, ">", 4); break; + case '"': cbdatumcat(datum, """, 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, " 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, "&", 5); + } else if(str[i] == '<'){ + CB_DATUMCAT(datum, "<", 4); + } else if(str[i] == '>'){ + CB_DATUMCAT(datum, ">", 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, "&", 5); + break; + case '<': + CB_DATUMCAT(datum, "<", 4); + break; + case '>': + CB_DATUMCAT(datum, ">", 4); + break; + case '"': + CB_DATUMCAT(datum, """, 6); + break; + case '\'': + CB_DATUMCAT(datum, "'", 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, "&")){ + CB_DATUMCAT(datum, "&", 1); + str += 5; + } else if(cbstrfwmatch(str, "<")){ + CB_DATUMCAT(datum, "<", 1); + str += 4; + } else if(cbstrfwmatch(str, ">")){ + CB_DATUMCAT(datum, ">", 1); + str += 4; + } else if(cbstrfwmatch(str, """)){ + CB_DATUMCAT(datum, "\"", 1); + str += 6; + } else if(cbstrfwmatch(str, "'")){ + 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 +#include + + +#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 `&', `<', `>', 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 *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 +#include +#include +#include +#include + +#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 +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("\n"); + rows = cbcsvrows(buf); + for(i = 0; i < cblistnum(rows); i++){ + if(html) printf(""); + row = cblistval(rows, i, NULL); + cells = cbcsvcells(row); + for(j = 0; j < cblistnum(cells); j++){ + cell = cblistval(cells, j, NULL); + if(html){ + printf(""); + } 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(""); + putchar('\n'); + } + cblistclose(rows); + if(html) printf("
    "); + for(k = 0; cell[k] != '\0'; k++){ + if(cell[k] == '\r' || cell[k] == '\n'){ + printf("
    "); + if(cell[k] == '\r' && cell[k] == '\n') k++; + } else { + switch(cell[k]){ + case '&': printf("&"); break; + case '<': printf("<"); break; + case '>': printf(">"); break; + default: putchar(cell[k]); break; + } + } + } + printf("
    \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, "")){ + 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 +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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("\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("\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("\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("\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\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\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\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, "<Nuts&Milk> is "very" surfin'!"); + 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.in b/qdbm/configure.in new file mode 100644 index 00000000..95349aec --- /dev/null +++ b/qdbm/configure.in @@ -0,0 +1,313 @@ +# 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 + +echo "Compiler version:" `$CC -v` + +if $CC -v 2>&1 | egrep -iq '(^| )clang '; then + MYOPTS="-O1 -fno-omit-frame-pointer" +elif uname | egrep -iq 'SunOS' || \ + uname | egrep -iq 'BSD' || \ + gcc --version | egrep -iq '^2\.(8|9)' +then + MYOPTS="-O1 -fno-omit-frame-pointer -fno-force-addr" +else + MYOPTS="-O1 -fno-omit-frame-pointer -fforce-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 +#include +#include +#include +#include +#include + +#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 +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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("\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("\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("\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 +#include +#include +#include +#include +#include + +#undef TRUE +#define TRUE 1 /* boolean true */ +#undef FALSE +#define FALSE 0 /* boolean false */ + + +/* for RISC OS */ +#if defined(__riscos__) || defined(__riscos) +#include +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 +#include +#include + + +#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 +#include + + +#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 +#include +#include +#include +#include + +#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 +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 +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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("\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("\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("\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 +#include +#include +#include +#include + +#undef TRUE +#define TRUE 1 /* boolean true */ +#undef FALSE +#define FALSE 0 /* boolean false */ + + +/* for RISC OS */ +#if defined(__riscos__) || defined(__riscos) +#include +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 +#include +#include +#include +#include + + +#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 +#include +#include +#include +#include +#include +#include + +#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 +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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. + + + Copyright (C) + + 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. + + , 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 Binary files /dev/null and b/qdbm/misc/benchmark.pdf differ diff --git a/qdbm/misc/icon16.png b/qdbm/misc/icon16.png new file mode 100644 index 00000000..6d6ed13d Binary files /dev/null and b/qdbm/misc/icon16.png differ diff --git a/qdbm/misc/icon20.png b/qdbm/misc/icon20.png new file mode 100644 index 00000000..edad7849 Binary files /dev/null and b/qdbm/misc/icon20.png 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 @@ + + + + + + + + + + + + + + +QDBM: Quick Database Manager + + + + + + +

    QDBM: Quick Database Manager

    + +
    Copyright (C) 2000-2007 Mikio Hirabayashi
    +
    Last Update: Thu, 26 Oct 2006 15:00:20 +0900
    + + + + +
    + +

    Overview

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +
    + +

    Documents

    + +

    The following are documents of QDBM. They are contained also in the source package.

    + + + +
    + +

    Packages

    + +

    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.

    + + + + + + + +
    + +

    Applications

    + +

    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.

    + +
      +
    • Hyper Estraier : Full-text Search System for Communities written by Mikio Hirabayashi
    • +
    • Estraier : Personal Full-text Search System written by Mikio Hirabayashi
    • +
    • Diqt : Multilingual Dictionary Searcher written by Mikio Hirabayashi
    • +
    • RBBS : WWW-based Bulletin Board System written by Mikio Hirabayashi
    • +
    • Harvest : Distributed Search System written by Kang-Jin Lee et al
    • +
    • Bogofilter : Bayesian Spam Mail Filter written by Eric S. Raymond et al
    • +
    • MQS : Minimalist Queue Services written by Alexandre Dulaunoy
    • +
    • Ruby/Odeum : Ruby Binding to the Inverted API written by Zed A. Shaw
    • +
    • Surfulater : Utility to Save and Organize Web Pages written by Soft As it Gets Pty Ltd
    • +
    • Mutt-NG : Text Based Mail Client written by Andreas Krennmair et al
    • +
    • SMFS : Smart Sendmail Filters written by Eugene Kurmanin
    • +
    • Dixit : Romanian Dictionary Searcher written by Octavian Procopiuc
    • +
    + +
    + +

    Brothers

    + +

    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.

    + +
      +
    • NDBM : New DBM written by BSD
    • +
    • SDBM : Substitute DBM written by Ozan S. Yigit
    • +
    • GDBM : GNU Database Manager written by Philip A. Nelson et al
    • +
    • TDB : Trivial Database written by Andrew Tridgell et al
    • +
    • TinyCDB : Constant Database written by Michael Tokarev
    • +
    • Berkeley DB : Berkeley DB written by Sleepycat Software
    • +
    + + + +
    + +

    Information

    + +

    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 `http://lists.sourceforge.net/lists/listinfo/qdbm-users'.

    + +

    The project page on SourceForge.net is `http://sourceforge.net/projects/qdbm/'.

    + +

    Update of this project is announced on Freshmeat.net at `http://freshmeat.net/projects/qdbm/'.

    + +
    + + + + + + 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 @@ + + + + + + + + + + + + + + +データベースライブラリ QDBM + + + + + + +

    QDBM: Quick Database Manager

    + +
    Copyright (C) 2000-2007 Mikio Hirabayashi
    +
    Last Update: Thu, 26 Oct 2006 15:00:20 +0900
    + + + + +
    + +

    概要

    + +

    QDBMはデータベースを扱うルーチン群のライブラリです。データベースといっても単純なもので、キーと値のペアからなるレコード群を格納したデータファイルです。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができます。テーブルやデータ型の概念はありません。レコードはハッシュ表またはB+木で編成されます。

    + +

    ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできません。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索したりすることができます。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできます。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものです。QDBMはDBMのより良い代替として利用することができます。

    + +

    B+木のデータベースでは、キーが重複する複数のレコードを格納することができます。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除したりすることができます。レコードはユーザが指示した比較関数に基づいて整列されて格納されます。カーソルを用いて各レコードを昇順または降順で参照することができます。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になります。また、B+木のデータベースではトランザクションが利用できます。

    + +

    QDBMはCで記述され、C、C++、Java、PerlおよびRubyのAPIとして提供されます。QDBMはPOSIX準拠のAPIを備えるプラットフォームで利用できます。QDBMはGNU Lesser General Public Licenseに基づくフリーソフトウェアです。

    + +
    + +

    文書

    + +

    以下の文書をお読みください。ソースパッケージにも同じものが含まれています。

    + + + +
    + +

    ダウンロード

    + +

    以下のパッケージをダウンロードしてください。Linux用バイナリパッケージは、CとC++とJavaのAPIと、CGIスクリプトを納めています。Windows用バイナリパッケージは、CとJavaのAPIと、CGIスクリプトを納めています。

    + + + + + + + +
    + +

    アプリケーション

    + +

    QDBMのアプリケーションには以下のものがあります。もしあなたのプロジェクトがQDBMを使っているなら、連絡をいただければこのリストに追加いたします。

    + +
      +
    • Hyper Estraier : 平林幹雄による共同体的全文検索システム
    • +
    • Estraier : 平林幹雄による個人用全文検索システム
    • +
    • Diqt : 平林幹雄による多言語辞書検索システム
    • +
    • RBBS : 平林幹雄によるWeb掲示板システム
    • +
    • Harvest : Kang-Jin Leeらによる分散情報検索システム
    • +
    • Bogofilter : Eric S. Raymondらによるベイズ式スパムメールフィルタ
    • +
    • MQS : Alexandre Dulaunoyによるキュー管理サービス
    • +
    • Ruby/Odeum : Zed A. Shawによる転置APIのRuby用インターフェイス
    • +
    • Surfulater : Soft As it Gets Pty社によるWebページスクラップブック
    • +
    • Mutt-NG : Andreas Krennmairらによるテキスト型メールクライアント
    • +
    • SMFS : Eugene KurmaninによるSendmail用フィルタシステム
    • +
    • Dixit : Octavian Procopiucによるルーマニア語辞書検索システム
    • +
    + +
    + +

    兄弟

    + +

    UNIX標準のDBMに追従したライブラリはたくさんあります。それぞれ特徴がありますので、あなたの製品にとって都合がいいものを選んでください。NDBMは古いので、もはや使う理由はないでしょう。SDBMはApache Projectで管理され、GDBMはGNU Projectで管理されています。それらは最も人気があり、また数多くテストされているものでしょう。TDBはSamba Teamで管理されており、複数同時の書き込みが可能です。TinyCDBは随時の更新はできませんが、最も高速に動作します。Berkeley DBは非常に多機能で、ACID準拠で、多くの商用製品で使われています。最後に、QDBMは性能と機能と移植性と利便性のバランスがとれた製品です。

    + +
      +
    • NDBM : BSDによるNew DBM
    • +
    • SDBM : Ozan S. YigitによるSubstitute DBM
    • +
    • GDBM : Philip A. NelsonらによるGNU Database Manager
    • +
    • TDB : Andrew TridgellらによるTrivial Database
    • +
    • TinyCDB : Michael TokarevによるConstant Database
    • +
    • Berkeley DB : Sleepycat Software社によるBerkeley DB
    • +
    + + + +
    + +

    その他の情報

    + +

    QDBMは平林幹雄が作成しました。作者と連絡をとるには、`mikio@users.sourceforge.net' 宛に電子メールを送ってください。ただし、質問やバグレポートなど、他のユーザと共有できる話題はメーリングリストに送ってください。メーリングリストの参加方法については、`http://lists.sourceforge.net/lists/listinfo/qdbm-users' を参照してください。

    + +

    SourceForge.netにおけるプロジェクトページは `http://sourceforge.net/projects/qdbm/' にあります。Freshmeatにおける更新情報は `http://freshmeat.net/projects/qdbm/' から参照できます。

    + +
    + + + + + + diff --git a/qdbm/misc/logo.png b/qdbm/misc/logo.png new file mode 100644 index 00000000..d5cf9771 Binary files /dev/null and b/qdbm/misc/logo.png 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 @@ + + + + + + + + + + +My Private Memo for QDBM + + + + +

    QDBMのための私的メモ

    + +
    + +

    cygwin+mingwでのビルド環境の設定方法

    + +
      +
    • Cygwinのセットアップ時に、mingwのgccとmingwのzlibも選択する。
    • +
    • mingwのlibiconvはmingwのサイトから手に入れる。そして、*.h は /usr/include/mingw の下に、*.dll.a は /lib/mingw の下に、*.dll は /binの下に移動させる。
    • +
    • QDBMのビルド環境の設定は ./configure --enable-zlib --enable-iconv で行い、パッケージの作成は make win32pkg で行う。
    • +
    + +
    + + + + + + 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 @@ + + + + + + + + + + + + + + +Tutorial of QDBM Version 1 (Japanese) + + + + + +

    QDBMのチュートリアル

    + +
    Copyright (C) 2000-2007 Mikio Hirabayashi
    +
    Last Update: Thu, 26 Oct 2006 15:00:20 +0900
    + + +
    + +

    目次

    + +
      +
    1. イントロダクション
    2. +
    3. Depot: 基本API
    4. +
    5. Curia: 拡張API
    6. +
    7. Relic: NDBM互換API
    8. +
    9. Hovel: GDBM互換API
    10. +
    11. Cabin: ユーティリティAPI
    12. +
    13. Villa: 上級API
    14. +
    15. Odeum: 転置API
    16. +
    + +
    + +

    イントロダクション

    + +

    QDBMは、シンプルながら便利なデータベースライブラリです。データベースというとSQLやリレーショナルデータベースを思い浮かべる人が多いと思いますが、QDBMはそんな高機能なものではありません。「キー」と「値」の組からなるレコードをファイルに保存したり、保存しておいたレコードの中から特定のキーを持つものを取り出す機能を提供するだけです。そのような機能をここでは「ハッシュデータベース」と呼ぶことにします。ハッシュデータベースの特長は、使い方が簡単で、パフォーマンスが高いことです。

    + +

    QDBMはC言語のライブラリです(他の言語のAPIもありますが)。QDBMにはハッシュデータベースの機能だけでなく、私(QDBMの作者)がプログラミングをする時によく使う機能が詰め込まれています。Cで書いたプログラムは高速に動作するのが利点ですが、C++、Java、Perl、Rubyといった比較的高級な言語では標準的にサポートされるデータ構造やアルゴリズムを自分で実装しなければなりません。そういった作業は面倒ですし、バグを生みやすいものです。そこで、QDBMの登場です。QDBMを再利用すれば、CでのプログラミングがPerlを使っているかのように手軽になります。しかも、UNIXでもWindowsでもMac OS Xでも利用できるので、移植性のあるプログラムが書きやすくなります。

    + +

    シンプルといいながら、QDBMの機能はなかなか豊富です。データベースとしては、ハッシュ表とB+木が利用できます。メモリ上で扱うユーティリティとしては、リストやマップなどがあります。MIMEやCSVやXMLの解析もできます。しまいには全文検索までできたりするので驚きです。

    + +

    このチュートリアルではQDBMの使い方を簡単に説明するとともに、基本仕様書の補足を述べます。QDBMの詳細については基本仕様書を御覧ください。なお、ここではハッシュ表やB+木といったデータ構造の説明はしませんので、不慣れな方は適当な本やWebサイトで調べておいてください。

    + +
    + +

    Depot: 基本API

    + +

    データベースアプリケーションの典型的な例として、「社員番号を入力すると、その内線番号がわかる」というプログラムを考えてみましょう。社員番号をキーとして、それに対応する値である内線番号を検索するということです。

    + +

    まずはQDBMを使わないでやってみます。社員番号と内線番号の対応表は、CSVテキストでファイルに保存することにします。書式の例を以下に示します。

    + +
    00001,8-902-1234
    +00002,7-938-834
    +00008,4-214-491
    +
    + +

    レコードを加える関数 `putphonenumber' と、レコードを検索する関数 `getphonenumber' を実装します。

    + +
    #include <stdio.h>
    +#include <stdlib.h>
    +#include <string.h>
    +
    +#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;
    +}
    +
    + +

    `fscanf' を使っている時点でかなり貧弱ですが、きちんと書こうとすると非常に長くなるので妥協しました(ちなみに、255文字を越える行があったら暴走します)。とにかく、この程度の処理でやたら長いコードを書かねばならないのでは悲しくなります。さらに重大な欠点は、検索の処理が遅いということです。ファイルの最初から最後まで(平均的には半分まで)読まなければならないからです。既存のレコードを修正する時にもかなり面倒なことをしなければなりません。

    + +

    QDBMを使えばもっとエレガントなコードが書けます。上記と同じ機能の関数を実装してみます。

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +
    +#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;
    +}
    +
    + +

    もはやファイル形式はCSVファイルではなく、区切り文字が何であるか気にする必要はありません。プログラマはファイル形式がどうであるかなど考えなくてもよいのです。メモリの確保などもQDBMの内部でやってくれるので、バッファのサイズを気にする必要はありません(解放は必要です)。処理速度を気にする必要もありません。データベースがどんなに大きくても、レコードの追加や削除や検索が一瞬でできます。このように、プログラマをデータ管理の苦悩から解放するのがQDBMの役割です。

    + +

    上記の例ではQDBMの基本APIであるDepotを利用しています。まず、`DEPOT' という型が登場しています。これは標準ライブラリの `FILE' と同様に、操作対象のファイルの情報を格納している構造体の型です。この型へのポインタをハンドルとして各種の関数に渡すことになります。関数としては、`dpopen'、`dpclose'、`dpput' および `dpget' が登場しています。この四つの使い方を覚えればQDBMの半分は理解したようなものです。

    + +
    DEPOT *dpopen(const char *name, int omode, int bnum);
    + +

    `dpopen' はその名の通り、Depotのデータベースを開く関数です。その結果として `DEPOT' 型の構造体へのポインタが返されます。第1引数にはデータを格納するファイル名を指定します。これは相対パスで指定しても絶対パスで指定しても構いません。第2引数は接続モードを指定します。読み込みと書き込みの両方をするなら `DP_OWRITER' を指定します。ただし、データベースファイルが存在しない場合に新規作成するならば同時に `DP_OCREAT' をビットORとして加える必要があります。この二つを指定すると、`fopen' の `a+' モードとほぼ同じ意味になります。他に `DP_OTRUNC' というフラグがあるのですが、それはファイルを切り詰めることを指示します。それも加えて三つを指定すると `fopen' の `w+' モードとほぼ同じ意味になります。読み込みだけをする場合、`DP_OREADER' を指定します。これは `fopen' の `r' モードとほぼ同じ意味です。第3引数はハッシュ表のバケット数を指定します。とりあえずデフォルト値を意味する `-1' を指定しておけばいいでしょう。

    + +

    複数のプロセスが同じファイルを読み書きする場合、「レースコンディション」という問題が起こります。同時にファイルに書き込むと、内容が混ざって変になってしまう可能性があるのです。QDBMではそれに対処するために「ファイルロック」をかけます。あるプロセスがデータベースを書き込みモードで開いている場合は、他のプロセスがデータベースを開こうとしてもブロックされるのです。処理が失敗するわけではなく、既にデータベースを開いているプロセスがデータベースを閉じるまで待ってくれるのです。なお、読み込みモード同士であればレースコンディションは起こらないので同時にアクセスすることができます。

    + +
    int dpclose(DEPOT *depot);
    + +

    `dpclose' はデータベースを閉じる関数です。第1引数には `dpopen' で開いたハンドルを渡します。開いたデータベースは必ず閉じてください。そうしないとデータベースが壊れます(読み込みモードの場合は壊れませんが、メモリリークになります)。

    + +

    ところで、QDBMを使わない例では、書き込みの際に呼び出す `fprintf' の戻り値をチェックしていません。`fprintf' が失敗した場合は `fclose' もエラーを返すと規定されているからです。同様に、QDBMでもデータベースに一度でも致命的なエラーが起きた場合は `dpclose' がエラーを返すので、エラーチェックを簡略化することができるのです。

    + +
    int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    + +

    `dpput' はデータベースにレコードを追加する関数です。第1引数には `dpopen' で開いたハンドルを渡します。第2引数には、書き込むレコードのキーの内容を保持する領域へのポインタを指定します。第3引数にはその領域のサイズを指定します。それが負数の場合は、第2引数を文字列として扱って、`strlen' の値をサイズとして判定します(文字列とバイナリの両方を簡単に扱えるようにするためです)。第4引数と第5引数は、レコードの値に関して同様にポインタとサイズを指定します。第6引数は書き込みのモードです。データベース内には同じキーを持つ複数のレコードを格納することができないので、既存のレコードのキーと同一のキーを持つレコードを格納しようとした際にどうするかを指示する必要があります。`DP_DOVER' とした場合は、既存のレコードを新しいレコードで上書きします。`DP_DKEEP' とした場合は、既存のレコードを優先し、エラーが返されます(`DP_EKEEP' というエラーコードが外部変数 `dpecode' に設定されます)。

    + +

    `dpput' の書き込みモードには、`DP_DCAT' による「連結モード」もあります。これを利用することはあまりないかもしれませんが、他のDBMにはない特徴なので説明します。連結モードは、レコードの値として配列を入れる際に便利なのです。例えば、[10,11,12] という三つの数値を要素に持つ配列を格納していて、それを [10,11,12,13] にしたい場合を考えてみます。他のDBMでは、まずそのレコードを検索して、[10,11,12] を獲得してから、それを [10,11,12,13] に加工して値を生成し、元のレコードに上書きで書き込むといったことをしなければなりません。QDBMの場合は、連結モードで [13] を既存のレコードに書き込むだけで同じことができます。配列の要素数が大きい場合にはこの違いはパフォーマンスに大きな影響を及ぼします。

    + +
    char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp);
    + +

    `dpget' はデータベースを検索してレコードを取り出す関数です。第1引数は他と一緒ですね。第2引数と第3引数は `dpput' と同様に検索キーのポインタとサイズを指定します。第4引数と第5引数はとりあえず `0' と '-1' にするものだと思っていただいて結構です。一応説明すると、取り出す領域の開始オフセットと最大サイズを指定します。`-1' は無制限という意味です。例えばレコードの値が `abcdef' の場合に開始オフセットを `1'、サイズを `3' にした場合、`bcd' が取り出されます。戻り値は取り出した値の内容を記録した領域へのポインタです。その領域は `malloc' でヒープに確保されているので、アプリケーションの責任で `free' に渡して解放する必要があります。戻り値の領域は終端にゼロコードが付加されていることが保証されているので、文字列として利用できます。ただし、バイナリを扱う場合には明示的にサイズが知りたいでしょうから、その為に第6引数にサイズを受け取る変数へのポインタを指定することができます(このサイズには終端のゼロコードは勘定されません)。該当のレコードがない場合には `NULL' が返されます(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。

    + +

    Depotの関数で他によく使うものとして、`dpout'、`dpiterinit'、`dpiternext' があります。これらについても説明しておきます。

    + +
    int dpout(DEPOT *depot, const char *kbuf, int ksiz);
    + +

    `dpout' はデータベースからレコードを削除する関数です。三つの引数の扱いは `dpget' のものと同じです。該当のレコードがない場合はエラーを返します(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。

    + +

    `dpiterinit' と `dpiternext' はデータベースの中のレコードを一つ一つ見ていく場合に使います。先程の内線番号データベースの全レコードを表示する関数の例を以下に示します。

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    全てのレコードを横断的に見ていくことを「トラバーサルアクセス」と呼ぶことにします。トラバーサルアクセスの際には、「イテレータ」を利用します。イテレータは集合に含まれる個々の要素を処理する際に、その要素を一つずつ取り出す機能です。トラバーサルアクセスをはじめる前にはイテレータを初期化します。そして、イテレータが「打ち止め」の合図を返すまで繰り返して呼び出します。

    + +
    int dpiterinit(DEPOT *depot);
    + +

    `dpiterinit' はイテレータを初期化する関数です。特に説明は必要ありませんね。

    + +
    char *dpiternext(DEPOT *depot, int *sp);
    + +

    `dpiternext' はイテレータから次のレコードのキーを取り出す関数です。第1引数は他と同じです。例によって戻り値はゼロコードが付加された領域へのポインタです。領域は `malloc' で確保されているので、アプリケーションの責任で `free' してください。明示的にサイズを知りたい場合は第2引数にそれを受け取る変数へのポインタを指定します。もう取り出すレコードがなくなったら `NULL' が返されます(`DP_ENOITEM' というエラーコードが外部変数 `dpecode' に設定されます)。

    + +

    トラバーサルアクセスで各レコードを辿る順番については規定されていませんので、レコードの順序に依存したプログラミングをしてはいけません(そのような場合はB+木データベースを使いましょう)。また、イテレータを繰り返している途中でレコードを上書きした場合、既に読んだはずのレコードがまた取り出される可能性があります。途中でレコードを削除することに関しては問題ありません。

    + +

    ここまででDepotの基本的な使い方は説明し終えました。QDBMのそれ以外のAPIもDepotに似たような使い方をしますので、基本仕様書のサンプルコードを見れば理解してもらえると思います。

    + +
    + +

    Curia: 拡張API

    + +

    Curiaは、Depotとほとんど同じ機能とインタフェースを備えますが、ファイルでなくディレクトリとしてデータベースを扱うAPIです。やたらと大量のデータを扱わなければならない場合にはCuriaがお薦めです。データをディレクトリの中の複数のファイルに分散して格納するので、Depotよりも大きな(2GB以上の)データベースを扱うことができます。Depotではハンドルに `DEPOT' へのポインタを使いましたが、Curiaでは `CURIA' へのポインタを使います。そして、Depotでは `dp' で始まっていた関数名が、Curiaでは `cr' で始まります。名前が違うだけで、使い方は全く一緒です。ただし、データベースを開く `cropen' という関数は、データベースの分割数を指定する引数が増えています。

    + +
    CURIA *cropen(const char *name, int omode, int bnum, int dnum);
    + +

    第1、第2、第3引数は `dpopen' のものと全く一緒です。第4引数は、データベースの分割数を指定します。ディレクトリの中に、指定した数だけのファイルが作られます。なお、第3引数で指定した値はその各々のデータベースファイルが持つバケット数になります。戻り値はCuriaのデータベースハンドルとなります。

    + +

    Curiaには「ラージオブジェクト」を扱う機能もあります。ラージオブジェクトとは、各レコードをファイルとして独立させて保存する仕組みです。ラージオブジェクトにすると通常のレコードより処理速度が落ちますが、データベースのサイズを無限に(ディスクが許す限り)大きくすることができます。なお、ラージオブジェクトのトラバーサルアクセスはサポートされません。

    + +
    + +

    Relic: NDBM互換API

    + +

    Relicは、NDBMのアプリケーションをすべからくQDBMに乗り換えさせるという野望の下に作られたAPIです。パフォーマンスはオリジナルのNDBMの数倍は出ます。NDBMのアプリケーションはあまり見ないですが、Perl等のスクリプト言語がそのインタフェースを備えています。つまりRelicによって各種のスクリプト言語でQDBMが使えることが保証されるわけです。

    + +

    あなたのアプリケーションのソースコード中で `ndbm.h' をインクルードしている部分を `relic.h' に書き換え、リンク対象を `ndbm' から `qdbm' に換えて再コンパイルしください。それだけであなたのアプリケーションはQDBMに乗り換えることができます。なお、新たにアプリケーションを書く際には、RelicでなくDepotを利用することをお薦めします。

    + +
    + +

    Hovel: GDBM互換API

    + +

    Hovelは、GDBMのアプリケーションをすべからくQDBMに乗り換えさせるという野望の下に作られたAPIです。パフォーマンスはオリジナルのGDBMの数倍は出ます。GDBMのアプリケーションは市場に多く見られますが、それらをお使いの場合は、ぜひともQDBMに移植してあげてください。パフォーマンスが目に見えてに改善されることうけあいです。

    + +

    あなたのアプリケーションのソースコード中で `gdbm.h' をインクルードしている部分を `hovel.h' に書き換え、リンク対象を `gdbm' から `qdbm' に換えて再コンパイルしください。それだけであなたのアプリケーションはQDBMに乗り換えることができます。なお、新たにアプリケーションを書く際には、HovelでなくDepotを利用することをお薦めします。

    + +

    通常、Hovelが生成するデータベースファイルはDepotのものと全く同じものです。しかし、ちょっと細工するとそれをCuriaによるデータベースディレクトリに変更することができます。データベースハンドルを取得する関数 `gdbm_open' を、`gdbm_open2' に書き換えればよいのです。単一のファイルにデータベースを格納している場合は、ファイルサイズが2GBまでという制限にひっかかってしまいますが、`gdbm_open2' を使えばそれを乗り越えることができます。`gdbm_open' を呼び出しているところ以外は全く変更する必要がないというのが嬉しいところです。

    + +
    GDBM_FILE gdbm_open2(char *name, int read_write, int mode, int bnum, int dnum, int align);
    + +

    第1引数は生成するファイルかディレクトリの名前です。第2引数と第3引数は `gdbm_open' の第3引数と第4引数として渡すものと一緒です。第4引数はバケットの要素数です。第5引数はデータベースファイルの分割数です。第6引数は各レコードのアラインメントです。戻り値は `gdbm_open' と同じくデータベースハンドルです。

    + +
    + +

    Cabin: ユーティリティAPI

    + +

    Cabinは、データの操作を簡単に行うためのユーティリティを集めたAPIです。密かにQDBMのAPIの中で最も充実しています。特にリストとマップに関連する関数が重宝します。他にも、ファイルやディレクトリを読んだり、文字列を分割したり、CSVやXMLを解析したり、各種の符号化と復号もできます。ここではリストとマップとXMLについて詳しく説明します。

    + +

    リストとは、順序を持った集合のことです。Cabinが扱うリストには任意の文字列やバイナリを要素として加えることができます。リストの先頭に対して要素の追加と削除ができるとともに、リストの末尾に対して要素の追加と削除をすることもできます(つまりデクです)。また、配列を使って実装されているので、任意の順番の要素の値を高速に参照することができます。以下の例では、`first'、`second'、`third' という文字列を順に末尾から追加した上で、先頭から末尾まで要素の内容を表示しています。

    + +
    #include <cabin.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +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 < cblistnum(list); i++){
    +    printf("%s\n", cblistval(list, i, NULL));
    +  }
    +  /* リストを閉じる */
    +  cblistclose(list);
    +}
    +
    + +

    `CBLIST' 型へのポインタをリストのハンドルとして用います。実際のハンドルは `cblistopen' を呼び出して獲得します。ハンドルを閉じてメモリを解放するには、`cblistclose' を呼び出します。`cblistpush' は末尾に要素を追加します。`cblistnum' はリストの要素数を返します。`cblistval' はリスト内の特定の番号(ゼロからはじまる)の要素を返します。リスト操作の関数はその他にもいくつかあります。

    + +

    マップとは、キーと値からなるレコードの集合です。Cabinが扱うマップには任意の文字列やバイナリをキーや値に持つレコードを格納することができます。キーが完全に一致するレコードを検索して値を取り出すことができます(実装はハッシュ表です)。マップ内のレコードを先頭から一つずつ取り出すこともできます。なお、各要素は格納した順番で並んでいることが保証されています。以下の例では、キー `one' と値 `first'、キー `two' と値 `second'、キー `three' と値 `third' のレコードを順に格納した上で、その各々を検索して表示しています。

    + +
    #include <cabin.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +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);
    +}
    +
    + +

    `CBMAP' 型へのポインタをマップのハンドルとして用います。実際のハンドルは `cbmapopen' を呼び出して獲得します。ハンドルを閉じてメモリを解放するには、`cbmapclose' を呼び出します。`cbmapput' でレコードを追加します。`cbmapget' でレコードを検索します。マップ操作の関数はその他にもいくつかあります。

    + +

    XMLを簡単に処理するために、簡易的なパーザが用意されています。このパーザは妥当性検証をせず、書式の検査も厳密でないのが特徴です。したがって、一般的なHTMLやSGMLの解析にも用いることができます。単純な構造のXML文書を処理する際には、DOMやSAXといったAPIを使うよりも便利です。以下の例では、XML文書の中から `id' 属性の値で要素を指定して、そのテキストを取り出して表示します。

    + +
    #include <cabin.h>
    +#include <stdlib.h>
    +#include <string.h>
    +#include <stdio.h>
    +
    +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 < cblistnum(elems); i++){
    +    /* 要素を取り出す */
    +    elem = cblistval(elems, i, NULL);
    +    /* タグでない場合は読み飛ばす */
    +    if(elem[0] != '<' || elem[1] == '?' || elem[1] == '!' || elem[1] == '/') continue;
    +    /* 属性のマップを取得する */
    +    attrs = cbxmlattrs(elem);
    +    /* ID要素の値を取り出し、一致を検査する */
    +    attr = cbmapget(attrs, "id", -1, NULL);
    +    if(attr && !strcmp(attr, id)){
    +      /* 次の要素を取り出す */
    +      elem = cblistval(elems, i + 1, NULL);
    +      if(elem){
    +        /* 実体参照を復元して表示する */
    +        orig = cbxmlunescape(elem);
    +        printf("%s\n", orig);
    +        free(orig);
    +      }
    +    }
    +    /* 属性マップを閉じる */
    +    cbmapclose(attrs);
    +  }
    +  /* 要素リストを閉じる */
    +  cblistclose(elems);
    +}
    +
    + +

    処理対象のXML文書のテキストを `cbxmlbreak' で分解します。例えば `<body><p id="nuts">NUTS&amp;MILK</p></body>' を分解すると、`<body>'、`<p id="nuts">'、`NUTS&amp;MILK'、`</p>'、`</body>' が得られます。そして、各要素を巡回します。1文字目が '<' であればタグか各種の宣言であり、かつ2文字目が '?'、`!'、`/' のいずれでもなければ開始タグまたは空タグであると判断できます。タグに対して `cbxmlattrs' を呼ぶことで属性のマップが得られます。このマップは属性名をキーにして値を取り出すことができます。属性値やテキストセクションの文字列は文書内に出現したままの形式になっています。実体参照を含んだ文字列を復元するには `cbxmlunescape' を用います。

    + +

    GTK+に付属するGLibやApacheに付属するAPRなどの便利なライブラリが世の中にはありますので、単体でCabinを利用する価値はあまりありません。正直言って、GLibやAPRの方が高機能で、ユーザ数も多く、参考になる情報も多いです。とはいえ、Cabinの方が手軽に使えるので私は好きです。

    + +
    + +

    Villa: 上級API

    + +

    Villaは、B+木のデータベースを扱うAPIです。B+木データベースにはユーザが指定した順序でレコードが並べられて格納されます。DepotやCuriaはキーの完全一致による検索しかできませんが、Villaを用いると範囲を指定してレコードを検索することができます。また、同じキーを持つ複数のレコードを格納することもできます。例えば、キーを文字列の辞書順(ABC順とかアイウエオ順と同じほぼ意味です)で並べるように指定した場合は、文字列の前方一致検索ができるのです。

    + +

    とはいえ、基本的な使い方はDepotと一緒です。Depotの説明で挙げた関数をVillaを使って実装しなおしてみます。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +
    +#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;
    +}
    +
    + +

    `VILLA' へのポインタは例によってデータベースハンドルです。`vlopen' でそのハンドルを獲得します。その第3引数の `VL_CMPLEX' は辞書順の比較を行う関数です。開いたハンドルは `vlclose' で閉じます。`vlput' はレコードを格納する関数で、`vlget' はレコードを検索する関数です。

    + +

    上記の例ではハッシュデータベースと全く同じ使い方をしましたが、順番に基づいてレコードにアクセスする機能がB+木データベースの特徴です。辞書順を例にとって説明します。キーがそれぞれ `one'、`two'、`three'、`four' というレコードを格納したとすれば、それは `four'、`one'、`three'、`two' という順番で並べられて保存されます。検索にはハッシュデータベースと同様に完全一致条件も使えます。さらに、「カーソル」という機能を使って範囲を指定した検索ができます。カーソルはレコードの位置を指し示します。例えば、`one' の場所にカーソルを飛ばすといった指定ができます。`one' がない場合は、`one' の直後の `three' の位置にカーソルが飛ぶことになります(`one' が複数あった場合は、その最初のレコードに飛びます)。カーソルは、現在位置から前に進めたり後ろに戻したりすることもできます。そして、カーソルの位置のレコードの内容を読み出せば、範囲を指定した検索ができるというわけです。以下の例は、`one' から `three' までのレコードのキーと値を表示する関数です。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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, &ksiz);
    +    /* 候補が範囲外であれば抜ける */
    +    if(!kbuf || VL_CMPLEX(kbuf, ksiz, BOTTOMWORD, sizeof(BOTTOMWORD) - 1) > 0){
    +      free(kbuf);
    +      break;
    +    }
    +    /* レコードの値を取り出して表示する */
    +    vbuf = vlcurval(villa, NULL);
    +    if(kbuf && vbuf) printf("%s: %s\n", kbuf, vbuf);
    +    /* キーと値の領域を解放する */
    +    free(vbuf);
    +    free(kbuf);
    +    /* カーソルを次に進める */
    +  } while(vlcurnext(villa));
    +  /* データベースを閉じる */
    +  vlclose(villa);
    +}
    +
    + +

    `vlcurjump' でカーソルを候補の先頭に飛ばしています。`VL_JFORWARD' はこれからカーソルを前に進めていく場合に指定します(候補の末尾に飛ばしてから後ろに戻して行く場合は `VL_JBACKWARD' を指定します)。do-whileループの条件部で `vlcurnext' を呼んでいますが、これがカーソルを前に進めています(データベースの最後まで来たら偽を返すのでループから抜けます)。`vlcurkey' と `vlcurval' はそれぞれカーソルのキーと値を取り出します。`VL_CMPLEX' を明示的に呼んでいる場所がありますが、ここで候補の末尾に来たかどうか判定しています。比較関数は、二つのキーのポインタとサイズを渡して、前者が大きい(つまり後ろに位置すべき)なら正の値、前者が小さい(つまり前に位置すべき)なら負の値、両者が同じならゼロを返すと規定されています。したがって、正の値が返された場合、今取り出したキーは候補の末尾よりも後ろだということになります。

    + +

    辞書順以外の比較関数も使えるところがVillaのミソです。int型の数値を比較する `VL_CMPINT' や、10進数の文字列を比較する `VL_CMPDEC' といった関数が最初から用意されています。さらに、あなたが自分で定義した関数も比較関数として使うことができます。使い方が面倒だという欠点を除けば、B+木データベースはハッシュデータベースよりも多くのシーンで活用できると思います。ファイルがハッシュデータベースよりも小さかったり、トランザクションが使えるといった特徴も、人によっては嬉しいかもしれません。

    + +

    Villaは、キュー(FIFO)を永続化する目的でも利用できます。`VL_CMPINT' を比較関数にしてデータベースを開きます。各レコードのキーはint型とし、値には任意のオブジェクトを入れることにします。キューに要素を追加するには、末尾のキーの数値(なければゼロ)をインクリメントしてキーを生成して格納します。キューから要素を取り出すには、先頭のレコードを取り出してから削除すればよいのです。そのような機能を持つラッパ関数を、`qopen'、`qclose'、`qappend'、`qconsume' といった名前で作っておくと小粋ですね。

    + +
    + +

    Odeum: 転置API

    + +

    Odeumは、全文検索用の転置インデックスを扱うAPIです。テキストファイル(またはテキストを含むHTMLやMS-Wordの文書など)は単語の集合とみなせますが、ある単語がどのファイルに含まれるかという情報をデータベースにしたものを転置インデックスと呼びます。本の巻末にある索引は、ある単語がどのページに含まれるかという情報を持っていますが、それと似たようなものです。私がQDBMを開発する契機となったのは、とある全文検索システムの開発で使っていたGDBMのパフォーマンスに限界を感じたことです。その経緯から、QDBM(特にCuria)には転置インデックスの実現に都合のよい特徴がいくつか備わっています。

    + +

    転置インデックスの核となるのは、ある単語をキーとし、その単語を含むファイルのIDの配列を値とするレコードからなるデータベースです。単語は完全一致で検索できればよいので、データ構造にはハッシュ表を採用しています。転置インデックスのイメージを例示します。ファイル `penguin.txt' には「flightless marine birds」というテキストが格納されていて、ファイル `ostrich.txt' には、「flightless birds in africa」というテキストが格納されているとします。各ファイルには、読み込んだ順番でIDをつけることにします。これらを対象として転置インデックスを作成すると、以下のようになります。IDとファイル名の対応づけは別の「文書データベース」に保存しておきます。

    + + + + + + + +
    flightless1,2
    marine1
    birds1,2
    in2
    africa2
    + +

    その次に読み込んだ文書に「birds」という単語が含まれていた場合には、`birds' の値を [1,2,3] に変更することになります。このように、既存のレコードの値の末尾にデータを追加するという操作が頻繁に発生します。この処理を効率的に行うために、DepotやCuriaの書き込み操作には「連結モード」があるのです。

    + +

    検索時には、検索語をキーにして値の配列を取り出し、個々のIDに対応するファイル名を文書データベースから取り出して提示することになります。ただし該当の文書が多い場合にその全てを提示してもユーザは困惑するので、先頭の何件かに絞って取り出します。DepotやCuriaの読み込み操作ではレコードの値から特定の部分のみを取り出すことができるので、この処理を効率良く行うことができます。実際は、ファイルにおける単語のスコア(重要度)も配列要素の一部として格納しておいて、それを基準にソートしておくことによって、先頭の要素がユーザにとって意味のあるものにしています。

    + +

    ファイルには名前以外にも、タイトルや作者名や更新日時といった属性をつけたい場合があります。それらの情報は文書データベースに保存します。Odeumで扱うファイル(以後は文書と呼びます)は、ファイル名の代わりにURIをつけるものとしています。URI以外の属性はユーザがラベルをつけて任意のものを格納できます。

    + +

    Odeumは、文書のテキストや属性を元のデータから抽出する機能については提供しません。それらはドメインに強く依存するので、共通化することが難しいからです。したがって、アプリケーションがそれを実装する必要があります。Odeumのサンプルアプリケーションは、ローカルのファイルシステムにあるプレーンテキストとHTMLからテキストと属性を抽出できます。あなたのアプリケーションでは、PDFやMS-Wordの文書に対応するのもよいでしょう。Webから文書を取得してもよいでしょう。同じ理由から、Odeumはテキストから単語を抽出する機能についても提供しません。英語と日本語では全く異なる手法でテキストの解析をしなければなりませんが、そういったこともアプリケーションに任されます。Odeumのサンプルアプリケーションでは、単に空白で語を区切るという手法をとっています。あなたのアプリケーションでは、日本語の形態素解析を行うのもよいでしょう。英単語のステミング処理を行ってもよいでしょう。

    + +

    多くの言語では、同じ単語に対して異体や活用が存在します。例えば「使う/使っ(た)」「go/went」「child/children」などです。そこで、Odeumは各単語を「正規形」および「出現形」の組として扱います。テキストから出現形の単語を切り出したり、それらの正規形を生成する処理はアプリケーションに任されます。転置インデックスでのレコードのキーには正規形が使われます。「child」で検索すれば、「children」を含む文書も該当させられるということです。検索語に対しても正規化の処理を行えば、「children」で検索して「child」を該当にできます。なお、出現形もデータベースに記録されますが、それは検索結果として文書の要約を提示するなどの用途で利用されます(不要な場合は出現形を全て空文字列にして記憶領域を節約できます)。

    + +

    全文検索システムの善し悪しを評価する際には、スコアリング(ランキング)が重要な要素になります。検索結果が多い場合に、その中から絞り込んでユーザに提示する文書をどうやって選択するかということです。いくら検索速度が速くても、満足のいく検索結果が提示されずに何度も検索したり、無駄な文書を閲覧しなければならないのでは、結局は時間がかかることになってしまいます。Odeumでは、文書を登録する際に、そこに含まれる各単語について以下の式でスコアを算出して、文書IDとともに転置インデックスに格納しています。

    + +
    スコア = (該当語の出現数 * 10000) / log(文書内の総語数) ^ 3
    + +

    ある単語がたくさん出現するということは、その文書でその単語のことについて詳しく説明されている可能性が高いと判断できます。ただし、大きな文書は小さい文書よりも単語の出現数が多くなりますので、その調整をする必要があります。そこで、該当語の出現数を、総語数の自然対数の三乗で割っているのです。なお、テキストの先頭10%以内に出て来る単語は「トピックセンテンス」を構成するとみなして、初出時に限り10000でなく15000を加算しています。

    + +

    全文検索は通常、複数の検索語を使って行われます。その際には、各検索語のスコアを調整しないと、特定の検索語のスコアの影響が強すぎるという事態が発生します。例えば「the beatles」で検索した場合、「the」のスコアが「beatles」のスコアを圧倒して、「beatles」について知りたいのに、関係ない文書ばかりが提示されることになってしまいます。Odeumは転置インデックスの内容を提示するだけで、それ以後の処理はアプリケーションに任されます。Odeumに付属するサンプルアプリケーションでは以下の式で調整を行っています。

    + +
    調整済みスコア = 登録されたスコア / log(その語を含む文書数) ^ 2
    + +

    ありふれた単語はスコアを下げ、特徴的な単語はスコアを維持すべきです。そこで、登録されたスコアを、その語を含む文書数の自然対数の二乗で割っているのです(TF-IDF法を強化したものです)。各検索語の調整済みスコアを足したものを文書のスコアとし、その降順で検索結果を提示します。他にもドメインに依存した様々なスコアリング手法があると思いますが、アプリケーションがそれを実装できるのが嬉しいところです。

    + +

    Odeumのサンプルアプリケーションでは、関連文書検索(類似文書検索と呼んだ方が適切かもしれません)の機能も実装しています。ある文書(種文書と呼びます)に関連した文書の一覧を提示するものです。単語の出現傾向が似通った文書は互いに関連しているという考え方(ベクトル空間モデル)に基づいています。文書をデータベースに登録する際には、各文書に含まれる全単語に対して調整済みスコアを計算し、その上位32語(キーワードと呼びます)の情報を文書と対応づけて登録しておきます。検索時には、種文書のキーワードとスコアを取り出し、それを32次元のベクトルとして表現します。関連度を判定する対象の各文書からもキーワードとスコアを取り出し、種文書のベクトル空間に対応したベクトルを生成します。そうしてできた二つのベクトルのなす角が小さいものは関連度が高いと判定します。実際には、なす角の余弦(0から1の範囲で、完全一致する場合は1になる)が大きいものから提示されることになります。なお、登録された全ての文書を対象として類似度の判定を行うとあまりに時間がかかるので、キーワードでOR検索を行った結果の上位の文書のみを関連度算出の対象としています。

    + +

    前置きはここまでにして、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' という名前のデータベースに登録する例を示します。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +
    +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;
    +}
    +
    + +

    `ODEUM' へのポインタは例によってデータベースハンドルです。`odopen' でそのハンドルを獲得します。開いたハンドルは `odclose' で閉じます。`ODDOC' は文書ハンドルです。各文書の内容は文書ハンドルによって表現されます。`oddocopen' はハンドルを開く関数です。その第1引数で文書のURIを指定します。文書ハンドルは不要になったら `oddocclose' で解放します。テキストの各単語を文書に登録するには、`oddocaddword' を用います。その第2引数は単語の正規形で、第3引数は出現形です。文書を表現したら、それを `odput' でデータベースに登録します。第3引数は、文書データベースに登録する語数を指定します。`-1' にすると全部の語が登録されます。第4引数は、同じURIの既存の文書がある場合に、それを上書きするか否か指定します。

    + +

    `odput' の第3引数の指定が少し難しいので補足します。ある文書が適切に検索されるために、転置インデックスにおいて、その文書に含まれる全ての単語のレコードの値にその文書のIDが無条件で追加されます。ところで、多くの全文検索システムでは、検索結果の画面で該当の文書の要約を表示します。そのためには、文書データベースの中に、各文書と関連づけて含まれる単語を順番に記録しておく必要があります。検索語の周辺の文を切り出して表示する場合を考えると、検索語が文書中のどこに現れるかは予想できないので、全ての単語を文書データベースに記録しておかなければなりません。あるいは、冒頭の何語かだけ表示する場合には、その語数分の語を登録しておけばよいことになります。そういった決定をアプリケーションに任せるために、文書データベースに登録する語数を指定できるようになっているのです。

    + +

    以下の例では、`grateful' という単語を含む文書を検索して、そのURIとスコアを表示します。まず転置インデックスを検索して結果の配列を受け取ります。その配列の各要素は文書のIDとスコアの組です。文書の内容を取得するには、文書IDを使って文書データベースに問い合わせます。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +void docsearchtest(void){
    +  ODEUM *odeum;
    +  ODPAIR *pairs;
    +  ODDOC *doc;
    +  int i, pnum;
    +  /* データベースを読み込みモードで開く */
    +  if(!(odeum = odopen("index", OD_OREADER))) return;
    +  /* 転置インデックスを検索する */
    +  pairs = odsearch(odeum, "grateful", -1, &pnum);
    +  if(pairs && pnum > 0){
    +    /* 該当の各文書を処理する */
    +    for(i = 0; i < 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);
    +}
    +
    + +

    転置インデックスを検索するのが `odsearch' という関数です。第2引数には正規形の検索語を指定します。第3引数には、検索する文書の最大数を指定しますが、全部取り出す場合は `-1' とします。結果の配列はスコアの降順でソートされていることが規定されています。第4引数には、結果の配列の要素数を格納する変数のポインタを指定します。次に、配列の各要素を処理していきます。`odgetbyid' は文書IDを用いて文書の内容を問い合わせる関数です。転置インデックスの中には既に削除されたり上書きされてIDが変更された文書の情報も入っているので(最適化すれば不要な情報はなくなりますが)、`odgetbyid' は失敗する可能性があります。そういう時は単に無視して次のループに進んでください。文書が取得できたら、あとはそれを表示します。`oddocuri' は文書のURIを返す関数です。他にも文書の情報を取得する関数がいくつか用意されています。

    + +

    Odeumでは、複数の検索語を用いて、AND条件(検索語の全てを含む)やOR条件(検索語のいずれかを含む)やNOTAND条件(検索語の前者を含むが後者は含まない)といった集合演算を処理するための関数が用意されているほか、全文検索システムの実装に便利なユーティリティ関数が多数提供されます。全文検索システムを実装する際には性能と精度のバランスを考えなければなりませんが、OdeumのAPIはアプリケーションがそれを任意に決められるように設計されています。大規模なインデックスを扱う際には、まず精度を落した検索を行って、その結果がユーザの要求を満たさなければ精度を高めたパラメータで再検索する手法が有効でしょう。

    + +
    + + + + + + 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 + + +#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 + +#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 + + +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 + +#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 + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#elif defined(_SYS_MINGW_) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#elif defined(_SYS_CYGWIN_) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include + + +#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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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, "= 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, ">", "(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, "&", -1, "&", -1, TRUE); + cbmapput(pairs, "<", -1, "<", -1, TRUE); + cbmapput(pairs, ">", -1, ">", -1, TRUE); + cbmapput(pairs, """, -1, "\"", -1, TRUE); + cbmapput(pairs, "'", -1, "'", -1, TRUE); + cbmapput(pairs, " ", -1, " ", -1, TRUE); + cbmapput(pairs, "©", -1, "(C)", -1, TRUE); + cbmapput(pairs, "®", -1, "(R)", -1, TRUE); + cbmapput(pairs, "™", -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, "‚", -1, ",", -1, TRUE); + cbmapput(pairs, "„", -1, ",,", -1, TRUE); + cbmapput(pairs, "…", -1, "...", -1, TRUE); + cbmapput(pairs, "‹", -1, "<", -1, TRUE); + cbmapput(pairs, "‘", -1, "'", -1, TRUE); + cbmapput(pairs, "’", -1, "'", -1, TRUE); + cbmapput(pairs, "“", -1, "\"", -1, TRUE); + cbmapput(pairs, "”", -1, "\"", -1, TRUE); + cbmapput(pairs, "–", -1, "-", -1, TRUE); + cbmapput(pairs, "—", -1, "-", -1, TRUE); + cbmapput(pairs, "˜", -1, "~", -1, TRUE); + cbmapput(pairs, "™", -1, "(TM)", -1, TRUE); + cbmapput(pairs, "›", -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 +#include +#include +#include +#include +#include + +#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 +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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("\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("\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 +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(MYPTHREAD) +#include +#include +#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 +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("\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("\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 +#include +#include +#include +#include + + +#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 +#include +#include +#include +#include +#include +#include +#include + +#undef TRUE +#define TRUE 1 /* boolean true */ +#undef FALSE +#define FALSE 0 /* boolean false */ + + +/* for RISC OS */ +#if defined(__riscos__) || defined(__riscos) +#include +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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 @@ + + + + + + + + + + + + + + + +Specifications of QDBM Version 1 (Japanese) + + + + + +

    QDBMバージョン1基本仕様書

    + +
    Copyright (C) 2000-2007 Mikio Hirabayashi
    +
    Last Update: Thu, 26 Oct 2006 15:00:20 +0900
    + + +
    + +

    目次

    + +
      +
    1. 概要
    2. +
    3. 特徴
    4. +
    5. インストール
    6. +
    7. Depot: 基本API
    8. +
    9. Depot用コマンド
    10. +
    11. Curia: 拡張API
    12. +
    13. Curia用コマンド
    14. +
    15. Relic: NDBM互換API
    16. +
    17. Relic用コマンド
    18. +
    19. Hovel: GDBM互換API
    20. +
    21. Hovel用コマンド
    22. +
    23. Cabin: ユーティリティAPI
    24. +
    25. Cabin用コマンド
    26. +
    27. Villa: 上級API
    28. +
    29. Villa用コマンド
    30. +
    31. Odeum: 転置API
    32. +
    33. Odeum用コマンド
    34. +
    35. ファイルフォーマット
    36. +
    37. 移植方法
    38. +
    39. バグ
    40. +
    41. よく聞かれる質問
    42. +
    43. ライセンス
    44. +
    + +
    + +

    概要

    + +

    QDBMはデータベースを扱うルーチン群のライブラリである。データベースといっても単純なものであり、キーと値のペアからなるレコード群を格納したデータファイルである。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができる。テーブルやデータ型の概念はない。レコードはハッシュ表またはB+木で編成される。

    + +

    ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできない。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索したりすることができる。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできる。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものである。QDBMはDBMのより良い代替として利用することができる。

    + +

    B+木のデータベースでは、キーが重複する複数のレコードを格納することができる。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除したりすることができる。レコードはユーザが指示した比較関数に基づいて整列されて格納される。カーソルを用いて各レコードを昇順または降順で参照することができる。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になる。また、B+木のデータベースではトランザクションが利用できる。

    + +

    QDBMはCで記述され、C、C++、Java、PerlおよびRubyのAPIとして提供される。QDBMはPOSIX準拠のAPIを備えるプラットフォームで利用できる。QDBMはGNU Lesser General Public Licenseに基づくフリーソフトウェアである。

    + +
    + +

    特徴

    + +

    効率的なハッシュデータベースの実装

    + +

    QDBMはGDBMを参考に次の三点を目標として開発された。処理がより高速であること、データベースファイルがより小さいこと、APIがより単純であること。これらの目標は達成されている。また、GDBMと同様に、伝統的なDBMが抱える三つの制限事項を回避している。すなわち、プロセス内で複数のデータベースを扱うことができ、キーと値のサイズに制限がなく、データベースファイルがスパースではない。

    + +

    QDBMはレコードの探索にハッシュアルゴリズムを用いる。バケット配列に十分な要素数があれば、レコードの探索にかかる時間計算量は O(1) である。すなわち、レコードの探索に必要な時間はデータベースの規模に関わらず一定である。追加や削除に関しても同様である。ハッシュ値の衝突はセパレートチェーン法で管理する。チェーンのデータ構造は二分探索木である。バケット配列の要素数が著しく少ない場合でも、探索等の時間計算量は O(log n) に抑えられる。

    + +

    QDBMはバケット配列を全てRAM上に保持することによって、処理の高速化を図る。バケット配列がRAM上にあれば、ほぼ1パスのファイル操作でレコードに該当するファイル上の領域を参照することができる。ファイルに記録されたバケット配列は `read' コールでRAM上に読み込むのではなく、`mmap' コールでRAMに直接マッピングされる。したがって、データベースに接続する際の準備時間が極めて短く、また、複数のプロセスでメモリマップを共有することができる。

    + +

    バケット配列の要素数が格納するレコード数の半分ほどであれば、データの性質によって多少前後するが、ハッシュ値の衝突率は56.7%ほどである(等倍だと36.8%、2倍だと21.3%、4倍だと11.5%、8倍だと6.0%ほど)。そのような場合、平均2パス以下のファイル操作でレコードを探索することができる。これを性能指標とするならば、例えば100万個のレコードを格納するためには50万要素のバケット配列が求められる。バケット配列の各要素は4バイトである。すなわち、2MバイトのRAMが利用できれば100万レコードのデータベースが構築できる。

    + +

    QDBMにはデータベースに接続するモードとして、「リーダ」と「ライタ」の二種類がある。リーダは読み込み専用であり、ライタは読み書き両用である。データベースにはファイルロックによってプロセス間での排他制御が行われる。ライタが接続している間は、他のプロセスはリーダとしてもライタとしても接続できない。リーダが接続している間は、他のプロセスのリーダは接続できるが、ライタは接続できない。この機構によって、マルチタスク環境での同時接続に伴うデータの整合性が保証される。

    + +

    伝統的なDBMにはレコードの追加操作に関して「挿入」モードと「置換」モードがある。前者では、キーが既存のレコードと重複する際に既存の値を残す。後者では、キーが既存のレコードと重複した際に新しい値に置き換える。QDBMはその2つに加えて「連結」モードがある。既存の値の末尾に指定された値を連結して格納する操作である。レコードの値を配列として扱う場合、要素を追加するには連結モードが役に立つ。また、DBMではレコードの値を取り出す際にはその全ての領域を処理対象にするしか方法がないが、QDBMでは値の領域の一部のみを選択して取り出すことができる。レコードの値を配列として扱う場合にはこの機能も役に立つ。

    + +

    一般的に、データベースの更新処理を続けるとファイル内の利用可能領域の断片化が起き、ファイルのサイズが肥大化してしまう。QDBMは隣接する不要領域を連結して再利用し、またデータベースの最適化機能を備えることによってこの問題に対処する。既存のレコードの値をより大きなサイズの値に上書きする場合、そのレコードの領域をファイル中の別の位置に移動させる必要がある。この処理の時間計算量はレコードのサイズに依存するので、値を拡張していく場合には効率が悪い。しかし、QDBMはアラインメントによってこの問題に対処する。増分がパディングに収まれば領域を移動させる必要はない。

    + +

    多くのファイルシステムでは、2GBを越えるサイズのファイルを扱うことができない。この問題に対処するために、QDBMは複数のデータベースファイルを含むディレクトリからなるデータベースを扱う機能を提供する。レコードをどのファイルに格納するかはキーに別のハッシュ関数を適用することによって決められる。この機能によって、理論的にはレコードの合計サイズが1TBまでのデータベースを構築することができる。また、データベースファイルを複数のディスクに振り分けることができるため、RAID-0(ストライピング)に見られるような更新操作の高速化が期待できる。NFS等を利用すれば複数のファイルサーバにデータベースを分散させることもできる。

    + +

    便利なB+木データベースの実装

    + +

    B+木データベースはハッシュデータベースより遅いが、ユーザが定義した順序に基づいて各レコードを参照できることが特長である。B+木は複数のレコードを整列させた状態で論理的なページにまとめて管理する。各ページに対してはB木すなわち多進平衡木によって階層化された疎インデックスが維持される。したがって、各レコードの探索等にかかる時間計算量は O(log n) である。各レコードを順番に参照するためにカーソルが提供される。カーソルの場所はキーを指定して飛ばすことができ、また現在の場所から次のレコードに進めたり前のレコードに戻したりすることができる。各ページは双方向リンクリストで編成されるので、カーソルを前後に移動させる時間計算量は O(1) である。

    + +

    B+木データベースは上述のハッシュデータベースを基盤として実装される。B+木の各ページはハッシュデータベースのレコードとして記録されるので、ハッシュデータベースの記憶管理の効率性を継承している。B+木では各レコードのヘッダが小さく、各ページのアラインメントはページサイズに応じて調整されるので、ほとんどの場合、ハッシュデータベースに較べてデータベースファイルのサイズが半減する。B+木を更新する際には多くのページを操作する必要があるが、QDBMはページをキャッシュすることによってファイル操作を減らして処理を効率化する。ほとんどの場合、疎インデックス全体がメモリ上にキャッシュされるので、各レコードを参照するのに必要なファイル操作は平均1パス以下である。

    + +

    B+木データベースはトランザクション機構を提供する。トランザクションを開始してから終了するまでの一連の操作を一括してデータベースにコミットしたり、一連の更新操作を破棄してデータベースの状態をトランザクションの開始前の状態にロールバックしたりすることができる。トランザクションの間にアプリケーションのプロセスがクラッシュしてもデータベースファイルは破壊されない。

    + +

    可逆データ圧縮ライブラリであるZLIBかLZOかBZIP2を有効化してQDBMをビルドすると、B+木の各ページの内容は圧縮されてファイルに書き込まれる。同一ページ内の各レコードは似たようなパターンを持つため、Lempel-Zivなどのアルゴリズムを適用すると高い圧縮効率が期待できる。テキストデータを扱う場合、データベースのサイズが元の25%程度になる。データベースの規模が大きくディスクI/Oがボトルネックとなる場合は、圧縮機能を有効化すると処理速度が大幅に改善される。

    + +

    単純だが多様なインタフェース群

    + +

    QDBMのAPIは非常に単純である。ANSI Cで定義された `FILE' ポインタを用いた通常のファイル入出力と同じようにデータベースファイルに対する入出力を行うことができる。QDBMの基本APIでは、データベースの実体は単一のファイルに記録される。拡張APIでは、データベースの実体は単一のディレクトリに含まれる複数のファイルに記録される。二つのAPIは互いに酷似しているので、アプリケーションを一方から他方に移植することはたやすい。

    + +

    NDBMおよびGDBMに互換するAPIも提供される。NDBMやGDBMのアプリケーションは市場に数多く存在するが、それらをQDBMに移植するのはたやすい。ほとんどの場合、ヘッダファイルの取り込み(#include)を書き換えてコンパイルしなおせばよい。ただし、オリジナルのNDBMやGDBMで作成したデータベースファイルをQDBMで扱うことはできない。

    + +

    メモリ上でレコードを簡単に扱うために、ユーティリティAPIが提供される。メモリ確保関数とソート関数と拡張可能なデータと配列リストとハッシュマップ等の実装である。それらを用いると、C言語でもPerlやRuby等のスクリプト言語のような手軽さでレコードを扱うことができる。

    + +

    B+木データベースは上級APIを介して利用する。上級APIは基本APIとユーティリティAPIを利用して実装される。上級APIも基本APIや拡張APIに類似した書式を持つので、使い方を覚えるのは容易である。

    + +

    全文検索システムで利用される転置インデックスを扱うために、転置APIが提供される。文書群の転置インデックスを容易に扱うことができれば、アプリケーションはテキスト処理や自然言語処理に注力できる。このAPIは文字コードや言語に依存しないので、ユーザの多様な要求に応える全文検索システムを実装することが可能となる。

    + +

    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スクリプトも提供される。

    + +

    幅広い移植性

    + +

    QDBMはANSI C(C89)の記法に従い、ANSI CまたはPOSIXで定義されたAPIのみを用いて実装される。したがって、ほとんどのUNIXおよびその互換をうたうOSで動作させることができる。C言語のAPIに関しては、少なくとも以下のプラットフォームで動作確認されている。

    + +
      +
    • Linux (2.2, 2.4, 2.6) (IA32, IA64, AMD64, PA-RISC, Alpha, PowerPC, M68000, ARM)
    • +
    • FreeBSD (4.9, 5.0, 5.1, 5.2, 5.3) (IA32, IA64, SPARC, Alpha)
    • +
    • NetBSD (1.6) (IA32)
    • +
    • OpenBSD (3.4) (IA32)
    • +
    • SunOS (5.6, 5.7, 5.8, 5.9, 5.10) (IA32, SPARC)
    • +
    • HP-UX (11.11, 11.23) (IA64, PA-RISC)
    • +
    • AIX (5.2) (POWER)
    • +
    • Windows (2000, XP) (IA32, IA64, AMD64) (Cygwin, MinGW, Visual C++)
    • +
    • Mac OS X (10.2, 10.3, 10.4) (IA32, PowerPC)
    • +
    • Tru64 (5.1) (Alpha)
    • +
    • RISC OS (5.03) (ARM)
    • +
    + +

    QDBMが作成したデータベースファイルは処理系のバイトオーダに依存するが、その対策として、バイトオーダに依存しない形式のデータをダンプするユーティリティが提供される。

    + +
    + +

    インストール

    + +

    準備

    + +

    ソースパッケージを用いてQDBMをインストールするには、GCCのバージョン2.8以降と `make' が必要である。

    + +

    QDBMの配布用アーカイブファイルを展開したら、生成されたディレクトリに入ってインストール作業を行う。

    + +

    普通の手順

    + +

    LinuxとBSDとSunOSでは以下の手順に従う。

    + +

    ビルド環境を設定する。

    + +
    ./configure
    +
    + +

    プログラムをビルドする。

    + +
    make
    +
    + +

    プログラムの自己診断テストを行う。

    + +
    make check
    +
    + +

    プログラムをインストールする。作業は `root' ユーザで行う。

    + +
    make install
    +
    + +

    GNU Libtoolを使う場合

    + +

    上記の方法でうまくいかない場合、以下の手順に従う。この手順には、GNU Libtoolのバージョン1.5以降が必要である。

    + +

    ビルド環境を設定する。

    + +
    ./configure
    +
    + +

    プログラムをビルドする。

    + +
    make -f LTmakefile
    +
    + +

    プログラムの自己診断テストを行う。

    + +
    make -f LTmakefile check
    +
    + +

    プログラムをインストールする。作業は `root' ユーザで行う。

    + +
    make -f LTmakefile install
    +
    + +

    結果

    + +

    一連の作業が終ると、以下のファイルがインストールされる。その他にも、マニュアルが `/usr/local/man/man1' と `/usr/local/man/man3' に、それ以外の文書が `/usr/local/share/qdbm' に、`pkg-config' 用の設定ファイルが `/usr/local/lib/pkgconfig' にインストールされる。

    + +
    /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
    +
    + +

    `libqdbm.so' と動的にリンクしたプログラムを実行する際には、ライブラリの検索パスに `/usr/local/lib' を含めるべきである。環境変数 `LD_LIBRARY_PATH' でライブラリの検索パスを設定することができる。

    + +

    QDBMをアンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。

    + +
    make uninstall
    +
    + +

    QDBMの古いバージョンがインストールされている場合、それをアンインストールしてからインストール作業を行うべきである。

    + +

    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' を参照すること。

    + +

    RPM等のバイナリパッケージを用いてインストールを行う場合は、それぞれのパッケージマネージャのマニュアルを参照すること。例えば、RPMを用いる場合、以下のようなコマンドを `root' ユーザで実行する。

    + +
    rpm -ivh qdbm-1.x.x-x.i386.rpm
    +
    + +

    Windowsの場合

    + +

    Windows(Cygwin)にインストールする場合、以下の手順に従う。

    + +

    ビルド環境を設定する。

    + +
    ./configure
    +
    + +

    プログラムをビルドする。

    + +
    make win
    +
    + +

    プログラムの自己診断テストを行う。

    + +
    make check-win
    +
    + +

    プログラムをインストールする。なお、アンインストールする場合は `make uninstall-win' とする。

    + +
    make install-win
    +
    + +

    Windowsでは、静的ライブラリ `libqdbm.a' に加えてインポートライブラリ `libqdbm.dll.a' が生成され、動的ライブラリ `libqdbm.so' 等の代わりにダイナミックリンクライブラリ `qdbm.dll' が生成される。`qdbm.dll' は `/usr/local/bin' にインストールされる。

    + +

    Cygwin環境でMinGWを用いてビルドするには、`make win' の代わりに `make mingw' を用いる。CygwinのUNIXエミュレーション層を用いる場合、生成されるプログラムは `cygwin1.dll' に依存したものになる(GNU GPLの影響を受ける)。MinGWによってWin32のネイティブDLLとリンクさせればこの問題を回避できる。

    + +

    Visual C++を用いてビルドするには、`VCmakefile' を編集してヘッダとライブラリの検索パスを設定した上で、`nmake /f VCMakefile' とすればよい。生成された `qdbm.dll' とリンクするアプリケーションは、コンパイラの `/MD' または `/MDd' オプションを用いて `msvcrt.dll' とリンクさせる必要がある。詳細設定に関しては `VCmakefile' を参照のこと。

    + +

    Mac OS Xの場合

    + +

    Mac OS X(Darwin)にインストールする場合、以下の手順に従う。

    + +

    ビルド環境を設定する。

    + +
    ./configure
    +
    + +

    プログラムをビルドする。

    + +
    make mac
    +
    + +

    プログラムの自己診断テストを行う。

    + +
    make check-mac
    +
    + +

    プログラムをインストールする。なお、アンインストールする場合は `make uninstall-mac' とする。

    + +
    make install-mac
    +
    + +

    Mac OS Xでは、`libqdbm.so' 等の代わりに `libqdbm.dylib' 等が生成される。ライブラリの検索パスの指定は環境変数 `DYLD_LIBRARY_PATH' で行うことができる。

    + +

    HP-UXの場合

    + +

    HP-UXにインストールする場合、以下の手順に従う。

    + +

    ビルド環境を設定する。

    + +
    ./configure
    +
    + +

    プログラムをビルドする。

    + +
    make hpux
    +
    + +

    プログラムの自己診断テストを行う。

    + +
    make check-hpux
    +
    + +

    プログラムをインストールする。なお、アンインストールする場合は `make uninstall-hpux' とする。

    + +
    make install-hpux
    +
    + +

    HP-UXでは、`libqdbm.so' 等の代わりに `libqdbm.sl' が生成される。ライブラリの検索パスの指定は環境変数 `SHLIB_PATH' で行うことができる。

    + +

    RISC OSの場合

    + +

    RISC OSにインストールする場合、以下の手順に従う。

    + +

    プログラムをビルドする。デフォルトではコンパイラに `cc' を用いるようになっているが、`gcc' を用いたければ `CC=gcc' という引数を加えればよい。

    + +
    make -f RISCmakefile
    +
    + +

    一連の作業が終ると、`libqdbm' というライブラリファイルと `dpmgr' 等のコマンドが生成される。それらのインストール方法は定義されていないので、手動で任意の場所にコピーしてインストールすること。`depot.h' 等のヘッダファイルも同様に手動でインストールすること。

    + +

    詳細設定

    + +

    `./configure' を実行する際に以下のオプション引数を指定することで、ビルド方法の詳細な設定を行うことができる。

    + +
      +
    • --enable-debug : デバッグ用にビルドする。デバッグシンボルを有効化し、最適化を行わず、静的にリンクする。
    • +
    • --enable-devel : 開発用にビルドする。デバッグシンボルを有効化し、最適化を行い、動的にリンクする。
    • +
    • --enable-stable : 安定版のリリース用にビルドする。保守的な最適化を行い、動的にリンクする。
    • +
    • --enable-pthread : POSIXスレッドを用い、グローバル変数をスレッド固有データとして扱う。
    • +
    • --disable-lock : ファイルロッキングが実装されていない環境用にビルドする。
    • +
    • --disable-mmap : メモリマッピングが実装されていない環境用にビルドする。
    • +
    • --enable-zlib : ZLIBによるB+木と転置インデックスのレコード圧縮を機能させる。
    • +
    • --enable-lzo : LZOによるB+木と転置インデックスのレコード圧縮を機能させる。
    • +
    • --enable-bzip : BZIP2によるB+木と転置インデックスのレコード圧縮を機能させる。
    • +
    • --enable-iconv : ICONVによる文字コード変換ユーティリティを機能させる。
    • +
    + +

    通常、QDBMとそのアプリケーションは `libqdbm.*' 以外の非標準のライブラリには依存しないでビルドすることができる。ただし、POSIXスレッドを有効にした場合は `libpthread.*' に依存し、ZLIBを有効にした場合は `libz.*' に依存し、LZOを有効にした場合は `liblzo2.*' に依存し、BZIP2を有効にした場合は `libbz2.*' に依存し、ICONVを有効にした場合は `libiconv.*' に依存するようになる。

    + +

    LZOのライセンスはGNU GPLなので、`liblzo2.*' とリンクしたアプリケーションはGNU GPLの制約を受けることに注意すること。

    + +
    + +

    Depot: 基本API

    + +

    概要

    + +

    DepotはQDBMの基本APIである。QDBMが提供するデータベース管理機能のほぼ全てがDepotによって実装される。その他のAPIはDepotのラッパーにすぎない。したがって、QDBMのAPIの中でDepotが最も高速に動作する。

    + +

    Depotを使うためには、`depot.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <depot.h>
    +
    #include <stdlib.h>
    +
    + +

    Depotでデータベースを扱う際には、`DEPOT' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `dpopen' で開き、関数 `dpclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `dpclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。

    + +

    API

    + +

    外部変数 `dpversion' はバージョン情報の文字列である。

    + +
    +
    extern const char *dpversion;
    +
    この変数の指す領域は書き込み禁止である。
    +
    + +

    外部変数 `dpecode' には直前のエラーコードが記録される。エラーコードの詳細については `depot.h' を参照すること。

    + +
    +
    extern int dpecode;
    +
    この変数の初期値は `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' がある。
    +
    + +

    エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。

    + +
    +
    const char *dperrmsg(int ecode);
    +
    `ecode' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止である。
    +
    + +

    データベースのハンドルを作成するには、関数 `dpopen' を用いる。

    + +
    +
    DEPOT *dpopen(const char *name, int omode, int bnum);
    +
    `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' を使う場合、アプリケーションが排他制御の責任を負う。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `dpclose' を用いる。

    + +
    +
    int dpclose(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
    +
    + +

    レコードを追加するには、関数 `dpput' を用いる。

    + +
    +
    int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`DP_DOVER' は既存のレコードの値を上書きし、`DP_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    レコードを削除するには、関数 `dpout' を用いる。

    + +
    +
    int dpout(DEPOT *depot, const char *kbuf, int ksiz);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
    +
    + +

    レコードを取得するには、関数 `dpget' を用いる。

    + +
    +
    char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    レコードを取得してバッファに書き込むには、関数 `dpgetwb' を用いる。

    + +
    +
    int dpgetwb(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, char *vbuf);
    +
    `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。
    +
    + +

    レコードの値のサイズを取得するには、関数 `dpvsiz' を用いる。

    + +
    +
    int dpvsiz(DEPOT *depot, const char *kbuf, int ksiz);
    +
    `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`dpget' と違って実データを読み込まないので効率がよい。
    +
    + +

    データベースのイテレータを初期化するには、関数 `dpiterinit' を用いる。

    + +
    +
    int dpiterinit(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。
    +
    + +

    データベースのイテレータから次のレコードのキーを取り出すには、関数 `dpiternext' を用いる。

    + +
    +
    char *dpiternext(DEPOT *depot, int *sp);
    +
    `depot' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。
    +
    + +

    データベースのアラインメントを設定するには、関数 `dpsetalign' を用いる。

    + +
    +
    int dpsetalign(DEPOT *depot, int align);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
    +
    + +

    データベースのフリーブロックプールのサイズ設定するには、関数 `dpsetfbpsiz' を用いる。

    + +
    +
    int dpsetfbpsiz(DEPOT *depot, int size);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。
    +
    + +

    データベースを更新した内容をファイルとデバイスに同期させるには、関数 `dpsync' を用いる。

    + +
    +
    int dpsync(DEPOT *depot);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。
    +
    + +

    データベースを最適化するには、関数 `dpoptimize' を用いる。

    + +
    +
    int dpoptimize(DEPOT *depot, int bnum);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。
    +
    + +

    データベースの名前を得るには、関数 `dpname' を用いる。

    + +
    +
    char *dpname(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    データベースファイルのサイズを得るには、関数 `dpfsiz' を用いる。

    + +
    +
    int dpfsiz(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。
    +
    + +

    データベースのバケット配列の要素数を得るには、関数 `dpbnum' を用いる。

    + +
    +
    int dpbnum(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数であり、エラーなら -1 である。
    +
    + +

    データベースのバケット配列の利用済みの要素数を得るには、関数 `dpbusenum' を用いる。

    + +
    +
    int dpbusenum(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。
    +
    + +

    データベースのレコード数を得るには、関数 `dprnum' を用いる。

    + +
    +
    int dprnum(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
    +
    + +

    データベースハンドルがライタかどうかを調べるには、関数 `dpwritable' を用いる。

    + +
    +
    int dpwritable(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
    +
    + +

    データベースに致命的エラーが起きたかどうかを調べるには、関数 `dpfatalerror' を用いる。

    + +
    +
    int dpfatalerror(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
    +
    + +

    データベースファイルのinode番号を得るには、関数 `dpinode' を用いる。

    + +
    +
    int dpinode(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。
    +
    + +

    データベースの最終更新時刻を得るには、関数 `dpmtime' を用いる。

    + +
    +
    time_t dpmtime(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
    +
    + +

    データベースファイルのファイルディスクリプタを得るには、関数 `dpfdesc' を用いる。

    + +
    +
    int dpfdesc(DEPOT *depot);
    +
    `depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースのファイルディスクリプタを直接操ることは推奨されない。
    +
    + +

    データベースファイルを削除するには、関数 `dpremove' を用いる。

    + +
    +
    int dpremove(const char *name);
    +
    `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    壊れたデータベースファイルを修復するには、関数 `dprepair' を用いる。

    + +
    +
    int dprepair(const char *name);
    +
    `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。
    +
    + +

    全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `dpexportdb' を用いる。

    + +
    +
    int dpexportdb(DEPOT *depot, const char *name);
    +
    `depot' はデータベースハンドルを指定する。`name' は出力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    エンディアン非依存データから全てのレコードをロードするには、関数 `dpimportdb' を用いる。

    + +
    +
    int dpimportdb(DEPOT *depot, const char *name);
    +
    `depot' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    データベースファイルからレコードを直接取得するには、関数 `dpsnaffle' を用いる。

    + +
    +
    char *dpsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
    +
    `name' はデータベースファイルの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースファイルが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。
    +
    + +

    データベースの内部で用いるハッシュ関数として、関数 `dpinnerhash' がある。

    + +
    +
    int dpinnerhash(const char *kbuf, int ksiz);
    +
    `kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがバケット配列の状態を予測する際に役立つ。
    +
    + +

    データベースの内部で用いるハッシュ関数と独立したハッシュ関数として、関数 `dpouterhash' がある。

    + +
    +
    int dpouterhash(const char *kbuf, int ksiz);
    +
    `kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがデータベースの更に上でハッシュアルゴリズムを利用する際に役立つ。
    +
    + +

    ある数以上の自然数の素数を得るには、関数 `dpprimenum' を用いる。

    + +
    +
    int dpprimenum(int num);
    +
    `num' は適当な自然数を指定する。戻り値は、指定した数と同じかより大きくかつなるべく小さい自然数の素数である。この関数はアプリケーションが利用するバケット配列のサイズを決める場合に役立つ。
    +
    + +

    サンプルコード

    + +

    名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    注記

    + +

    Depotを利用したプログラムをビルドするには、ライブラリ `libqdbm.a' または `libqdbm.so' をリンク対象に加える必要がある。例えば、`sample.c' から `sample' を作るには、以下のようにビルドを行う。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Depotの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +
    + +

    Depot用コマンド

    + +

    Depotに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

    + +
    +
    dpmgr create [-s] [-bnum num] name
    +
    データベースファイルを作成する。
    +
    dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val
    +
    キーと値に対応するレコードを追加する。
    +
    dpmgr out [-kx|-ki] name key
    +
    キーに対応するレコードを削除する。
    +
    dpmgr get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key
    +
    キーに対応するレコードの値を取得して標準出力する。
    +
    dpmgr list [-nl] [-k|-v] [-ox] name
    +
    データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
    +
    dpmgr optimize [-bnum num] [-na] name
    +
    データベースを最適化する。
    +
    dpmgr inform [-nl] name
    +
    データベースの雑多な情報を出力する。
    +
    dpmgr remove name
    +
    データベースファイルを削除する。
    +
    dpmgr repair name
    +
    壊れたデータベースファイルを修復する。
    +
    dpmgr exportdb name file
    +
    全てのレコードをエンディアン非依存のデータとしてダンプする。
    +
    dpmgr importdb [-bnum num] name file
    +
    エンディアン非依存データから全てのレコードをロードする。
    +
    dpmgr snaffle [-kx|-ki] [-ox] [-n] name key
    +
    ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。
    +
    dpmgr version
    +
    QDBMのバージョン情報を標準出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -s : ファイルをスパースにする。
    • +
    • -bnum num : バケット配列の要素数を `num' に指定する。
    • +
    • -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
    • +
    • -ki : 10進数による数値表現として `key' を扱う。
    • +
    • -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
    • +
    • -vi : 10進数による数値表現として `val' を扱う。
    • +
    • -vf : 名前が `val' のファイルのデータを値として読み込む。
    • +
    • -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
    • +
    • -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
    • +
    • -na : アラインメントを設定しない。
    • +
    • -nl : ファイルロックをかけずにデータベースを開く。
    • +
    • -start : 値から取り出すデータの開始オフセットを指定する。
    • +
    • -max : 値から取り出すデータの最大の長さを指定する。
    • +
    • -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
    • +
    • -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
    • +
    • -k : キーのみを出力する。
    • +
    • -v : 値のみを出力する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `dptest' はDepotの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `dpmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。

    + +
    +
    dptest write [-s] name rnum bnum
    +
    `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
    +
    dptest read [-wb] name
    +
    上記で生成したデータベースの全レコードを検索する。
    +
    dptest rcat [-c] name rnum bnum pnum align fbpsiz
    +
    キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
    +
    dptest combo name
    +
    各種操作の組み合わせテストを行う。
    +
    dptest wicked [-c] name rnum
    +
    各種更新操作を無作為に選択して実行する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -s : ファイルをスパースにする。
    • +
    • -wb : 関数 `dpget' の代わりに関数 `dpgetwb' を用いる。
    • +
    • -c : Cabinのマップを使って比較テストを行う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `dptsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとDepotのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。

    + +
    +
    dptsv import [-bnum num] [-bin] name
    +
    TSVファイルを読み込んでデータベースを作成する。
    +
    dptsv export [-bin] name
    +
    データベースの全てのレコードをTSVファイルとして出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -bnum num : バケット配列の要素数を `num' に指定する。
    • +
    • -bin : Base64形式でレコードを扱う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    Depotのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。

    + +
    cat /etc/passwd | tr ':' '\t' | dptsv import casket
    +
    + +

    そして、`mikio' というユーザの情報を取り出すには、以下のようにする。

    + +
    dpmgr get casket mikio
    +
    + +

    これらのコマンドと同等の機能をDepotのAPIを用いて実装することも容易である。

    + +
    + +

    Curia: 拡張API

    + +

    概要

    + +

    CuriaはQDBMの拡張APIであり、複数のデータベースファイルをディレクトリで一括して扱う機能を提供する。データベースを複数のファイルに分割することで、ファイルシステムによるファイルサイズの制限を回避することができる。複数のデバイスにファイルを分散させれば、スケーラビリティを向上させることができる。

    + +

    Depotではファイル名を指定してデータベースを構築するが、Curiaではディレクトリ名を指定してデータベースを構築する。指定したディレクトリの直下には、`depot' という名前のデータベースファイルが生成される。これはディレクトリの属性を保持するものであり、レコードの実データは格納されない。それとは別に、データベースを分割した個数だけ、4桁の10進数値の名前を持つサブディレクトリが生成され、各々のサブディレクトリの中には `depot' という名前でデータベースファイルが生成される。レコードの実データはそれらに格納される。例えば、`casket' という名前のデータベースを作成し、分割数を3にする場合、`casket/depot'、`casket/0001/depot'、`casket/0002/depot'、`casket/0003/depot' が生成される。データベースを作成する際にすでにディレクトリが存在していてもエラーとはならない。したがって、予めサブディレクトリを生成しておいて、各々に異なるデバイスのファイルシステムをマウントしておけば、データベースファイルを複数のデバイスに分散させることができる。

    + +

    Curiaにはラージオブジェクトを扱う機能がある。通常のレコードのデータはデータベースファイルに格納されるが、ラージオブジェクトのレコードのデータは個別のファイルに格納される。ラージオブジェクトのファイルはハッシュ値を元にディレクトリに分けて格納されるので、通常のレコードには劣るが、それなりの速度で参照できる。サイズが大きく参照頻度が低いデータは、ラージオブジェクトとしてデータベースファイルから分離すべきである。そうすれば、通常のレコードに対する処理速度が向上する。ラージオブジェクトのディレクトリ階層はデータベースファイルが格納されるサブディレクトリの中の `lob' という名前のディレクトリの中に作られる。通常のデータベースとラージオブジェクトのデータベースはキー空間が異なり、互いに干渉することはない。

    + +

    Curiaを使うためには、`depot.h' と `curia.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <depot.h>
    +
    #include <curia.h>
    +
    #include <stdlib.h>
    +
    + +

    Curiaでデータベースを扱う際には、`CURIA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `cropen' で開き、関数 `crclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `crclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースディレクトリを同時に利用することは可能であるが、同じデータベースディレクトリの複数のハンドルを利用してはならない。

    + +

    CuriaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。

    + +

    API

    + +

    データベースのハンドルを作成するには、関数 `cropen' を用いる。

    + +
    +
    CURIA *cropen(const char *name, int omode, int bnum, int dnum);
    +
    `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' を使う場合、アプリケーションが排他制御の責任を負う。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `crclose' を用いる。

    + +
    +
    int crclose(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
    +
    + +

    レコードを追加するには、関数 `crput' を用いる。

    + +
    +
    int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    レコードを削除するには、関数 `crout' を用いる。

    + +
    +
    int crout(CURIA *curia, const char *kbuf, int ksiz);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
    +
    + +

    レコードを取得するには、関数 `crget' を用いる。

    + +
    +
    char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    レコードを取得してバッファに書き込むには、関数 `crgetwb' を用いる。

    + +
    +
    int crgetwb(CURIA *curia, const char *kbuf, int ksiz, int start, int max, char *vbuf);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。
    +
    + +

    レコードの値のサイズを取得するには、関数 `crvsiz' を用いる。

    + +
    +
    int crvsiz(CURIA *curia, const char *kbuf, int ksiz);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crget' と違って実データを読み込まないので効率がよい。
    +
    + +

    データベースのイテレータを初期化するには、関数 `criterinit' を用いる。

    + +
    +
    int criterinit(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。
    +
    + +

    データベースのイテレータから次のレコードのキーを取り出すには、関数 `criternext' を用いる。

    + +
    +
    char *criternext(CURIA *curia, int *sp);
    +
    `curia' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。
    +
    + +

    データベースのアラインメントを設定するには、関数 `crsetalign' を用いる。

    + +
    +
    int crsetalign(CURIA *curia, int align);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
    +
    + +

    データベースのフリーブロックプールのサイズ設定するには、関数 `crsetfbpsiz' を用いる。

    + +
    +
    int crsetfbpsiz(CURIA *curia, int size);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。
    +
    + +

    データベースを更新した内容をファイルとデバイスに同期させるには、関数 `crsync' を用いる。

    + +
    +
    int crsync(CURIA *curia);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。
    +
    + +

    データベースを最適化するには、関数 `croptimize' を用いる。

    + +
    +
    int croptimize(CURIA *curia, int bnum);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。
    +
    + +

    データベースの名前を得るには、関数 `crname' を用いる。

    + +
    +
    char *crname(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    データベースファイルのサイズの合計を得るには、関数 `crfsiz' を用いる。

    + +
    +
    int crfsiz(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1 である。戻り値が2GBを越えた場合は桁溢れが起こる。
    +
    + +

    データベースファイルのサイズの合計を倍精度浮動小数として得るには、関数 `crfsizd' を用いる。

    + +
    +
    double crfsizd(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計の倍精度値であり、エラーなら -1 である。
    +
    + +

    データベースのバケット配列の要素数の合計を得るには、関数 `crbnum' を用いる。

    + +
    +
    int crbnum(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数の合計であり、エラーなら -1 である。
    +
    + +

    データベースのバケット配列の利用済みの要素数の合計を得るには、関数 `crbusenum' を用いる。

    + +
    +
    int crbusenum(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数の合計であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。
    +
    + +

    データベースのレコード数を得るには、関数 `crrnum' を用いる。

    + +
    +
    int crrnum(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
    +
    + +

    データベースハンドルがライタかどうかを調べるには、関数 `crwritable' を用いる。

    + +
    +
    int crwritable(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
    +
    + +

    データベースに致命的エラーが起きたかどうかを調べるには、関数 `crfatalerror' を用いる。

    + +
    +
    int crfatalerror(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
    +
    + +

    データベースディレクトリのinode番号を得るには、関数 `crinode' を用いる。

    + +
    +
    int crinode(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。
    +
    + +

    データベースの最終更新時刻を得るには、関数 `crmtime' を用いる。

    + +
    +
    time_t crmtime(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
    +
    + +

    データベースディレクトリを削除するには、関数 `crremove' を用いる。

    + +
    +
    int crremove(const char *name);
    +
    `name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    壊れたデータベースディレクトリを修復するには、関数 `crrepair' を用いる。

    + +
    +
    int crrepair(const char *name);
    +
    `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。
    +
    + +

    全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `crexportdb' を用いる。

    + +
    +
    int crexportdb(CURIA *curia, const char *name);
    +
    `curia' はデータベースハンドルを指定する。`name' は出力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    エンディアン非依存データから全てのレコードをロードするには、関数 `crimportdb' を用いる。

    + +
    +
    int crimportdb(CURIA *curia, const char *name);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    データベースディレクトリからレコードを直接取得するには、関数 `crsnaffle' を用いる。

    + +
    +
    char *crsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
    +
    `name' はデータベースディレクトリの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースディレクトリが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。
    +
    + +

    ラージオブジェクト用データベースにレコードを追加するには、関数 `crputlob' を用いる。

    + +
    +
    int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    ラージオブジェクト用データベースからレコードを削除するには、関数 `croutlob' を用いる。

    + +
    +
    int croutlob(CURIA *curia, const char *kbuf, int ksiz);
    +
    `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
    +
    + +

    ラージオブジェクト用データベースからレコードの値を取得するには、関数 `crgetlob' を用いる。

    + +
    +
    char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    ラージオブジェクト用データベースにあるレコードのファイルディスクリプタを取得するには、関数 `crgetlobfd' を用いる。

    + +
    +
    int crgetlobfd(CURIA *curia, const char *kbuf, int ksiz);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら該当のファイルディスクリプタであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。戻り値のファイルディスクリプタは `open' コールで開かれる。データベースがライタで接続された場合はそのディスクリプタは書き込み可能(O_RDWR)であり、そうでなければ書き込み不可能(O_RDONLY)である。ディスクリプタが不要になったら `close' で閉じるべきである。
    +
    + +

    ラージオブジェクト用データベースにあるレコードの値のサイズを取得するには、関数 `crvsizlob' を用いる。

    + +
    +
    int crvsizlob(CURIA *curia, const char *kbuf, int ksiz);
    +
    `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであり、該当がない場合やエラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crgetlob' と違って実データを読み込まないので効率がよい。
    +
    + +

    ラージオブジェクト用データベースのレコード数の合計を得るには、関数 `crrnumlob' を用いる。

    + +
    +
    int crrnumlob(CURIA *curia);
    +
    `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数の合計であり、エラーなら -1 である。
    +
    + +

    サンプルコード

    + +

    名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <curia.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <curia.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    注記

    + +

    Curiaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Curiaの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +
    + +

    Curia用コマンド

    + +

    Curiaに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

    + +
    +
    crmgr create [-s] [-bnum num] [-dnum num] name
    +
    データベースディレクトリを作成する。
    +
    crmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] name key val
    +
    キーと値に対応するレコードを追加する。
    +
    crmgr out [-kx|-ki] [-lob] name key
    +
    キーに対応するレコードを削除する。
    +
    crmgr get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key
    +
    キーに対応するレコードの値を取得して標準出力する。
    +
    crmgr list [-nl] [-k|-v] [-ox] name
    +
    データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
    +
    crmgr optimize [-bnum num] [-na] name
    +
    データベースを最適化する。
    +
    crmgr inform [-nl] name
    +
    データベースの雑多な情報を出力する。
    +
    crmgr remove name
    +
    データベースディレクトリを削除する。
    +
    crmgr repair name
    +
    壊れたデータベースディレクトリを修復する。
    +
    crmgr exportdb name dir
    +
    全てのレコードをエンディアン非依存のデータとしてダンプする。
    +
    crmgr importdb [-bnum num] [-dnum num] name dir
    +
    エンディアン非依存データから全てのレコードをロードする。
    +
    crmgr snaffle [-kx|-ki] [-ox] [-n] name key
    +
    ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。
    +
    crmgr version
    +
    QDBMのバージョン情報を標準出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -s : ファイルをスパースにする。
    • +
    • -bnum num : バケット配列の要素数を `num' に指定する。
    • +
    • -dnum num : データベースファイルの分割数を `num' に指定する。
    • +
    • -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
    • +
    • -ki : 10進数による数値表現として `key' を扱う。
    • +
    • -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
    • +
    • -vi : 10進数による数値表現として `val' を扱う。
    • +
    • -vf : 名前が `val' のファイルのデータを値として読み込む。
    • +
    • -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
    • +
    • -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
    • +
    • -na : アラインメントを設定しない。
    • +
    • -nl : ファイルロックをかけずにデータベースを開く。
    • +
    • -start : 値から取り出すデータの開始オフセットを指定する。
    • +
    • -max : 値から取り出すデータの最大の長さを指定する。
    • +
    • -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
    • +
    • -lob : ラージオブジェクトを扱う。
    • +
    • -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
    • +
    • -k : キーのみを出力する。
    • +
    • -v : 値のみを出力する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `crtest' はCuriaの機能テストや性能テストに用いるツールである。`crtest' によって生成されたデータベースディレクトリを `crmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`dnum' はデータベースファイルの分割数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。

    + +
    +
    crtest write [-s] [-lob] name rnum bnum dnum
    +
    `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
    +
    crtest read [-wb] [-lob] name
    +
    上記で生成したデータベースの全レコードを検索する。
    +
    crtest rcat [-c] name rnum bnum dnum pnum align fbpsiz
    +
    キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
    +
    crtest combo name
    +
    各種操作の組み合わせテストを行う。
    +
    crtest wicked [-c] name rnum
    +
    各種更新操作を無作為に選択して実行する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -s : ファイルをスパースにする。
    • +
    • -lob : ラージオブジェクトを扱う。
    • +
    • -wb : 関数 `crget' の代わりに関数 `crgetwb' を用いる。
    • +
    • -c : Cabinのマップを使って比較テストを行う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `crtsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとCuriaのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`-dnum' オプションの引数 `num' は要素データベースの数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。

    + +
    +
    crtsv import [-bnum num] [-dnum num] [-bin] name
    +
    TSVファイルを読み込んでデータベースを作成する。
    +
    crtsv export [-bin] name
    +
    データベースの全てのレコードをTSVファイルとして出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -bnum num : バケット配列の要素数を `num' に指定する。
    • +
    • -dnum num : データベースファイルの分割数を `num' に指定する。
    • +
    • -bin : Base64形式でレコードを扱う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    Curiaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。

    + +
    cat /etc/passwd | tr ':' '\t' | crtsv import casket
    +
    + +

    そして、`mikio' というユーザの情報を取り出すには、以下のようにする。

    + +
    crmgr get casket mikio
    +
    + +

    これらのコマンドと同等の機能をCuriaのAPIを用いて実装することも容易である。

    + +
    + +

    Relic: NDBM互換API

    + +

    概要

    + +

    Relicは、NDBMと互換するAPIである。すなわち、Depotの関数群をNDBMのAPIで包んだものである。Relicを使ってNDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `ndbm.h' から `relic.h' に換え、ビルドの際のリンカオプションを `-lndbm' から `-lqdbm' に換えるだけでよい。

    + +

    オリジナルのNDBMでは、データベースは二つのファイルの対からなる。ひとつは接尾辞に `.dir' がつく名前で、キーのビットマップを格納する「ディレクトリファイル」である。もうひとつは接尾辞に `.pag' がつく名前で、データの実体を格納する「データファイル」である。Relicではディレクトリファイルは単なるダミーとして作成し、データファイルをデータベースとする。RelicではオリジナルのNDBMと違い、格納するデータのサイズに制限はない。なお、オリジナルのNDBMで生成したデータベースファイルをRelicで扱うことはできない。

    + +

    Relicを使うためには、`relic.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' と `fcntl.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <relic.h>
    +
    #include <stdlib.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    #include <fcntl.h>
    +
    + +

    Relicでデータベースを扱う際には、`DBM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `dbm_open' で開き、関数 `dbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。

    + +

    API

    + +

    データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。

    + +
    +
    typedef struct { void *dptr; size_t dsize; } datum;
    +
    `dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。
    +
    + +

    データベースのハンドルを作成するには、関数 `dbm_open' を用いる。

    + +
    +
    DBM *dbm_open(char *name, int flags, int mode);
    +
    `name' はデータベースの名前を指定するが、ファイル名はそれに接尾辞をつけたものになる。`flags' は `open' コールに渡すものと同じだが、`O_WRONLY' は `O_RDWR' と同じになり、追加フラグでは `O_CREAT' と `O_TRUNC' のみが有効である。`mode' は `open' コールに渡すものと同じでファイルのモードを指定する。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `dbm_close' を用いる。

    + +
    +
    void dbm_close(DBM *db);
    +
    `db' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
    +
    + +

    レコードを追加するには、関数 `dbm_store' を用いる。

    + +
    +
    int dbm_store(DBM *db, datum key, datum content, int flags);
    +
    `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `DBM_INSERT' ならキーの重複時に書き込みを断念し、`DBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 であり、重複での断念なら 1 であり、その他のエラーなら -1 である。
    +
    + +

    レコードを削除するには、関数 `dbm_delete' を用いる。

    + +
    +
    int dbm_delete(DBM *db, datum key);
    +
    `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 であり、エラーなら -1 である。
    +
    + +

    レコードを取得するには、関数 `dbm_fetch' を用いる。

    + +
    +
    datum dbm_fetch(DBM *db, datum key);
    +
    `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、メンバ `dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
    +
    + +

    最初のレコードのキーを得るには、関数 `dbm_firstkey' を用いる。

    + +
    +
    datum dbm_firstkey(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_nextkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
    +
    + +

    次レコードのキーを得るには、関数 `dbm_nextkey' を用いる。

    + +
    +
    datum dbm_nextkey(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_firstkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
    +
    + +

    データベースに致命的エラーが起きたかどうかを調べるには、関数 `dbm_error' を用いる。

    + +
    +
    int dbm_error(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
    +
    + +

    関数 `dbm_clearerr' は何もしない。

    + +
    +
    int dbm_clearerr(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値は 0 である。この関数は互換性のためにのみ存在する。
    +
    + +

    データベースが読み込み専用かどうかを調べるには、関数 `dbm_rdonly' を用いる。

    + +
    +
    int dbm_rdonly(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値は読み込み専用なら真であり、そうでなければ偽である。
    +
    + +

    ディレクトリファイルのファイルディスクリプタを得るには、関数 `dbm_dirfno' を用いる。

    + +
    +
    int dbm_dirfno(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値はディレクトリファイルのファイルディスクリプタである。
    +
    + +

    データファイルのファイルディスクリプタを得るには、関数 `dbm_pagfno' を用いる。

    + +
    +
    int dbm_pagfno(DBM *db);
    +
    `db' はデータベースハンドルを指定する。戻り値はデータファイルのファイルディスクリプタである。
    +
    + +

    サンプルコード

    + +

    名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <relic.h>
    +#include <stdlib.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < val.dsize; i++){
    +      putchar(((char *)val.dptr)[i]);
    +    }
    +    putchar('\n');
    +  } else {
    +    perror("dbm_fetch");
    +  }
    +
    +  /* データベースを閉じる */
    +  dbm_close(db);
    +
    +  return 0;
    +}
    +
    + +

    注記

    + +

    Relicを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lndbm' ではなく `-lqdbm' である。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    スレッド間で同時に同じハンドルにアクセスしない限りは、Relicの各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +
    + +

    Relic用コマンド

    + +

    Relicに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `rlmgr' はRelicやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

    + +
    +
    rlmgr create name
    +
    データベースファイルを作成する。
    +
    rlmgr store [-kx] [-vx|-vf] [-insert] name key val
    +
    キーと値に対応するレコードを追加する。
    +
    rlmgr delete [-kx] name key
    +
    キーに対応するレコードを削除する。
    +
    rlmgr fetch [-kx] [-ox] [-n] name key
    +
    キーに対応するレコードの値を取得して標準出力する。
    +
    rlmgr list [-ox] name
    +
    データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
    • +
    • -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
    • +
    • -vf : 名前が `val' のファイルのデータを値として読み込む。
    • +
    • -insert : 既存のレコードとキーが重複時に上書きせずにエラーにする。
    • +
    • -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
    • +
    • -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    コマンド `rltest' はRelicの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `rlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。

    + +
    +
    rltest write name rnum
    +
    `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
    +
    rltest read name rnum
    +
    上記で生成したデータベースを検索する。
    +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +
    + +

    Hovel: GDBM互換API

    + +

    概要

    + +

    Hovelは、GDBMと互換するAPIである。すなわち、DepotおよびCuriaの関数群をGDBMのAPIで包んだものである。Hovelを使ってGDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `gdbm.h' から `hovel.h' に換え、ビルドの際のリンカオプションを `-lgdbm' から `-lqdbm' に換えるだけでよい。なお、オリジナルのGDBMで生成したデータベースファイルをHovelで扱うことはできない。

    + +

    Hovelを使うためには、`hovel.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <hovel.h>
    +
    #include <stdlib.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    + +

    Hovelでデータベースを扱う際には、`GDBM_FILE' 型のオブジェクト(それ自体がポインタ型)をハンドルとして用いる。ハンドルは、関数 `gdbm_open' で開き、関数 `gdbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。Hovelは通常はDepotのラッパーとして動作してデータベースファイルを扱うが、ハンドルを開く際に関数 `gdbm_open2' を用いることによってCuriaのラッパーとしてデータベースディレクトリを扱うようにすることができる。

    + +

    API

    + +

    データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。

    + +
    +
    typedef struct { char *dptr; size_t dsize; } datum;
    +
    `dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。
    +
    + +

    外部変数 `gdbm_version' はバージョン情報の文字列である。

    + +
    +
    extern char *gdbm_version;
    +
    この変数の指す領域は書き込み禁止である。
    +
    + +

    外部変数 `gdbm_errno' には直前のエラーコードが記録される。エラーコードの詳細については `hovel.h' を参照すること。

    + +
    +
    extern gdbm_error gdbm_errno;
    +
    この変数の初期値は `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' がある。
    +
    + +

    エラーコードに対応するメッセージ文字列を得るには、関数 `gdbm_strerror' を用いる。

    + +
    +
    char *gdbm_strerror(gdbm_error gdbmerrno);
    +
    `gdbmerrno' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止領域である。
    +
    + +

    GDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open' を用いる。

    + +
    +
    GDBM_FILE gdbm_open(char *name, int block_size, int read_write, int mode, void (*fatal_func)(void));
    +
    `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' である。
    +
    + +

    QDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open2' を用いる。

    + +
    +
    GDBM_FILE gdbm_open2(char *name, int read_write, int mode, int bnum, int dnum, int align);
    +
    `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のものかが自動的に判断される。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `gdbm_close' を用いる。

    + +
    +
    void gdbm_close(GDBM_FILE dbf);
    +
    `dbf' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
    +
    + +

    レコードを追加するには、関数 `gdbm_store' を用いる。

    + +
    +
    int gdbm_store(GDBM_FILE dbf, datum key, datum content, int flag);
    +
    `dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `GDBM_INSERT' ならキーの重複時に書き込みを断念し、`GDBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 、重複での断念なら 1 、その他のエラーなら -1 である。
    +
    + +

    レコードを削除するには、関数 `gdbm_delete' を用いる。

    + +
    +
    int gdbm_delete(GDBM_FILE dbf, datum key);
    +
    `dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 、エラーなら -1 である。
    +
    + +

    レコードを取得するには、関数 `gdbm_fetch' を用いる。

    + +
    +
    datum gdbm_fetch(GDBM_FILE dbf, datum key);
    +
    `dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    レコードが存在するか調べるには、関数 `gdbm_exists' を用いる。

    + +
    +
    int gdbm_exists(GDBM_FILE dbf, datum key);
    +
    `dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は該当があれば真であり、該当がない場合やエラーの場合は偽である。
    +
    + +

    最初のレコードのキーを得るには、関数 `gdbm_firstkey' を用いる。

    + +
    +
    datum gdbm_firstkey(GDBM_FILE dbf);
    +
    `dbf' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    次のレコードのキーを得るには、関数 gdbm_nextkey を用いる。

    + +
    +
    datum gdbm_nextkey(GDBM_FILE dbf, datum key);
    +
    `dbf' はデータベースハンドルを指定する。`key' は無視される。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    データベースを更新した内容をファイルとデバイスに同期させるには、関数 `gdbm_sync' を用いる。

    + +
    +
    void gdbm_sync(GDBM_FILE dbf);
    +
    `dbf' はライタで接続したデータベースハンドルを指定する。
    +
    + +

    データベースを最適化するには、関数 `gdbm_reorganize' を用いる。

    + +
    +
    int gdbm_reorganize(GDBM_FILE dbf);
    +
    `dbf' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら 0 であり、エラーなら -1 である。
    +
    + +

    データベースファイルのファイルディスクリプタを得るには、関数 `gdbm_fdesc' を用いる。

    + +
    +
    int gdbm_fdesc(GDBM_FILE dbf);
    +
    `dbf' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースがディレクトリなら、戻り値は -1 である。
    +
    + +

    関数 `gdbm_setopt' は何もしない。

    + +
    +
    int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size);
    +
    `dbf' はデータベースハンドルを指定する。`option' は無視される。`value' は無視される。`size' は無視される。戻り値は 0 である。この関数は互換性のためにのみ存在する。
    +
    + +

    サンプルコード

    + +

    名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <hovel.h>
    +#include <stdlib.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < 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;
    +}
    +
    + +

    注記

    + +

    Hovelを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lgdbm' ではなく `-lqdbm' である。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `gdbm_errno' はスレッド固有データへの参照として扱われ、Hovelの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +
    + +

    Hovel用コマンド

    + +

    Hovelに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `hvmgr' はHovelやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりす機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

    + +
    +
    hvmgr [-qdbm bnum dnum] [-s] create name
    +
    データベースファイルを作成する。
    +
    hvmgr store [-qdbm] [-kx] [-vx|-vf] [-insert] name key val
    +
    キーと値に対応するレコードを追加する。
    +
    hvmgr delete [-qdbm] [-kx] name key
    +
    キーに対応するレコードを削除する。
    +
    hvmgr fetch [-qdbm] [-kx] [-ox] [-n] name key
    +
    キーに対応するレコードの値を取得して標準出力する。
    +
    hvmgr list [-qdbm] [-ox] name
    +
    データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
    +
    hvmgr optimize [-qdbm] name
    +
    データベースを最適化する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -qdbm [bnum dnum] : `gdbm_open2' でデータベースを開く。`bnum' と `dnum' はバケット配列の要素数とデータベースの分割数を指定する。
    • +
    • -s : ファイルをスパースにする。
    • +
    • -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
    • +
    • -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
    • +
    • -vf : 名前が `val' のファイルのデータを値として読み込む。
    • +
    • -insert : 既存のレコードとキーが重複時に上書きせずにエラーにする。
    • +
    • -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
    • +
    • -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    コマンド `hvtest' はHovelの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `hvmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。

    + +
    +
    hvtest write [-qdbm] [-s] name rnum
    +
    `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
    +
    hvtest read [-qdbm] name rnum
    +
    上記で生成したデータベースを検索する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -qdbm : `gdbm_open2' を用いてCuriaのハンドルを開く。
    • +
    • -s : ファイルをスパースにする。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +
    + +

    Cabin: ユーティリティAPI

    + +

    概要

    + +

    Cabinはメモリ上で簡単にレコードを扱うためのメモリ確保関数や整列関数や拡張可能なデータや配列リストやハッシュマップやヒープ配列など提供するユーティリティのAPIである。MIMEやCSVやXMLを解析する機能や、各種の符号化と復号を行う機能も備える。

    + +

    Cabinを使うためには、`cabin.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <cabin.h>
    +
    #include <stdlib.h>
    +
    + +

    拡張可能なデータを扱う際には、`CBDATUM' 型へのポインタをハンドルとして用いる。データハンドルは、関数 `cbdatumopen' で開き、関数 `cbdatumclose' で閉じる。リストを扱う際には、`CBLIST' 型へのポインタをハンドルとして用いる。リストハンドルは、関数 `cblistopen' で開き、関数 `cblistclose' で閉じる。マップを扱う際には、`CBMAP' 型へのポインタをハンドルとして用いる。マップハンドルは、関数 `cbmapopen' で開き、関数 `cbmapclose' で閉じる。ヒープ配列を扱う際には `CBHEAP' 型へのポインタをハンドルとして用いる。ヒープハンドルは関数 `cbheapopen' で開き、関数 `cbheapclose' で閉じる。各ハンドルのメンバを直接参照することは推奨されない。 + +

    + +

    API

    + +

    外部変数 `cbfatalfunc' は致命的エラーをハンドリングするコールバック関数である。

    + +
    +
    extern void (*cbfatalfunc)(const char *message);
    +
    引数はエラーメッセージを指定する。この変数の初期値は `NULL' であり、`NULL' ならば致命的エラーの発生時にはデフォルトの関数が呼ばれる。致命的エラーはメモリの割り当てに失敗した際に起こる。
    +
    + +

    メモリ上に領域を確保するには、関数 `cbmalloc' を用いる。

    + +
    +
    void *cbmalloc(size_t size);
    +
    `size' は領域のサイズを指定する。戻り値は確保した領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    メモリ上の領域を再確保するには、関数 `cbrealloc' を用いる。

    + +
    +
    void *cbrealloc(void *ptr, size_t size);
    +
    `ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値の領域は `remalloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    メモリ上の領域を複製するには、関数 `cbmemdup' を用いる。

    + +
    +
    char *cbmemdup(const char *ptr, int size);
    +
    `ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値は複製の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    メモリ上の開放を解放するには、関数 `cbfree' を用いる。

    + +
    +
    void cbfree(void *ptr);
    +
    `ptr' は領域へのポインタを指定するが、`NULL' の場合は何もしない。この関数は `free' のラッパーに過ぎないが、`malloc' シリーズの別のパッケージを使うアプリケーションにおいてQDBMが確保した領域を解放するのに便利である。
    +
    + +

    オブジェクトのポインタかハンドルをグローバルガベージコレクタに登録するには、関数 `cbglobalgc' を用いる。

    + +
    +
    void cbglobalgc(void *ptr, void (*func)(void *));
    +
    `ptr' はオブジェクトのポインタかハンドルを指定する。`func' はオブジェクトのリソースを解放する関数を指定する。その引数は解放するオブジェクトのポインタかハンドルである。この関数は、`main' 関数がリターンするか `exit' 関数が呼ばれてプロセスが正常終了する際に、オブジェクトのリソースが解放されることを保証する。
    +
    + +

    グローバルガベージコレクタを明示的に発動させるには、関数 `cbggcsweep' を用いる。

    + +
    +
    void cbggcsweep(void);
    +
    この関数を呼んだ後はグローバルガベージコレクタに登録してあったオブジェクトは利用することができなくなることに注意すること。グローバルガベージコレクタは初期化されるので、新しいオブジェクトを入れることができるようになる。
    +
    + +

    仮想メモリの割り当て可能性を調べるには、関数 `cbvmemavail' を用いる。

    + +
    +
    int cbvmemavail(size_t size);
    +
    `size' は新たに割り当て可能であるべき領域のサイズを指定する。戻り値は割り当てが可能であれば真、そうでなければ偽である。
    +
    + +

    配列の各要素を挿入ソートで整列させるには、関数 `cbisort' を用いる。

    + +
    +
    void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。挿入ソートは、ほとんどの要素が既に整列済みの場合にのみ有用である。
    +
    + +

    配列の各要素をシェルソートで整列させるには、関数 `cbssort' を用いる。

    + +
    +
    void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ほとんどの要素が整列済みの場合、シェルソートの方がヒープソートやクイックソートより速いかもしれない。
    +
    + +

    配列の各要素をヒープソートで整列させるには、関数 `cbhsort' を用いる。

    + +
    +
    void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ヒープソートは入力の偏りに対して頑丈であるが、ほとんどの場合でクイックソートの方が速い。
    +
    + +

    配列の各要素をクイックソートで整列させるには、関数 `cbqsort' を用いる。

    + +
    +
    void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。入力の偏りに敏感ではあるが、クイックソートは最速の整列アルゴリズムである。
    +
    + +

    大文字と小文字の違いを無視して二つの文字列を比較するには、関数 `cbstricmp' を用いる。

    + +
    +
    int cbstricmp(const char *astr, const char *bstr);
    +
    `astr' は一方の文字列へのポインタを指定する。`bstr' は他方の文字列へのポインタを指定する。戻り値は前者が大きければ正、後者が大きければ負、両者が等価なら 0 である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。
    +
    + +

    文字列があるキーで始まっているか調べるには、関数 `cbstrfwmatch' を用いる。

    + +
    +
    int cbstrfwmatch(const char *str, const char *key);
    +
    `str' は対象の文字列へのポインタを指定する。`key' は前方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで始まっていれば真、そうでなければ偽である。
    +
    + +

    大文字と小文字の違いを無視しつつ、文字列があるキーで始まっているか調べるには、関数 `cbstrfwimatch' を用いる。

    + +
    +
    int cbstrfwimatch(const char *str, const char *key);
    +
    `str' は対象の文字列へのポインタを指定する。`key' は前方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで始まっていれば真、そうでなければ偽である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。
    +
    + +

    文字列があるキーで終っているか調べるには、関数 `cbstrbwmatch' を用いる。

    + +
    +
    int cbstrbwmatch(const char *str, const char *key);
    +
    `str' は対象の文字列へのポインタを指定する。`key' は後方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで終っていれば真、そうでなければ偽である。
    +
    + +

    大文字と小文字の違いを無視しつつ、文字列があるキーで終っているか調べるには、関数 `cbstrbwimatch' を用いる。

    + +
    +
    int cbstrbwimatch(const char *str, const char *key);
    +
    `str' は対象の文字列へのポインタを指定する。`key' は後方一致のキーの文字列へのポインタを指定する。戻り値は対象の文字列がキーで終っていれば真、そうでなければ偽である。ASCIIコード中のアルファベットの大文字と小文字は区別されない。
    +
    + +

    KMP法を用いて文字列の部分文字列の位置を得るには、関数 `cbstrstrkmp' を用いる。

    + +
    +
    char *cbstrstrkmp(const char *haystack, const char *needle);
    +
    `haystack' は文字列へのポインタを指定する。`needle' は探すべき部分文字列へのポインタを指定する。戻り値は部分文字列の開始を指すポインタか、見つからなければ `NULL' である。大抵の場合、この関数よりコンパイラのビルドインである `strstr' の方が高速である。
    +
    + +

    BM法を用いて文字列の部分文字列の位置を得るには、関数 `cbstrstrbm' を用いる。

    + +
    +
    char *cbstrstrbm(const char *haystack, const char *needle);
    +
    `haystack' は文字列へのポインタを指定する。`needle' は探すべき部分文字列へのポインタを指定する。戻り値は部分文字列の開始を指すポインタか、見つからなければ `NULL' である。大抵の場合、この関数よりコンパイラのビルドインである `strstr' の方が高速である。
    +
    + +

    文字列の全ての文字を大文字に変換するには、関数 `cbstrtoupper' を用いる。

    + +
    +
    char *cbstrtoupper(char *str);
    +
    `str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。
    +
    + +

    文字列の全ての文字を小文字に変換するには、関数 `cbstrtolower' を用いる。

    + +
    +
    char *cbstrtolower(char *str);
    +
    `str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。
    +
    + +

    文字列の先頭と末尾にある空白文字を削除するには、関数 `cbstrtrim' を用いる。

    + +
    +
    char *cbstrtrim(char *str);
    +
    `str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。
    +
    + +

    文字列内の連続する空白を絞って整形するには、関数 `cbstrsqzcpc' を用いる。

    + +
    +
    char *cbstrsqzspc(char *str);
    +
    `str' は変換対象の文字列へのポインタを指定する。戻り値はその文字列へのポインタである。
    +
    + +

    UTF-8の文字列に含まれる文字数を数えるには、関数 `cbstrcountutf' を用いる。

    + +
    +
    int cbstrcountutf(const char *str);
    +
    `str' はUTF-8の文字列へのポインタを指定する。戻り値はその文字列に含まれる文字数である。
    +
    + +

    UTF-8の文字列を指定した文字数で切るには、関数 `cbstrcututf' を用いる。

    + +
    +
    char *cbstrcututf(char *str, int num);
    +
    `str' はUTF-8の文字列へのポインタを指定する。`num' は保持する文字数を指定する。戻り値はその文字列へのポインタである。
    +
    + +

    データハンドルを作成するには、関数 `cbdatumopen' を用いる。

    + +
    +
    CBDATUM *cbdatumopen(const char *ptr, int size);
    +
    `ptr' は初期内容の領域へのポインタを指定するか、`NULL' なら空のデータを作成する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値はデータハンドルである。
    +
    + +

    データを複製するには、関数 `cbdatumdup' を用いる。

    + +
    +
    CBDATUM *cbdatumdup(const CBDATUM *datum);
    +
    `datum' はデータハンドルを指定する。戻り値は新しいデータハンドルである。
    +
    + +

    データハンドルを破棄するには、関数 `cbdatumclose' を用いる。

    + +
    +
    void cbdatumclose(CBDATUM *datum);
    +
    `datum' はデータハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
    +
    + +

    データに別の領域を連結するには、関数 `cbdatumcat' を用いる。

    + +
    +
    void cbdatumcat(CBDATUM *datum, const char *ptr, int size);
    +
    `datum' はデータハンドルを指定する。`ptr' は連結する領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。
    +
    + +

    データの領域へのポインタを得るには、関数 `cbdatumptr' を用いる。

    + +
    +
    const char *cbdatumptr(const CBDATUM *datum);
    +
    `datum' はデータハンドルを指定する。戻り値はデータの領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
    +
    + +

    データの領域のサイズを得るには、関数 `cbdatumsize' を用いる。

    + +
    +
    int cbdatumsize(const CBDATUM *datum);
    +
    `datum' はデータハンドルを指定する。戻り値はデータの領域のサイズである。
    +
    + +

    データの領域のサイズを変更するには、関数 `cbdatumsetsize' を用いる。

    + +
    +
    void cbdatumsetsize(CBDATUM *datum, int size);
    +
    `datum' はデータハンドルを指定する。`size' は領域の新しいサイズを指定する。新しいサイズが既存のサイズより大きい場合、余った領域は終端文字で埋められる。
    +
    + +

    データに書式出力を行なうには、関数 `cbdatumprintf' を用いる。

    + +
    +
    void cbdatumprintf(CBDATUM *datum, const char *format, ...);
    +
    `format' はprintf風の書式文字列を指定する。変換文字 `%' を `s'、`d'、`o'、`u'、`x'、`X'、`c'、`e'、`E'、`f'、`g'、`G'、`@'、`?'、`:'、`%' と併せて利用することができる。`@' は `s' と同様に機能するが、XMLのメタ文字をエスケープする。`?' は `s' と同様に機能するが、URLのメタ文字をエスケープする。`:' は `s' と同様に機能するが、UTF-8としてのMIMEエンコーディングを施す。それ以外の変換文字は元来のものと同様に機能する。
    +
    + +

    データを確保された領域に変換するには、関数 `cbdatumtomalloc' を用いる。

    + +
    +
    char *cbdatumtomalloc(CBDATUM *datum, int *sp);
    +
    `datum' はデータハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値はデータの領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。元のデータの領域は解放されるので、それを再び解放してはならない。
    +
    + +

    リストハンドルを作成するには、関数 `cblistopen' を用いる。

    + +
    +
    CBLIST *cblistopen(void);
    +
    戻り値はリストハンドルである。
    +
    + +

    リストを複製するには、関数 `cblistdup' を用いる。

    + +
    +
    CBLIST *cblistdup(const CBLIST *list);
    +
    `list' はリストハンドルを指定する。戻り値は新しいリストハンドルである。
    +
    + +

    リストハンドルを破棄するには、関数 `cblistclose' を用いる。

    + +
    +
    void cblistclose(CBLIST *list);
    +
    `list' はリストハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
    +
    + +

    リストに格納された要素数を得るには、関数 `cblistnum' を用いる。

    + +
    +
    int cblistnum(const CBLIST *list);
    +
    `list' はリストハンドルを指定する。戻り値はリストに格納された要素数である。
    +
    + +

    リスト内のある要素の領域へのポインタを得るには、関数 `cblistval' を用いる。

    + +
    +
    const char *cblistval(const CBLIST *list, int index, int *sp);
    +
    `list' はリストハンドルを指定する。`index' は取り出す要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。`index' が要素数以上ならば、戻り値は `NULL' である。
    +
    + +

    要素をリストの末尾に加えるには、関数 `cblistpush' を用いる。

    + +
    +
    void cblistpush(CBLIST *list, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
    +
    + +

    リストの末尾の要素を削除するには、関数 `cblistpop' を用いる。

    + +
    +
    char *cblistpop(CBLIST *list, int *sp);
    +
    `list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。
    +
    + +

    要素をリストの先頭に加えるには、関数 `cblistunshift' を用いる。

    + +
    +
    void cblistunshift(CBLIST *list, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
    +
    + +

    リストの先頭の要素を削除するには、関数 `cblistshift' を用いる。

    + +
    +
    char *cblistshift(CBLIST *list, int *sp);
    +
    `list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。
    +
    + +

    リスト内の指定した位置に要素を加えるには、関数 `cblistinsert' を用いる。

    + +
    +
    void cblistinsert(CBLIST *list, int index, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。`index' は追加する要素のインデックスを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
    +
    + +

    リスト内の指定した位置の要素を削除するには、関数 `cblistremove' を用いる。

    + +
    +
    char *cblistremove(CBLIST *list, int index, int *sp);
    +
    `list' はリストハンドルを指定する。`index' は削除する要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。`index' が要素数以上ならば、要素は削除されず、戻り値は `NULL' である。
    +
    + +

    リスト内の指定した位置の要素を上書きするには、関数 `cblistover' を用いる。

    + +
    +
    void cblistover(CBLIST *list, int index, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。`index' は削除する要素のインデックスを指定する。`ptr' は新しい内容の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。`index' が要素数以上ならば、この関数は何もしない。
    +
    + +

    リストの要素を辞書順で整列させるには、関数 `cblistsort' を用いる。

    + +
    +
    void cblistsort(CBLIST *list);
    +
    `list' はリストハンドルを指定する。整列にはクイックソートが用いられる。
    +
    + +

    リストの要素を線形探索を使って検索するには、関数 `cblistlsearch' を用いる。

    + +
    +
    int cblistlsearch(const CBLIST *list, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合、前者が返される。
    +
    + +

    リストの要素を二分探索を使って検索するには、関数 `cblistbsearch' を用いる。

    + +
    +
    int cblistbsearch(const CBLIST *list, const char *ptr, int size);
    +
    `list' はリストハンドルを指定する。リストは辞書順にソートされている必要がある。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合にどちらが返るかは未定義である。
    +
    + +

    リストを直列化してバイト配列にするには、関数 `cblistdump' を用いる。

    + +
    +
    char *cblistdump(const CBLIST *list, int *sp);
    +
    `list' はリストハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    直列化されたリストを復元するには、関数 `cblistload' を用いる。

    + +
    +
    CBLIST *cblistload(const char *ptr, int size);
    +
    `ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいリストハンドルである。
    +
    + +

    マップハンドルを作成するには、関数 `cbmapopen' を用いる。

    + +
    +
    CBMAP *cbmapopen(void);
    +
    戻り値はマップハンドルである。
    +
    + +

    マップを複製するには、関数 `cbmapdup' を用いる。

    + +
    +
    CBMAP *cbmapdup(CBMAP *map);
    +
    `map' はマップハンドルを指定する。戻り値は新しいマップハンドルである。コピー元のマップのイテレータは初期化される。
    +
    + +

    マップハンドルを破棄するには、関数 `cbmapclose' を用いる。

    + +
    +
    void cbmapclose(CBMAP *map);
    +
    `map' はマップハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
    +
    + +

    マップにレコードを追加するには、関数 `cbmapput' を用いる。

    + +
    +
    int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over);
    +
    `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`over' は重複したレコードを上書きするか否かを指定する。`over' が偽でキーが重複した場合は戻り値は偽であるが、そうでない場合は真である。
    +
    + +

    既存のレコードの値の末尾に値を連結するには、関数 `cbmapputcat' を用いる。

    + +
    +
    void cbmapputcat(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz);
    +
    `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。該当のレコードが存在しない場合は新しいレコードが作られる。
    +
    + +

    マップのレコードを削除するには、関数 `cbmapout' を用いる。

    + +
    +
    int cbmapout(CBMAP *map, const char *kbuf, int ksiz);
    +
    `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。
    +
    + +

    マップのレコードを取得するには、関数 `cbmapget' を用いる。

    + +
    +
    const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp);
    +
    `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、該当のレコードがない場合は `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
    +
    + +

    レコードをマップの端に移動させるには、関数 `cbmapmove' を用いる。

    + +
    +
    int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head);
    +
    `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`head' は移動先を指定し、真なら先頭、偽なら末尾となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。
    +
    + +

    マップのイテレータを初期化するには、関数 `cbmapiterinit' を用いる。

    + +
    +
    void cbmapiterinit(CBMAP *map);
    +
    `map' はマップハンドルを指定する。イテレータは、マップに格納された全てのレコードを参照するために用いられる。
    +
    + +

    マップのイテレータから次のレコードのキーを取り出すには、関数 `cbmapiternext' を用いる。

    + +
    +
    const char *cbmapiternext(CBMAP *map, int *sp);
    +
    `map' はマップハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。取り出す順番は格納した際の順番に一致することが保証されている。
    +
    + +

    マップのイテレータから取り出したキーに対応する値を取り出すには、関数 `cbmapiterval' を用いる。

    + +
    +
    const char *cbmapiterval(const char *kbuf, int *sp);
    +
    `kbuf' はイテレータから取り出したキーの領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は値を格納した領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
    +
    + +

    マップのレコード数を得るには、関数 `cbmaprnum' を用いる。

    + +
    +
    int cbmaprnum(const CBMAP *map);
    +
    `map' はマップハンドルを指定する。戻り値はデータベースのレコード数である。
    +
    + +

    マップに含まれる全てのキーを含むリストを得るには、関数 `cbmapkeys' を用いる。

    + +
    +
    CBLIST *cbmapkeys(CBMAP *map);
    +
    `map' はマップハンドルを指定する。戻り値はマップに含まれる全てのキーを含むリストハンドルである。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    マップに含まれる全ての値を含むリストを得るには、関数 `cbmapvals' を用いる。

    + +
    +
    CBLIST *cbmapvals(CBMAP *map);
    +
    `map' はマップハンドルを指定する。戻り値はマップに含まれる全ての値を含むリストハンドルである。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    マップを直列化してバイト配列にするには、関数 `cbmapdump' を用いる。

    + +
    +
    char *cbmapdump(const CBMAP *map, int *sp);
    +
    `map' はマップハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    直列化されたマップを復元するには、関数 `cbmapload' を用いる。

    + +
    +
    CBMAP *cbmapload(const char *ptr, int size);
    +
    `ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいマップハンドルである。
    +
    + +

    直列化したマップからひとつのレコードを抽出するには、関数 `cbmaploadone' を用いる。

    + +
    +
    char *cbmaploadone(const char *ptr, int size, const char *kbuf, int ksiz, int *sp);
    +
    `ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、該当のレコードがない場合は `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
    +
    + +

    ヒープハンドルを作成するには、関数 `cbheapopen' を用いる。

    + +
    +
    CBHEAP *cbheapopen(int size, int max, int(*compar)(const void *, const void *));
    +
    `size' は各レコードの領域のサイズを指定する。`max' はヒープに格納するレコードの最大数を指定する。`compar' は比較関数を指定する。二つの引数はレコードへのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。戻り値はヒープハンドル。
    +
    + +

    ヒープハンドルを複製するには、関数 `cbheapdup' を用いる。

    + +
    +
    CBHEAP *cbheapdup(CBHEAP *heap);
    +
    `heap' はヒープハンドルを指定する。戻り値は新しいヒープハンドルである。
    +
    + +

    ヒープハンドルを破棄するには、関数 `cbheapclose' を用いる。

    + +
    +
    void cbheapclose(CBHEAP *heap);
    +
    `heap' はヒープハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
    +
    + +

    ヒープに格納されたレコード数を得るには、関数 `cbheapnum' を用いる。

    + +
    +
    int cbheapnum(CBHEAP *heap);
    +
    `heap' はヒープハンドルを指定する。戻り値はリストに格納されたレコード数である。
    +
    + +

    ヒープにレコードを挿入するには、関数 `cbheapinsert' を用いる。

    + +
    +
    int cbheapinsert(CBHEAP *heap, const void *ptr);
    +
    `heap' はヒープハンドルを指定する。`ptr' は追加するレコードの領域へのポインタを指定する。戻り値はレコードが追加されれば真であり、そうでなければ偽である。新しいレコードの値が既存のレコードの最大値より大きければ、新しいレコードは追加されない。新しいレコードが追加されてレコード数が最大数を越えた場合、既存のレコードの最大値のものが削除される。
    +
    + +

    ヒープ内のあるレコードの領域へのポインタを取得するには、関数 `cbheapget' を用いる。

    + +
    +
    void *cbheapval(CBHEAP *heap, int index);
    +
    `heap' はヒープハンドルを指定する。`index' は取り出すレコードのインデックスを指定する。戻り値は該当レコードの領域へのポインタである。`index' が要素数以上ならば、戻り値は `NULL' である。レコードは内部的には比較関数の負値に基づいて組織化されていることに注意すべきである。
    +
    + +

    ヒープを確保された領域に変換するには、関数 `cbheaptomalloc' を用いる。

    + +
    +
    void *cbheaptomalloc(CBHEAP *heap, int *np);
    +
    `heap' はヒープハンドルを指定する。`np' が `NULL' でなければ、その参照先に抽出した領域のレコード数を格納する。戻り値はヒープの領域へのポインタである。レコードはソート済みになる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。元のデータの領域は解放されるので、それを再び解放してはならない。
    +
    + +

    書式に基づいた文字列をメモリ上で確保するには、関数 `cbsprintf' を用いる。

    + +
    +
    char *cbsprintf(const char *format, ...);
    +
    `format' はprintf風の書式文字列を指定する。変換文字 `%' をフラグ文字 `d'、`o'、`u'、`x'、`X'、`e'、`E'、`f'、`g'、`G'、`c'、`s' および `%' を伴わせて使用することができる。フィールドの幅と精度の指示子を変換文字とフラグ文字の間に置くことができる。その指示子は10進数字、`.'、`+'、`-' およびスペース文字からなる。その他の引数は書式文字列によって利用される。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    文字列中のパターンを置換するには、関数 `cbreplace' を用いる。

    + +
    +
    char *cbreplace(const char *str, CBMAP *pairs);
    +
    `str' は置換前の文字列を指定する。`pairs' は置換のペアからなるマップのハンドルを指定する。各ペアのキーは置換前のパターンを指定し、値は置換後のパターンを指定する。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    一連のデータを分割してリストを作成するには、関数 `cbsplit' を用いる。

    + +
    +
    CBLIST *cbsplit(const char *ptr, int size, const char *delim);
    +
    `ptr' は内容の領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`delim' は区切り文字を含む文字列を指定するか、`NULL' なら終端文字を区切り文字とする。戻り値はリストハンドルである。区切り文字が連続している場合でも、その間に空の要素があるとみなす。戻り値のハンドルは、関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    ファイルの全データを読み込むには、関数 `cbreadfile' を用いる。

    + +
    +
    char *cbreadfile(const char *name, int *sp);
    +
    `name' はファイルの名前を指定するが、`NULL' なら標準入力を読み込む。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら読み込んだデータを格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    一連のデータをファイルに書き込むには、関数 `cbwritefile' を用いる。

    + +
    +
    int cbwritefile(const char *name, const char *ptr, int size);
    +
    `name' はファイル名を指定するが、`NULL' なら標準出力に書き出される。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は正常なら真であり、該当のエラーなら偽である。ファイルが存在する場合には上書きされ、そうでない場合は新しいファイルが生成される。
    +
    + +

    ファイルの各行を読み込むには、関数 `cbreadlines' を用いる。

    + +
    +
    CBLIST *cbreadlines(const char *name);
    +
    `name' はファイルの名前を指定するが、`NULL' なら標準入力を読み込む。成功すれば戻り値は各行のデータを保持するリストのハンドルであり、失敗なら `NULL' である。改行文字は削除される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    ディレクトリに含まれるファイルの名前のリストを得るには、関数 `cbdirlist' を用いる。

    + +
    +
    CBLIST *cbdirlist(const char *name);
    +
    `name' はディレクトリの名前を指定する。成功すれば戻り値は各ファイルの名前を保持するリストのハンドルであり、失敗なら `NULL' である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    ファイルやディレクトリの状態を得るには、関数 `cbfilestat' を用いる。

    + +
    +
    int cbfilestat(const char *name, int *isdirp, int *sizep, int *mtimep);
    +
    `name' はファイルやディレクトリの名前を指定する。`dirp' が `NULL' でなければ、その参照先にファイルがディレクトリか否かを格納する。`sizep' が `NULL' でなければ、その参照先にファイルのサイズを格納する。`mtimep' が `NULL' でなければ、その参照先にファイルの最終更新時刻を格納する。戻り値は正常なら真であり、エラーなら偽である。ファイルが存在しない場合やパーミッションがない場合も偽を返す。
    +
    + +

    ファイルかディレクトリとその内容を削除するには、関数 `cbremove' を用いる。

    + +
    +
    int cbremove(const char *name);
    +
    `name' はファイルやディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。ファイルが存在しない場合やパーミッションがない場合も偽を返す。
    +
    + +

    URLを構成要素に分解するには、関数 `cburlbreak' を用いる。

    + +
    +
    CBMAP *cburlbreak(const char *str);
    +
    `str' はURLの文字列へのポインタを指定する。戻り値はマップハンドルである。マップの各キーは要素名である。キー "self" はURLそれ自体を指示する。キー "scheme" はURLのスキームを指示する。キー "host" はサーバのホスト名を指示する。キー "port" はサーバのポート番号を指示する。キー "authority" は認証情報を指示する。キー "path" はリソースのパスを指示する。キー "file" はディレクトリ部分を除いたファイル名を指示する。キー "query" はクエリ文字列を指示する。キー "fragment" はフラグメント文字列を指示する。サポートされるスキームはHTTPとHTTPSとFTPとFILEである。絶対URLにも相対URLにも対応する。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。
    +
    + +

    相対URLを絶対URLを用いて解決するには、関数 `cburlresolve' を用いる。

    + +
    +
    char *cburlresolve(const char *base, const char *target);
    +
    `base' はベースロケーションの絶対URLを指定する。`target' は解決すべきURLを指定する。戻り値は解決されたURLである。ターゲットURLが相対URLの場合、ベースロケーションからの相対位置のURLを返す。ターゲットURLが絶対URLの場合、ターゲットURLのコピーを返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    一連のオブジェクトをURLエンコーディングで符号化するには、関数 `cburlencode' を用いる。

    + +
    +
    char *cburlencode(const char *ptr, int size);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    URLエンコーディングで符号化された文字列を復元するには、関数 `cburldecode' を用いる。

    + +
    +
    char *cburldecode(const char *str, int *sp);
    +
    `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    一連のオブジェクトをBase64エンコーディングで符号化するには、関数 `cbbaseencode' を用いる。

    + +
    +
    char *cbbaseencode(const char *ptr, int size);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    Base64エンコーディングで符号化された文字列を復元するには、関数 `cbbasedecode' を用いる。

    + +
    +
    char *cbbasedecode(const char *str, int *sp);
    +
    `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    一連のオブジェクトをquoted-printableエンコーディングで符号化するには、関数 `cbquoteencode' を用いる。

    + +
    +
    char *cbquoteencode(const char *ptr, int size);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    quoted-printableエンコーディングで符号化された文字列を復元するには、関数 `cbquotedecode' を用いる。

    + +
    +
    char *cbquotedecode(const char *str, int *sp);
    +
    `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    MIMEの文字列をヘッダとボディに分割するには、関数 `cbmimebreak' を用いる。

    + +
    +
    char *cbmimebreak(const char *ptr, int size, CBMAP *attrs, int *sp);
    +
    `ptr' はMIMEのデータの領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`attrs' は属性を保存するためのマップハンドルを指定するが、`NULL' の場合には利用されない。マップの各キーは小文字に正規化された属性名である。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値はボディの文字列である。コンテントタイプが指定されている場合、マップのキー "TYPE" はそのタイプを指示する。文字コードが指定されている場合、キー "CHARSET" はそのコード名を指示する。マルチパートの区切り文字列が指定されている場合、キー "BOUNDARY" はその文字列を指示する。コンテントディスポジションが指定されている場合、キー "DISPOSITION" はその方針を指示する。ファイル名が指定されている場合、キー "FILENAME" はその名前を指示する。属性名が指定されている場合、キー "NAME" はその名前を指示する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    MIMEのマルチパートデータの文字列を各パートに分割するには、関数 `cbmimeparts' を用いる。

    + +
    +
    CBLIST *cbmimeparts(const char *ptr, int size, const char *boundary);
    +
    `ptr' はMIMEのデータの領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`boundary' は区切り文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素はパートの文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    文字列をMIMEエンコーディングで符号化するには、関数 `cbmimeencode' を用いる。

    + +
    +
    char *cbmimeencode(const char *str, const char *encname, int base);
    +
    `str' は文字列へのポインタを指定する。`encname' は文字コード名の文字列を指定する。`base' はBase64エンコードを使うか否かを指定する。偽の場合はquoted-printableが用いられる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    MIMEエンコーディングで符号化された文字列を復元するには、関数 `cbmimedecode' を用いる。

    + +
    +
    char *cbmimedecode(const char *str, char *enp);
    +
    `str' は符号化された文字列へのポインタを指定する。`enp' は文字コード名を書き込むための領域へのポインタを指定するが、`NULL' の場合は利用されない。バッファのサイズは32バイト以上でなければならない。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    CSVの文字列を行に分割するには、関数 `cbcsvrows' を用いる。

    + +
    +
    CBLIST *cbcsvrows(const char *str);
    +
    `str' はCSVの文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素は行の文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。入力文字列の文字コードはUS-ASCII、UTF-8、ISO-8859-*、EUC-*、Shift_JISのどれかである必要がある。MS-Excelと互換して、これらCSV用関数群は、ダブルクォートで囲んでコンマなどのメタ文字を含めたセルを扱うことができる。
    +
    + +

    CSVの行の文字列をセルに分割するには、関数 `cbcsvcells' を用いる。

    + +
    +
    CBLIST *cbcsvcells(const char *str);
    +
    `str' はCSVの行の文字列へのポインタを指定する。戻り値はリストハンドルである。リストの各要素はセル内容をアンエスケープした文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    CSVのメタ文字をエスケープした文字列を得るには、関数 `cbcsvescape' を用いる。

    + +
    +
    char *cbcsvescape(const char *str);
    +
    `str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を無効化した結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    XMLの実体参照をアンエスケープした文字列を得るには、関数 `cbcsvunescape' を用いる。

    + +
    +
    char *cbcsvunescape(const char *str);
    +
    `str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を伴った結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    XMLの文字列をタグとテキストセクションに分割するには、関数 `cbxmlbreak' を用いる。

    + +
    +
    CBLIST *cbxmlbreak(const char *str, int cr);
    +
    `str' はXMLの文字列へのポインタを指定する。`cr' はコメントを削除するか否かを指定する。戻り値はリストハンドルである。リストの各要素はタグかテキストセクションの文字列である。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。入力文字列の文字コードはUS-ASCII、UTF-8、ISO-8859-*、EUC-*、Shift_JISのどれかである必要がある。これらXML用関数群は妥当性検証を行うXMLパーザではないので、HTMLやSGMLも扱うことができる。
    +
    + +

    XMLのタグの属性のマップを得るには、関数 `cbxmlattrs' を用いる。

    + +
    +
    CBMAP *cbxmlattrs(const char *str);
    +
    `str' はタグの文字列へのポインタを指定する。戻り値はマップハンドルである。マップの各キーは属性の名前である。各値はアンエスケープされる。空文字列をキーにするとタグの名前を取り出すことができる。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。
    +
    + +

    XMLのメタ文字をエスケープした文字列を得るには、関数 `cbxmlescape' を用いる。

    + +
    +
    char *cbxmlescape(const char *str);
    +
    `str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を無効化した結果の文字列へのポインタである。この関数は `&'、 `<'、`>'、`"' のみを変換する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    XMLの実体参照をアンエスケープした文字列を得るには、関数 `cbxmlunescape' を用いる。

    + +
    +
    char *cbxmlunescape(const char *str);
    +
    `str' は対象の文字列へのポインタを指定する。戻り値はメタ文字を伴った結果の文字列へのポインタである。この関数は `&amp;'、`&lt;'、`&gt;'、`&quot;' のみを復元する。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    ZLIBを用いて一連のオブジェクトを圧縮するには、関数 `cbdeflate' を用いる。

    + +
    +
    char *cbdeflate(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    ZLIBを用いて圧縮されたオブジェクトを伸長するには、関数 `cbinflate' を用いる。

    + +
    +
    char *cbinflate(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    GZIPを用いて一連のオブジェクトを圧縮するには、関数 `cbgzencode' を用いる。

    + +
    +
    char *cbgzencode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    GZIPを用いて圧縮されたオブジェクトを伸長するには、関数 `cbgzdecode' を用いる。

    + +
    +
    char *cbgzdecode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    一連のオブジェクトのCRC32チェックサムを得るには、関数 `cbgetcrc' を用いる。

    + +
    +
    unsigned int cbgetcrc(const char *ptr, int size);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値はオブジェクトのCRC32チェックサムである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    LZOを用いて一連のオブジェクトを圧縮するには、関数 `cblzoencode' を用いる。

    + +
    +
    char *cblzoencode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがLZOを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    LZOを用いて圧縮されたオブジェクトを伸長するには、関数 `cblzodecode' を用いる。

    + +
    +
    char *cblzodecode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがLZOを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    BZIP2を用いて一連のオブジェクトを圧縮するには、関数 `cbbzencode' を用いる。

    + +
    +
    char *cbbzencode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがBZIP2を有効にしてビルドされた場合のみ利用できる。
    +
    + +

    BZIP2を用いて圧縮されたオブジェクトを伸長するには、関数 `cbbzdecode' を用いる。

    + +
    +
    char *cbbzdecode(const char *ptr, int size, int *sp);
    +
    `ptr' は領域へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがBZIP2を有効にしてビルドされた場合のみ利用できる。
    +
    + +

    文字列の文字コードを変換するには、関数 `cbiconv' を用いる。

    + +
    +
    char *cbiconv(const char *ptr, int size, const char *icode, const char *ocode, int *sp, int *mp);
    +
    `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`icode' は入力文字列の文字コードの名前を指定する。`outcode' は出力文字列の文字コードの名前を指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。`mp' が `NULL' でなければ、その参照先に変換に失敗した文字数を格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがICONVを有効にしてビルドされた場合のみ利用できる。
    +
    + +

    文字列の文字コードを自動判定するには、関数 `cbencname' を用いる。

    + +
    +
    const char *cbencname(const char *str, int size);
    +
    `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を有効にしてビルドされた場合のみ利用できる。
    +
    + +

    ローカル時間の秒単位の時差を得るには、関数 `cbjetlag' を用いる。

    + +
    +
    int cbjetlag(void);
    +
    戻り値はローカル時間の秒単位の時差である。
    +
    + +

    ある時間のグレゴリオ暦を得るには、関数 `cbcalendar' を用いる。

    + +
    +
    void cbcalendar(time_t t, int jl, int *yearp, int *monp, int *dayp, int *hourp, int *minp, int *secp);
    +
    `t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。`yearp' が `NULL' でなければ、その参照先に年を格納する。`monp' が `NULL' でなければ、その参照先に月を格納する。1は1月を意味し、12は12月を意味する。`dayp' が `NULL' でなければ、その参照先に日を格納する。`hourp' が `NULL' でなければ、その参照先に時を格納する。`minp' が `NULL' でなければ、その参照先に分を格納する。`secp' が `NULL' でなければ、その参照先に秒を格納する。
    +
    + +

    ある日付の曜日を得るには、関数 `cbdayofweek' を用いる。

    + +
    +
    int cbdayofweek(int year, int mon, int day);
    +
    `year' は日付の年を指定する。`mon' は日付の月を指定する。`day' は日付の日を指定する。戻り値は曜日の値である。0は日曜を意味し、6は土曜を意味する。
    +
    + +

    ある日付をW3CDTFの書式で表した文字列を得るには、関数 `cbdatestrwww' を用いる。

    + +
    +
    char *cbdatestrwww(time_t t, int jl);
    +
    `t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。戻り値はW3CDTFの書式(YYYY-MM-DDThh:mm:ddTZD)の文字列である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    ある日付をRFC 1123の書式で表した文字列を得るには、関数 `cbdatestrhttp' を用いる。

    + +
    +
    char *cbdatestrhttp(time_t t, int jl);
    +
    `t' は対象の時間を指定するが、負数なら現在時間が使われる。`jl' はある場所の時差を秒単位で指定する。戻り値はRFC 1123の書式(Wdy, DD-Mon-YYYY hh:mm:dd TZD)の文字列である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    10進数か16進数かW3CDTFかRFC 822(1123)の書式で表した文字列から時間の値を得るには、関数 `cbstrmktime' を用いる。

    + +
    +
    time_t cbstrmktime(const char *str);
    +
    `str' は10進数か16進数かW3CDTFかRFC 822(1123)の書式で表した文字列を指定する。戻り値はその時間の値か、書式が不正な場合は -1 である。10進数に "s" を後置すると秒単位を意味し、"m" を後置すると分単位を意味し、"h" を後置すると時単位を意味し、"d" を後置すると日単位を意味する。
    +
    + +

    ユーザとシステムのプロセス時間を得るには、関数 `cbproctime' を用いる。

    + +
    +
    void cbproctime(double *usrp, double *sysp);
    +
    `usrp' が `NULL' でなければ、その参照先にユーザ時間を格納する。時間の単位は秒である。`sysp' が `NULL' でなければ、その参照先にシステム時間を格納する。時間の単位は秒である。
    +
    + +

    標準入出力がバイナリモードであることを保証するには、関数 `cbstdiobin' を用いる。

    + +
    +
    void cbstdiobin(void);
    +
    この関数はDOS的なファイルシステムの上のアプリケーションで有用である。
    +
    + +

    サンプルコード

    + +

    以下のサンプルコードは典型的な利用例である。

    + +
    #include <cabin.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +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 < 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 < cblistnum(list); i++){
    +    printf("%s\n", cblistval(list, i, NULL));
    +  }
    +
    +  return 0;
    +}
    +
    + +

    注記

    + +

    Cabinを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    スレッド間で同時に同じハンドルにアクセスしない限りは、`cbglobalgc' を除いたCabinの各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +
    + +

    Cabin用コマンド

    + +

    Cabinに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `cbtest' はCabinの機能テストや性能テストに用いるツールである。`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`rnum' はレコード数を指定する。

    + +
    +
    cbtest sort [-d] rnum
    +
    ソートアルゴリズムのテストを行う。
    +
    cbtest strstr [-d] rnum
    +
    文字列探索アルゴリズムのテストを行う。
    +
    cbtest list [-d] rnum
    +
    リストの書き込みテストを行う。
    +
    cbtest map [-d] rnum
    +
    マップの書き込みテストを行う。
    +
    cbtest wicked rnum
    +
    リストとマップの各種更新操作を無作為に選択して実行する。
    +
    cbtest misc
    +
    雑多なルーチンのテストを実行する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -d : 結果のデータを読み込んで表示する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    コマンド `cbcodec' はCabinが提供するエンコードおよびデコードの機能を利用するツールである。以下の書式で用いる。`file' は入力ファイルを指定するが、省略されれば標準入力を読み込む。

    + +
    +
    cbcodec url [-d] [-br] [-rs base target] [-l] [-e expr] [file]
    +
    URLエンコードとそのデコードを行う。
    +
    cbcodec base [-d] [-l] [-c num] [-e expr] [file]
    +
    Base64エンコードとそのデコードを行う。
    +
    cbcodec quote [-d] [-l] [-c num] [-e expr] [file]
    +
    quoted-printableエンコードとそのデコードを行う。
    +
    cbcodec mime [-d] [-hd] [-bd] [-part num] [-l] [-ec code] [-qp] [-dc] [-e expr] [file]
    +
    MIMEエンコードとそのデコードを行う。
    +
    cbcodec csv [-d] [-t] [-l] [-e expr] [-html] [file]
    +
    CSVの処理を行う。デフォルトではメタ文字のエスケープを行う。
    +
    cbcodec xml [-d] [-p] [-l] [-e expr] [-tsv] [file]
    +
    XMLの処理を行う。デフォルトではメタ文字のエスケープを行う。
    +
    cbcodec zlib [-d] [-gz] [-crc] [file]
    +
    ZLIBの圧縮とその伸長を行う。ZLIBを有効化してQDBMをビルドした場合にのみ利用可能である。
    +
    cbcodec lzo [-d] [file]
    +
    LZOの圧縮とその伸長を行う。LZOを有効化してQDBMをビルドした場合にのみ利用可能である。
    +
    cbcodec bzip [-d] [file]
    +
    BZIP2の圧縮とその伸長を行う。BZIP2を有効化してQDBMをビルドした場合にのみ利用可能である。
    +
    cbcodec iconv [-ic code] [-oc code] [-ol ltype] [-cn] [-um] [-wc] [file]
    +
    ICONVによる文字コードの変換を行う。ICONVを有効化してQDBMをビルドした場合にのみ利用可能である。
    +
    cbcodec date [-wf] [-rf] [-utc] [str]
    +
    `str' で指定した日付の文字列の書式を変換する。デフォルトでは、UNIX時間を出力する。`str' が省略されると、現在日時を扱う。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -d : エンコード(エスケープ)ではなく、デコード(アンエスケープ)を行う。
    • +
    • -br : URLを構成要素に分解する。
    • +
    • -rs : 相対URLを解決する。
    • +
    • -l : 出力の末尾に改行文字を加える。
    • +
    • -e expr : 入力データを直接指定する。
    • +
    • -c num : エンコードの際の桁数制限を指定する。
    • +
    • -hd : MIMEの構文解析を行い、ヘッダをTSV形式で抽出する。
    • +
    • -bd : MIMEの構文解析を行い、ボディを抽出する。
    • +
    • -part num : MIMEの構文解析を行い、特定のパートを抽出する。
    • +
    • -ec code : 入力の文字コードを指定する。デフォルトはUTF-8である。
    • +
    • -qp : quoted-printableエンコードを用いる。デフォルトはBase64である。
    • +
    • -dc : デコード結果の文字列でなく、文字コード名を出力する。
    • +
    • -t : CSVの構造を解析する。TSVに変換して表示する。セル内のタブと改行は削除される。
    • +
    • -html : CSVの構造を解析する。HTMLに変換して出力する。
    • +
    • -p : XMLの構文解析を行う。タグとテキストセクションをヘッダで分けて表示する。
    • +
    • -tsv : XMLの構文解析を行う。結果をTSV形式で出力する。テキストセクションのタブと改行はURLエンコードされる。
    • +
    • -gz : GZIP形式を用いる。
    • +
    • -crc : CRC32のチェックサムをビッグエンディアンの16進数で出力する。
    • +
    • -ic code : 入力の文字コードを指定する。デフォルトだと自動判定する。
    • +
    • -oc code : 出力の文字コードを指定する。デフォルトだとUTF-8になる。
    • +
    • -ol ltype : 改行文字を変換する。`unix'(LF)、`dos'(CRLF)、`mac'(CR) のどれかを指定する。
    • +
    • -cn : 入力の文字コードを自動判定し、その名前を表示する。
    • +
    • -wc : 入力の文字コードをUTF-8と仮定し、その文字数を表示する。
    • +
    • -um : UCS-2の文字と、C言語の文字列表現でのUTF-16BEとUTF-8の対応表を出力する。
    • +
    • -wf : W3CDTFの書式で出力する。
    • +
    • -rf : RFC 1123の書式で出力する。
    • +
    • -utc : 協定世界時を出力する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +
    + +

    Villa: 上級API

    + +

    概要

    + +

    VillaはQDBMの上級APIであり、B+木のデータベースを管理するルーチンを提供する。各レコードはユーザが指定した順序で整列されて格納される。ハッシュデータベースではレコードの検索はキーの完全一致によるしかなかった。しかし、Villaを用いると範囲を指定してレコードを検索することができる。各レコードを順番に参照するにはカーソルを用いる。データベースにはキーが重複する複数のレコードを格納することができる。また、トランザクション機構によってデータベースの操作を一括して反映させたり破棄したりすることができる。

    + +

    VillaはDepotおよびCabinを基盤として実装される。VillaのデータベースファイルはDepotのデータベースファイルそのものである。レコードの検索や格納の処理速度はDepotより遅いが、データベースファイルのサイズはより小さい。

    + +

    Villaを使うためには、`depot.h' と `cabin.h' と `villa.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <depot.h>
    +
    #include <cabin.h>
    +
    #include <villa.h>
    +
    #include <stdlib.h>
    +
    + +

    Villaでデータベースを扱う際には、`VILLA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `vlopen' で開き、関数 `vlclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `vlclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。カーソルを使う前には `vlcurfirst' か `vlcurlast' か `vlcurjump' のどれかで初期化する必要がある。`vlcurput' と `vlcurout' 以外の関数でレコードの更新や削除をした後にもカーソルを初期化する必要がある。

    + +

    VillaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。

    + +

    API

    + +

    レコードの順番を指定するためには、比較関数を定義する。比較関数には以下の型を用いる。

    + +
    +
    typedef int(*VLCFUNC)(const char *aptr, int asiz, const char *bptr, int bsiz);
    +
    `aptr' は一方のキーのデータ領域へのポインタを指定する。`asiz' はそのキーのデータ領域のサイズを指定する。`bptr' は他方のキーのデータ領域へのポインタを指定する。`bsiz' はそのキーのデータ領域のサイズを指定する。戻り値は前者が大きければ正、後者が大きければ負、両者が等価なら 0 である。
    +
    + +

    データベースのハンドルを作成するには、関数 `vlopen' を用いる。

    + +
    +
    VILLA *vlopen(const char *name, int omode, VLCFUNC cmp);
    +
    `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' を使う場合、アプリケーションが排他制御の責任を負う。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `vlclose' を用いる。

    + +
    +
    int vlclose(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。トランザクションが有効でコミットされていない場合、それは破棄される。
    +
    + +

    レコードを追加するには、関数 `vlput' を用いる。

    + +
    +
    int vlput(VILLA *villa, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' はキーが既存レコードと重複した際の制御を指定する。`VL_DOVER' は既存のレコードの値を上書きし、`VL_DKEEP' は既存のレコードを残してエラーを返し、`VL_DCAT' は指定された値を既存の値の末尾に加え、`VL_DDUP' はキーの重複を許して指定された値を最後の値として加え、`VL_DDUPR' はキーの重複を許して指定された値を最初の値として加える。戻り値は正常なら真であり、エラーなら偽である。データベースの更新によってカーソルは使用不能になる。
    +
    + +

    レコードを削除するには、関数 `vlout' を用いる。

    + +
    +
    int vlout(VILLA *villa, const char *kbuf, int ksiz);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。重複するレコード群のキーが指定された場合、その最初のものが削除される。データベースの更新によってカーソルは使用不能になる。
    +
    + +

    レコードを取得するには、関数 `vlget' を用いる。

    + +
    +
    char *vlget(VILLA *villa, const char *kbuf, int ksiz, int *sp);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。重複するレコード群のキーが指定された場合、その最初のものが選択される。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    レコードの値のサイズを取得するには、関数 `vlvsiz' を用いる。

    + +
    +
    int vlvsiz(VILLA *villa, const char *kbuf, int ksiz);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであるが、該当がない場合やエラーの場合は -1 である。複数のレコードが該当する場合は、最初のレコードの値のサイズを返す。
    +
    + +

    キーに一致するレコードの数を取得するには、関数 `vlvnum' を用いる。

    + +
    +
    int vlvnum(VILLA *villa, const char *kbuf, int ksiz);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値の数であり、該当がない場合やエラーの場合は 0 である。
    +
    + +

    キーに一致する複数のレコードを追加するには、関数 `vlputlist' を用いる。

    + +
    +
    int vlputlist(VILLA *villa, const char *kbuf, int ksiz, const CBLIST *vals);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vals' は値のリストのハンドルを指定する。リストは空であってはならない。戻り値は正常なら真であり、エラーなら偽である。データベースの更新によってカーソルは使用不能になる。
    +
    + +

    キーに一致する全てのレコードを削除するには、関数 `vloutlist' を用いる。

    + +
    +
    int vloutlist(VILLA *villa, const char *kbuf, int ksiz);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。データベースの更新によってカーソルは使用不能になる。
    +
    + +

    キーに一致する全てのレコードを取得するには、関数 `vlgetlist' を用いる。

    + +
    +
    CBLIST *vlgetlist(VILLA *villa, const char *kbuf, int ksiz);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は一致するレコードの値のリストのハンドルであるか、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    キーに一致する全てのレコードの連結した値を取得するには、関数 `vlgetcat' を用いる。

    + +
    +
    char *vlgetcat(VILLA *villa, const char *kbuf, int ksiz, int *sp);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら連結した値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    カーソルを最初のレコードに移動させるには、関数 `vlcurfirst' を用いる。

    + +
    +
    int vlcurfirst(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。
    +
    + +

    カーソルを最後のレコードに移動させるには、関数 `vlcurlast' を用いる。

    + +
    +
    int vlcurlast(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。
    +
    + +

    カーソルを前のレコードに移動させるには、関数 `vlcurprev' を用いる。

    + +
    +
    int vlcurprev(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。前のレコードがない場合も偽を返す。
    +
    + +

    カーソルを次のレコードに移動させるには、関数 `vlcurnext' を用いる。

    + +
    +
    int vlcurnext(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。次のレコードがない場合も偽を返す。
    +
    + +

    カーソルを特定のレコードの前後に移動させるには、関数 `vlcurjump' を用いる。

    + +
    +
    int vlcurjump(VILLA *villa, const char *kbuf, int ksiz, int jmode);
    +
    `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`jmode' は詳細な調整を指定する。`VL_JFORWARD' はキーが同じレコード群の最初のレコードにカーソルが設定され、また完全に一致するレコードがない場合は次の候補にカーソルが設定されることを意味する。`VL_JBACKWORD' はキーが同じレコード群の最後のレコードにカーソルが設定され、また完全に一致するレコードがない場合は前の候補にカーソルが設定されることを意味する。戻り値は正常なら真であり、エラーなら偽である。条件に一致するレコードがない場合も偽を返す。
    +
    + +

    カーソルのあるレコードのキーを取得するには、関数 `vlcurkey' を用いる。

    + +
    +
    char *vlcurkey(VILLA *villa, int *sp);
    +
    `villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    カーソルのあるレコードの値を取得するには、関数 `vlcurkey' を用いる。

    + +
    +
    char *vlcurval(VILLA *villa, int *sp);
    +
    `villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    カーソルのあるレコードの周辺にレコードを挿入するには、関数 `vlcurput' を用いる。

    + +
    +
    int vlcurput(VILLA *villa, const char *vbuf, int vsiz, int cpmode);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`cpmode' は詳細な調整を指定する。`VL_CPCURRENT' は現在のレコードの値を上書きすることを指示し、`VL_CPBEFORE' はカーソルの直前にレコードを挿入することを指示し、`VL_CPAFTER' はカーソルの直後にレコードを挿入することを指示する。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。挿入操作の後には、カーソルは挿入されたレコードの位置に移動する。
    +
    + +

    カーソルのあるレコードを削除するには、関数 `vlcurout' を用いる。

    + +
    +
    int vlcurout(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。削除操作の後には、可能であればカーソルは次のレコードに移動する。
    +
    + +

    性能を調整するパラメータを指定するには、関数 `vlsettuning' を用いる。

    + +
    +
    void vlsettuning(VILLA *villa, int lrecmax, int nidxmax, int lcnum, int ncnum);
    +
    `villa' はデータベースハンドルを指定する。`lrecmax' はB+木のひとつのリーフに入れるレコードの最大数を指定するが、0 以下ならデフォルト値が使われる。`nidxmax' はB+木の非リーフノードに入れるインデックスの最大数を指定するが、0 以下ならデフォルト値が使われる。`lcnum' はキャッシュに入れるリーフの最大数を指定するが、0 以下ならデフォルト値が使われる。`ncnum' はキャッシュに入れる非リーフノードの最大数を指定するが、0 以下ならデフォルト値が使われる。デフォルトの設定は `vlsettuning(49, 192, 1024, 512)' に相当する。性能調整のパラメータはデータベースに保存されないので、データベースを開く度に指定する必要がある。
    +
    + +

    データベースのフリーブロックプールのサイズ設定するには、関数 `vlsetfbpsiz' を用いる。

    + +
    +
    int vlsetfbpsiz(VILLA *villa, int size);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは256である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。
    +
    + +

    データベースを更新した内容をファイルとデバイスに同期させるには、関数 `vlsync' を用いる。

    + +
    +
    int vlsync(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。トランザクションが有効な間はこの関数を使用すべきではない。
    +
    + +

    データベースを最適化するには、関数 `vloptimize' を用いる。

    + +
    +
    int vloptimize(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返したりする場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。トランザクションが有効な間はこの関数を使用すべきではない。
    +
    + +

    データベースの名前を得るには、関数 `vlname' を用いる。

    + +
    +
    char *vlname(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    データベースファイルのサイズを得るには、関数 `vlfsiz' を用いる。

    + +
    +
    int vlfsiz(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。I/Oバッファにより、戻り値は実際のサイズより小さくなる場合がある。
    +
    + +

    B+木のリーフノードの数を得るには、関数 `vllnum' を用いる。

    + +
    +
    int vllnum(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常ならリーフノードの数であり、エラーなら -1 である。
    +
    + +

    B+木の非リーフノードの数を得るには、関数 `vlnnum' を用いる。

    + +
    +
    int vlnnum(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常なら非リーフノードの数であり、エラーなら -1 である。
    +
    + +

    データベースのレコード数を得るには、関数 `vlrnum' を用いる。

    + +
    +
    int vlrnum(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
    +
    + +

    データベースハンドルがライタかどうかを調べるには、関数 `vlwritable' を用いる。

    + +
    +
    int vlwritable(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
    +
    + +

    データベースに致命的エラーが起きたかどうかを調べるには、関数 `vlfatalerror' を用いる。

    + +
    +
    int vlfatalerror(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
    +
    + +

    データベースファイルのinode番号を得るには、関数 `vlinode' を用いる。

    + +
    +
    int vlinode(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。
    +
    + +

    データベースの最終更新時刻を得るには、関数 `vlmtime' を用いる。

    + +
    +
    time_t vlmtime(VILLA *villa);
    +
    `villa' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
    +
    + +

    トランザクションを開始するには、関数 `vltranbegin' を用いる。

    + +
    +
    int vltranbegin(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はマルチスレッドでの相互排他制御を行わないので、アプリケーションがその責任を負う。一つのデータベースハンドルで同時に有効にできるトランザクションは一つだけである。
    +
    + +

    トランザクションをコミットするには、関数 `vltrancommit' を用いる。

    + +
    +
    int vltrancommit(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はコミットが成功した時に確定する。
    +
    + +

    トランザクションを破棄するには、関数 `vltranabort' を用いる。

    + +
    +
    int vltranabort(VILLA *villa);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はアボートした時には破棄される。データベースの状態はトランザクションの前の状態にロールバックされる。
    +
    + +

    データベースファイルを削除するには、関数 `vlremove' を用いる。

    + +
    +
    int vlremove(const char *name);
    +
    `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    壊れたデータベースファイルを修復するには、関数 `vlrepair' を用いる。

    + +
    +
    int vlrepair(const char *name, VLCFUNC cmp);
    +
    `name' はデータベースファイルの名前を指定する。`cmp' はデータベースファイルの比較関数を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。
    +
    + +

    全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `vlexportdb' を用いる。

    + +
    +
    int vlexportdb(VILLA *villa, const char *name);
    +
    `villa' はデータベースハンドルを指定する。`name' は出力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    エンディアン非依存データから全てのレコードをロードするには、関数 `vlimportdb' を用いる。

    + +
    +
    int vlimportdb(VILLA *villa, const char *name);
    +
    `villa' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    サンプルコード

    + +

    名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    文字列の前方一致検索を行うアプリケーションのサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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;
    +}
    +
    + +

    注記

    + +

    Villaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Villaの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +

    Vista: 拡張上級API

    + +

    VistaはVillaを拡張したAPIである。Villaが2GB以上のファイルを扱うことができないという欠点を補うために、VistaではDepotではなくCuriaを用いて内部データベースを管理する。VistaはVillaと同じくB+木のデータ構造とその操作を提供するが、そのデータベースはディレクトリで実現される。

    + +

    Vistaを使うには、`villa.h' の代わりに `vista.h' をインクルードすればよい。VistaはVillaのシンボルをマクロでオーバーライドして実装されているため、Villaと全く同様のAPIで利用することができる。すなわち、双方のシグネチャは全く同じである。ただし、その副作用として、Vistaを使うモジュール(コンパイルユニット)はVillaを利用することができない(`villa.h' をインクルードしてはならない)。

    + +
    + +

    Villa用コマンド

    + +

    Villaに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `vlmgr' はVillaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

    + +
    +
    vlmgr create [-cz|-cy|-cx] name
    +
    データベースファイルを作成する。
    +
    vlmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat|-dup] name key val
    +
    キーと値に対応するレコードを追加する。
    +
    vlmgr out [-l] [-kx|-ki] name key
    +
    キーに対応するレコードを削除する。
    +
    vlmgr get [-nl] [-l] [-kx|-ki] [-ox] [-n] name key
    +
    キーに対応するレコードの値を取得して標準出力する。
    +
    vlmgr list [-nl] [-k|-v] [-kx|-ki] [-ox] [-top key] [-bot key] [-gt] [-lt] [-max num] [-desc] name
    +
    データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
    +
    vlmgr optimize name
    +
    データベースを最適化する。
    +
    vlmgr inform [-nl] name
    +
    データベースの雑多な情報を出力する。
    +
    vlmgr remove name
    +
    データベースファイルを削除する。
    +
    vlmgr repair [-ki] name
    +
    壊れたデータベースファイルを修復する。
    +
    vlmgr exportdb [-ki] name file
    +
    全てのレコードをエンディアン非依存のデータとしてダンプする。
    +
    vlmgr importdb [-ki] name file
    +
    エンディアン非依存データから全てのレコードをロードする。
    +
    vlmgr version
    +
    QDBMのバージョン情報を標準出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -cz : データベースのリーフをZLIBで圧縮する。
    • +
    • -cy : データベースのリーフをLZOで圧縮する。
    • +
    • -cx : データベースのリーフをBZIP2で圧縮する。
    • +
    • -l : キーに一致する全てのレコードを処理対象とする。
    • +
    • -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
    • +
    • -ki : 10進数による数値表現として `key' を扱う。
    • +
    • -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
    • +
    • -vi : 10進数による数値表現として `val' を扱う。
    • +
    • -vf : 名前が `val' のファイルのデータを値として読み込む。
    • +
    • -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
    • +
    • -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
    • +
    • -dup : レコードのキーが重複するのを許す。
    • +
    • -nl : ファイルロックをかけずにデータベースを開く。
    • +
    • -top key : リストの最小のキーを指定する。
    • +
    • -bot key : リストの最大のキーを指定する。
    • +
    • -gt : 最小のキーをリストに含めない。
    • +
    • -lt : 最大のキーをリストに含めない。
    • +
    • -max num : リストする最大数を指定する。
    • +
    • -desc : リストを降順で行う。
    • +
    • -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
    • +
    • -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
    • +
    • -k : キーのみを出力する。
    • +
    • -v : 値のみを出力する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `vltest' はVillaの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `vlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`pnum' はキーのパターン数を指定する。

    + +
    +
    vltest write [-int] [-cz|-cy|-cx] [-tune lrecmax nidxmax lcnum ncnum] [-fbp num] name rnum
    +
    `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
    +
    vltest read [-int] [-vc] name
    +
    上記で生成したデータベースの全レコードを検索する。
    +
    vltest rdup [-int] [-cz|-cy|-cx] [-cc] [-tune lrecmax nidxmax lcnum ncnum] [-fbp num] name rnum pnum
    +
    キーがある程度重複するようにレコードの追加を行い、重複モードで処理する。
    +
    vltest combo [-cz|-cy|-cx] name
    +
    各種操作の組み合わせテストを行う。
    +
    vltest wicked [-cz|-cy|-cx] name rnum
    +
    各種更新操作を無作為に選択して実行する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -int : `int' 型のオブジェクトをキーと値に用い、比較関数もそれに合わせる。
    • +
    • -cz : データベースのリーフをZLIBで圧縮する。
    • +
    • -cy : データベースのリーフをLZOで圧縮する。
    • +
    • -cx : データベースのリーフをBZIP2で圧縮する。
    • +
    • -vc : 揮発性キャッシュを参照する。
    • +
    • -cc : 連結モードと重複モードを無作為に選択する。
    • +
    • -tune lrecmax nidxmax lcnum ncnum : 性能パラメータを指定する。
    • +
    • -fbp num : フリーブロックプールのサイズを指定する。
    • +
    • -c : Cabinのマップを使って比較テストを行う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `vltsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとVillaのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。`import' サブコマンドではTSVのデータが標準出力に書き出される。

    + +
    +
    vltsv import [-bin] name
    +
    TSVファイルを読み込んでデータベースを作成する。
    +
    vltsv export [-bin] name
    +
    データベースの全てのレコードをTSVファイルとして出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -bin : Base64形式でレコードを扱う。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    Villaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。

    + +
    cat /etc/passwd | tr ':' '\t' | vltsv import casket
    +
    + +

    そして、`mikio' というユーザの情報を取り出すには、以下のようにする。

    + +
    vlmgr get casket mikio
    +
    + +

    これらのコマンドと同等の機能をVillaのAPIを用いて実装することも容易である。

    + +

    コマンド `qmttest' はDepotとCuriaとVillaのマルチスレッドにおける安全性を調査する。このコマンドはPOSIXスレッドを有効にしてQDBMをビルドした場合にのみマルチスレッドで動作する。以下の書式で用いる。`name' はデータベース名の接頭辞を指定する。`rnum' は各データベースに書き込むレコード数を指定する。`tnum' はスレッド数を指定する。

    + +
    +
    qmttest name rnum tnum
    +
    マルチスレッドにおける安全性を調査する。
    +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +
    + +

    Odeum: 転置API

    + +

    概要

    + +

    Odeumは転置インデックスを扱うAPIである。転置インデックスとは、母集団の文書群に含まれる語を抽出して、各語をキーとし、その語を含む文書のリストを検索するためのデータ構造である。転置インデックスを用いると、全文検索システムを容易に実現することができる。Odeumは文書を語や属性の集合として扱うための抽象データ型を提供する。それは、各アプリケーションがOdeumのデータベースに文書を格納する際や、データベースから文書を検索する際に用いられる。

    + +

    Odeumは元来の文書データからテキストを抽出する方法は提供しない。それはアプリケーションが実装する必要がある。Odeumはテキストを分解して語群を抽出するユーティリティを提供するが、それは空白で語が分割される英語などの言語を指向している。形態素解析やN-gram解析が必要な日本語などの言語を扱う際や、ステミングなどのより高度な自然言語処理を行う際には、アプリケーションは独自の解析方法を適用することができる。検索結果は文書のID番号とそのスコアをメンバに持つ構造体を要素とする配列として得られる。複数の語を用いて検索を行うために、Odeumは結果の配列の集合演算を行うユーティリティを提供する。

    + +

    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番号である。

    + +

    Odeumを使うためには、`depot.h' と `cabin.h' と `odeum.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。

    + +
    +
    #include <depot.h>
    +
    #include <cabin.h>
    +
    #include <odeum.h>
    +
    #include <stdlib.h>
    +
    + +

    Odeumでデータベースを扱う際には、`ODEUM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `odopen' で開き、関数 `odclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `odclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。

    + +

    各文書を扱う際には、`ODDOC' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `oddocopen' で開き、関数 `oddocclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。文書は属性と語の集合からなる。語は正規形と出現形のペアとして表現される。

    + +

    OdeumでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。

    + +

    API

    + +

    検索結果を扱うためには、`ODPAIR' 型の構造体を用いる。

    + +
    +
    typedef struct { int id; int score; } ODPAIR;
    +
    `id' は文書のID番号である。`score' は文書に含まれる検索語の数を元に算出されるスコアである。
    +
    + +

    データベースのハンドルを作成するには、関数 `odopen' を用いる。

    + +
    +
    ODEUM *odopen(const char *name, int omode);
    +
    `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' を使う場合、アプリケーションが排他制御の責任を負う。
    +
    + +

    データベースとの接続を閉じてハンドルを破棄するには、関数 `odclose' を用いる。

    + +
    +
    int odclose(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
    +
    + +

    文書を追加するには、関数 `odput' を用いる。

    + +
    +
    int odput(ODEUM *odeum, const ODDOC *doc, int wmax, int over);
    +
    `odeum' はライタで接続したデータベースハンドルを指定する。`doc' は文書ハンドルを指定する。`wmax' は文書データベースに格納する語の最大数を指定するが、負数なら無制限となる。`over' は重複した文書の上書きを行うか否かを指定する。それが偽で文書のURIが重複した場合はエラーとなる。戻り値は正常なら真であり、エラーなら偽である。
    +
    + +

    URIで指定した文書を削除するには、関数 `odout' を用いる。

    + +
    +
    int odout(ODEUM *odeum, const char *uri);
    +
    `odeum' はライタで接続したデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。
    +
    + +

    ID番号で指定した文書を削除するには、関数 `odoutbyid' を用いる。

    + +
    +
    int odoutbyid(ODEUM *odeum, int id);
    +
    `odeum' はライタで接続したデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。
    +
    + +

    URIで指定した文書を取得するには、関数 `odget' を用いる。

    + +
    +
    ODDOC *odget(ODEUM *odeum, const char *uri);
    +
    `odeum' はデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
    +
    + +

    ID番号で指定した文書を取得するには、関数 `odgetbyid' を用いる。

    + +
    +
    ODDOC *odgetbyid(ODEUM *odeum, int id);
    +
    `odeum' はデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
    +
    + +

    URIで指定した文書のIDを取得するには、関数 `odgetidbyuri' を用いる。

    + +
    +
    int odgetidbyuri(ODEUM *odeum, const char *uri);
    +
    `odeum' はデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら該当の文書のIDであり、エラーなら -1 である。該当の文書がない場合も -1 を返す。
    +
    + +

    ID番号で指定した文書が存在しているか調べるには、関数 `odcheck' を用いる。

    + +
    +
    int odcheck(ODEUM *odeum, int id);
    +
    `odeum' はデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は文書が存在すれば真、そうでなければ偽である。
    +
    + +

    転置インデックスを検索して特定の語を含む文書群を知るには、関数 `odsearch' を用いる。

    + +
    +
    ODPAIR *odsearch(ODEUM *odeum, const char *word, int max, int *np);
    +
    `odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。`max' は取り出す文書の最大数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は正常なら配列へのポインタであり、エラーなら `NULL' である。その配列の各要素は文書のID番号とスコアのペアであり、スコアの降順で並べられる。検索語に該当する文書が一つもなかったとしてもエラーにはならずに、空の配列を返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。配列の各要素には既に削除された文書のデータも含まれることに注意すべきである。
    +
    + +

    特定の語を含む文書の数を知るには、関数 `odsearchdnum' を用いる。

    + +
    +
    int odsearchdnum(ODEUM *odeum, const char *word);
    +
    `odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。戻り値は正常なら検索語を含む文書の数であり、該当がない場合やエラーの場合は -1 である。この関数は転置インデックスの実データを読み込まないので効率がよい。
    +
    + +

    データベースのイテレータを初期化するには、関数 `oditerinit' を用いる。

    + +
    +
    int oditerinit(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全ての文書を参照するために用いられる。
    +
    + +

    データベースのイテレータから次の文書を取り出すには、関数 `oditernext' を用いる。

    + +
    +
    ODDOC *oditernext(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常なら文書ハンドルであり、エラーなら `NULL' である。イテレータが最後まできて該当の文書がない場合も `NULL' を返す。この関数を繰り返して呼ぶことによって全ての文書を一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
    +
    + +

    データベースを更新した内容をファイルとデバイスに同期させるには、関数 `odsync' を用いる。

    + +
    +
    int odsync(ODEUM *odeum);
    +
    `odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースディレクトリを利用させる場合に役立つ。
    +
    + +

    データベースを最適化するには、関数 `odoptimize' を用いる。

    + +
    +
    int odoptimize(ODEUM *odeum);
    +
    `odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。転置インデックスにおける削除された文書の要素は削除される。
    +
    + +

    データベースの名前を得るには、関数 `odname' を用いる。

    + +
    +
    char *odname(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    データベースファイルのサイズの合計を得るには、関数 `odfsiz' を用いる。

    + +
    +
    double odfsiz(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1.0 である。
    +
    + +

    転置インデックス内のバケット配列の要素数の合計を得るには、関数 `odbnum' を用いる。

    + +
    +
    int odbnum(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の要素数の合計であり、エラーなら -1 である。
    +
    + +

    転置インデックス内のバケット配列の利用済みの要素数の合計を得るには、関数 `odbusenum' を用いる。

    + +
    +
    int odbusenum(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用済みの要素数の合計であり、エラーなら -1 である。
    +
    + + +

    データベースに格納された文書数を得るには、関数 `oddnum' を用いる。

    + +
    +
    int oddnum(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された文書の数であり、エラーなら -1 である。
    +
    + +

    データベースに格納された単語数を得るには、関数 `odwnum' を用いる。

    + +
    +
    int odwnum(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された語の数であり、エラーなら -1 である。I/Oバッファにより、戻り値は実際のサイズより小さくなる場合がある。
    +
    + +

    データベースハンドルがライタかどうかを調べるには、関数 `odwritable' を用いる。

    + +
    +
    int odwritable(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
    +
    + +

    データベースに致命的エラーが起きたかどうかを調べるには、関数 `odfatalerror' を用いる。

    + +
    +
    int odfatalerror(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
    +
    + +

    データベースディレクトリのinode番号を得るには、関数 `odinode' を用いる。

    + +
    +
    int odinode(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。
    +
    + +

    データベースの最終更新時刻を得るには、関数 `odmtime' を用いる。

    + +
    +
    time_t odmtime(ODEUM *odeum);
    +
    `odeum' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
    +
    + +

    複数のデータベースをマージするには、関数 `odmerge' を用いる。

    + +
    +
    int odmerge(const char *name, const CBLIST *elemnames);
    +
    `name' は作成するデータベースディレクトリの名前を指定する。`elemnames' は要素データベースの名前のリストを指定する。戻り値は正常なら真であり、エラーなら偽である。同じURLを持つ複数の文書が現れた場合、最初に現れたものが採用され、残りは無視される。
    +
    + +

    データベースディレクトリを削除するには、関数 `odremove' を用いる。

    + +
    +
    int odremove(const char *name);
    +
    `name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。データベースディレクトリの中にはQDBMの他のAPIによるデータベースを置くことができるが、それらもこの関数によって削除される。
    +
    + +

    文書ハンドルを作成するには、関数 `oddocopen' を用いる。

    + +
    +
    ODDOC *oddocopen(const char *uri);
    +
    `uri' は文書のURIを指定する。戻り値は文書ハンドルである。新しい文書のID番号は定義されない。それはデータベースに格納した際に定義される。
    +
    + +

    文書ハンドルを破棄するには、関数 `oddocclose' を用いる。

    + +
    +
    void oddocclose(ODDOC *doc);
    +
    `doc' は文書ハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
    +
    + +

    文書に属性を追加するには、関数 `oddocaddattr' を用いる。

    + +
    +
    void oddocaddattr(ODDOC *doc, const char *name, const char *value);
    +
    `doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。`value' は属性値の文字列を指定する。
    +
    + +

    文書に語を追加するには、関数 `oddocaddword' を用いる。

    + +
    +
    void oddocaddword(ODDOC *doc, const char *normal, const char *asis);
    +
    `doc' は文書ハンドルを指定する。`normal' は語の正規形の文字列を指定する。正規形は転置インデックスのキーとして扱われる。正規形が空文字列の場合、その語は転置インデックスに反映されない。`asis' は語の出現形の文字列を指定する。出現形はアプリケーションが文書を取得した際に利用される。
    +
    + +

    文書のIDを得るには、関数 `oddocid' を用いる。

    + +
    +
    int oddocid(const ODDOC *doc);
    +
    `doc' は文書ハンドルを指定する。戻り値は文書のID番号である。
    +
    + +

    文書のURIを得るには、関数 `oddocuri' を用いる。

    + +
    +
    const char *oddocuri(const ODDOC *doc);
    +
    `doc' は文書ハンドルを指定する。戻り値は文書のURIの文字列である。
    +
    + +

    文書の属性値を得るには、関数 `oddocgetattr' を用いる。

    + +
    +
    const char *oddocgetattr(const ODDOC *doc, const char *name);
    +
    `doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。戻り値は属性値の文字列であるか、該当がなければ `NULL' である。
    +
    + +

    文書内の語群の正規形のリストを得るには、関数 `oddocnwords' を用いる。

    + +
    +
    const CBLIST *oddocnwords(const ODDOC *doc);
    +
    `doc' は文書ハンドルを指定する。戻り値は正規形の語群を格納したリストハンドルである。
    +
    + +

    文書内の語群の出現形のリストを得るには、関数 `oddocawords' を用いる。

    + +
    +
    const CBLIST *oddocawords(const ODDOC *doc);
    +
    `doc' は文書ハンドルを指定する。戻り値は出現形の語群を格納したリストハンドルである。
    +
    + +

    文書のキーワードの正規形とそのスコアのマップを得るには、関数 `oddocscores' を用いる。

    + +
    +
    CBMAP *oddocscores(const ODDOC *doc, int max, ODEUM *odeum);
    +
    `doc' は文書ハンドルを指定する。`max' は取得するキーワードの最大数を指定する。`odeum' が `NULL' でなければ、それを用いて重みづけのためのIDFが算出される。戻り値はキーワードとそのスコアを格納したマップハンドルである。スコアは10進数の文字列で表現される。戻り値のハンドルは関数 `cbmapopen' で開かれるので、不要になったら `cbmapclose' で閉じるべきである。
    +
    + +

    テキストを分解して語群の出現形のリストを得るには、関数 `odbreaktext' を用いる。

    + +
    +
    CBLIST *odbreaktext(const char *text);
    +
    `text' はテキストの文字列を指定する。戻り値は語群の出現形のリストハンドルである。語群は空白文字とピリオドやコンマ等の区切り文字で分割される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
    +
    + +

    語の正規形を取得するには、関数 `odnormalizeword' を用いる。

    + +
    +
    char *odnormalizeword(const char *asis);
    +
    `asis' は語の出現形の文字列を指定する。戻り値は語の正規形の文字列である。ASCIIコードのアルファベットは小文字に統一される。区切り文字のみからなる文字は空文字列として扱われる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    二つの文書集合からその共通集合を得るには、関数 `odpairsand' を用いる。

    + +
    +
    ODPAIR *odpairsand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合に共通して属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    二つの文書集合からその和集合を得るには、関数 `odpairsor' を用いる。

    + +
    +
    ODPAIR *odpairsor(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合の両方あるいはどちらか一方に属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    二つの文書集合からその差集合を得るには、関数 `odpairsnotand' を用いる。

    + +
    +
    ODPAIR *odpairsnotand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は前者の集合には属するが後者の集合には属さないものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
    +
    + +

    文書集合をスコアの降順で並べるには、関数 `odpairssort' を用いる。

    + +
    +
    void odpairssort(ODPAIR *pairs, int pnum);
    +
    `pairs' は文書集合の配列を指定する。`pnum' はその配列の要素数を指定する。
    +
    + +

    ある数の自然対数を得るには、関数 `odlogarithm' を用いる。

    + +
    +
    double odlogarithm(double x);
    +
    `x' はある数を指定する。戻り値はその数の自然対数である。もしその数が 1.0 以下であれば、戻り値は 0.0 となる。この関数はアプリケーションが検索結果のIDFを算出する際に便利である。
    +
    + +

    二つのベクトルのなす角の余弦を得るには、関数 `odvectorcosine' を用いる。

    + +
    +
    double odvectorcosine(const int *avec, const int *bvec, int vnum);
    +
    `avec' は前者の数値配列を指定する。`bvec' は後者の数値配列を指定する。`vnum' は各々の配列の要素数を指定する。戻り値は二つのベクトルのなす角の余弦である。この関数はアプリケーションが文書の類似度を算出する際に便利である。
    +
    + +

    性能を調整する大域的なパラメータを指定するには、関数 `odsettuning' を用いる。

    + +
    +
    void odsettuning(int ibnum, int idnum, int cbnum, int csiz);
    +
    `ibnum' は転置インデックスのバケット数を指定する。`idnum' は転置インデックスの分割数を指定する。`cbnum' はダーティバッファのバケット数を指定する。`csiz' はダーティバッファに使うメモリの最大バイト数を指定する。デフォルトの設定は `odsettuning(32749, 7, 262139, 8388608)' に相当する。この関数はハンドルを開く前に呼び出すべきである。
    +
    + +

    テキストを分解して出現形と正規形を別々のリストに格納するには、関数 `odanalyzetext' を用いる。

    + +
    +
    void odanalyzetext(ODEUM *odeum, const char *text, CBLIST *awords, CBLIST *nwords);
    +
    `odeum' はデータベースハンドルを指定する。`text' はテキストの文字列を指定する。`awords' は出現型を格納するリストハンドルを指定する。`nwords' は正規型を格納するリストハンドルを指定するが、`NULL' なら無視される。語群は空白文字とピリオドやコンマ等の区切り文字で分割される。
    +
    + +

    関数 `odanalyzetext' で使われる文字の分類を設定するには、関数 `odsetcharclass' を用いる。

    + +
    +
    void odsetcharclass(ODEUM *odeum, const char *spacechars, const char *delimchars, const char *gluechars);
    +
    `odeum' はデータベースハンドルを指定する。`spachechars' は空白文字を含む文字列を指定する。`delimchars' は区切り文字を含む文字列を指定する。`gluechars' は接着文字を含む文字列を指定する。
    +
    + +

    小さな問い合わせ言語を使って検索を行うには、関数 `odquery' を用いる。

    + +
    +
    ODPAIR *odquery(ODEUM *odeum, const char *query, int *np, CBLIST *errors);
    +
    `odeum' はデータベースハンドルを指定する。`query' は問い合わせ言語の文字列を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。`error' はエラーメッセージを格納するリストハンドルを指定する。戻り値は正常なら配列へのポインタであり、エラーなら `NULL' である。その配列の各要素は文書のID番号とスコアのペアであり、スコアの降順で並べられる。検索語に該当する文書が一つもなかったとしてもエラーにはならずに、空の配列を返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。配列の各要素には既に削除された文書のデータも含まれることに注意すべきである。
    +
    + +

    サンプルコード

    + +

    文書をデータベースに格納するサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < 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;
    +}
    +
    + +

    データベース内の文書を検索するサンプルコードを以下に示す。

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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, &pnum)) != NULL){
    +
    +    /* 文書の配列を走査する */
    +    for(i = 0; i < 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 < 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;
    +}
    +
    + +

    注記

    + +

    Odeumを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Odeumの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。

    + +

    ZLIBを有効にしてQDBMをビルドした場合、文書属性用データベース内のレコードは圧縮されて保存される。その場合、サイズが30%以下になる。したがって、Odeumを利用する場合はZLIBを有効にした方がよい。ZLIBを有効にして作成したOdeumのデータベースを、ZLIBを有効にしていない環境で利用することはできず、またその逆も同様である。ZLIBが有効でなくLZOが有効な場合は、ZLIBの変わりにLZOが用いられる。

    + +

    問い合わせ言語

    + +

    関数 `odquery' の問い合わせ言語は以下の文法に基づく。

    + +
    expr ::= subexpr ( op subexpr )*
    +subexpr ::= WORD
    +subexpr ::= LPAREN expr RPAREN
    +
    + +

    演算子としては "&"(AND)と "|"(OR)と "!"(NOTAND)が利用できる。また、括弧 "()" を使うことで演算子の評価順序を制御することができる。問い合わせの文字列は関数 `odanalyzetext' を用いて分解されるので、"&"、"|"、"!"、"("、")" は区切り文字として設定されている必要がある。また、空白で単語を区切っても "&" で区切ったのと同じことになる。つまり "joe blow" は "joe & blow" と同じである。

    + +

    問い合わせ文字列の文字コードは対象文書の文字コードと一致している必要がある。また、空白文字と区切り文字と接着文字に指定できるのは1バイト文字だけである。

    + +
    + +

    Odeum用コマンド

    + +

    Odeumに対応するコマンドラインインタフェースは以下のものである。

    + +

    コマンド `odmgr' はOdeumやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトで全文検索システムを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`file' はファイル名、`expr' は文書のURIかID番号、`words' は検索語、`elems' は要素データベースを指定する。

    + +
    +
    odmgr create name
    +
    データベースファイルを作成する。
    +
    odmgr put [-uri str] [-title str] [-author str] [-date str] [-wmax num] [-keep] name [file]
    +
    ファイルを読み込んで文書を追加する。`file' を省略すると標準入力を読み込むが、その場合はURIの指定が必須となる。
    +
    odmgr out [-id] name expr
    +
    URIに対応する文書を削除する。
    +
    odmgr get [-id] [-t|-h] name expr
    +
    URIに対応する文書を表示する。出力は文書のID番号とURIとスコアをタブで区切ったものである。
    +
    odmgr search [-max num] [-or] [-idf] [-t|-h|-n] name words...
    +
    指定した語を含む文書を検索する。出力の第1行は、検索語全体の該当数と各検索語およびその該当数をタブで区切ったものである。第2行以降は、該当の各文書のID番号とURIとスコアをタブで区切ったものである。
    +
    odmgr list [-t|-h] name
    +
    データベース内の全ての文書を表示する。出力の各行は文書のID番号とURIとスコアをタブで区切ったものである。
    +
    odmgr optimize name
    +
    データベースを最適化する。
    +
    odmgr inform name
    +
    データベースの雑多な情報を出力する。
    +
    odmgr merge name elems...
    +
    複数のデータベースをマージする。
    +
    odmgr remove name
    +
    データベースディレクトリを削除する。
    +
    odmgr break [-h|-k|-s] [file]
    +
    ファイルを読み込んで、テキストを語に分解して出力する。出力の各行は各語の正規形と出現形をタブで区切ったものである。
    +
    odmgr version
    +
    QDBMのバージョン情報を出力する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -uri str : 文書のURIを明示的に指定する。
    • +
    • -title str : 文書のタイトルを指定する。
    • +
    • -author str : 文書の著者名を指定する。
    • +
    • -date str : 文書の更新日時を指定する。
    • +
    • -wmax num : 格納する語の最大数を指定する。
    • +
    • -keep : 同じURIの文書が既存であれば上書きを行わない。
    • +
    • -id : URIでなくID番号で文書を指定する。
    • +
    • -t : 文書の詳細情報をタブ区切りで出力する。
    • +
    • -h : 文書の詳細情報を人間が読みやすい形式で出力する。
    • +
    • -k : 文書のキーワードのみを出力する。
    • +
    • -s : 文書の要約のみを出力する。
    • +
    • -max num : 出力する文書の最大数を指定する。
    • +
    • -or : AND検索でなくOR検索を行う。
    • +
    • -idf : IDFでスコアを重みづけする。
    • +
    • -n : 文書のIDとスコアのみを表示する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `odtest' はOdeumの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースディレクトリを `odmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`dnum' は文書数、`wnum' は文書毎の語数、`pnum' は語のパターン数を指定する。

    + +
    +
    odtest write [-tune ibnum idnum cbnum csiz] name dnum wnum pnum
    +
    無作為な属性と語を持つ文書を連続してデータベースに追加する。
    +
    odtest read name
    +
    上記で生成したデータベースの全文書を検索する。
    +
    odtest combo name
    +
    各種操作の組み合わせテストを行う。
    +
    odtest wicked name dnum
    +
    各種更新操作を無作為に選択して実行する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -tune ibnum idnum cbnum csiz : 性能パラメータを指定する。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。

    + +

    コマンド `odidx' はローカルファイルシステム上のファイルを読み込んでOdeumのデータベースに登録するユーティリティである。このコマンドはWebサイトの全文検索システムを構築する際に役立つ。サポートされるファイルフォーマットはプレーンテキストとHTMLである。サポートされる文字コードはUS-ASCIIとISO-8859-1である。各文書のURIにはファイルのパスが指定される。各文書には、`title' と `date' という属性が付与される。既にデータベース登録してあるファイルを登録しようとした場合、更新時刻が新しければ登録され、そうでなければ無視される。更新時刻はデータベースディレクトリの中の '_mtime' というサブデータベースに記録される。スコア情報はデータベースディレクトリの中の `_score' というサブデータベースに記録される。以下の書式で用いる。`name' はデータベース名、`dir' はディレクトリ名を指定する。

    + +
    +
    odidx register [-l file] [-wmax num] [-tsuf sufs] [-hsuf sufs] name [dir]
    +
    特定のディレクトリ以下のファイル群をデータベース登録する。`dir' が省略された場合、カレントディレクトリが指定される。
    +
    odidx relate name
    +
    データベースの各文書に関連文書検索のためのスコア情報を付加する。
    +
    odidx purge name
    +
    ファイルシステムに存在しない文書をデータベースから削除する。
    +
    + +

    各オプションは以下の機能を持つ。

    + +
      +
    • -l file : 登録すべきファイルのパスのリストをファイルから読み込む。`-' を指定した場合、標準入力が読み込まれる。
    • +
    • -wmax num : データベースに格納する語の最大数を指定する。
    • +
    • -tsuf sufs : プレーンテキストファイルの拡張子をカンマ区切りで指定する。デフォルトは `-tsuf .txt,.text' と同意である。
    • +
    • -hsuf sufs : HTMLファイルの拡張子をカンマ区切りで指定する。デフォルトは `-hsuf .html,.htm' と同意である。
    • +
    + +

    このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

    + +

    Odeumのコマンド群を駆使すると、全文検索システムを簡単に実現することができる。例えば `/home/mikio' 以下にあり、かつ `.txt' か `.c' か `.h' という接尾辞を持つファイル群を `casket' という名前のインデックスに登録するなら、以下のようにする。

    + +
    odidx register -tsuf ".txt,.c,.h" -hsuf "" casket /home/mikio
    +
    + +

    そして、`unix' および `posix' という語を含む文書を検索し、上位8件を表示するには、以下のようにする。

    + +
    odmgr search -max 8 -h casket "unix posix"
    +
    + +

    `odidx' で生成したデータベースは、QDBMに付録される全文検索のためのCGIスクリプト `qfts.cgi' でそのまま利用することができる。

    + +
    + +

    ファイルフォーマット

    + +

    Depotのファイルフォーマット

    + +

    Depotが管理するデータベースファイルの内容は、ヘッダ部、バケット部、レコード部の三つに大別される。

    + +

    ヘッダ部はファイルの先頭から 48 バイトの固定長でとられ、以下の情報が記録される。

    + +
      +
    1. マジックナンバ : オフセット 0 から始まる。ビッグエンディアン用なら文字列 "[DEPOT]\n\f" を内容とし、リトルエンディアン用なら文字列 "[depot]\n\f" を内容とする。
    2. +
    3. バージョン番号 : オフセット 12 から始まる。ライブラリのバージョン番号を10進数で表現した文字列を内容とする。
    4. +
    5. ラッパー用フラグ : オフセット 16 から始まる。`int' 型の整数である。
    6. +
    7. ファイルサイズ : オフセット 24 から始まる。`int' 型の整数である。
    8. +
    9. バケット配列の要素数 : オフセット 32 から始まる。`int' 型の整数である。
    10. +
    11. レコード数 : オフセット 40 から始まる。`int' 型の整数である。
    12. +
    + +

    バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、チェーンの先頭要素のオフセットが各要素に記録される。

    + +

    レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録される。

    + +
      +
    1. フラグ : `int' 型の整数である。
    2. +
    3. キーの第二ハッシュ値 : `int' 型の整数である。
    4. +
    5. キーのサイズ : `int' 型の整数である。
    6. +
    7. 値のサイズ : `int' 型の整数である。
    8. +
    9. パディングのサイズ : `int' 型の整数である。
    10. +
    11. 左の子の位置 : `int' 型の整数である。
    12. +
    13. 右の子の位置 : `int' 型の整数である。
    14. +
    15. キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
    16. +
    17. 値の実データ : 値のサイズで定義される長さを持つ一連のバイトである。
    18. +
    19. パディング : 値のサイズとアラインメントにより算出される長さを持つ一連のバイトである。
    20. +
    + +

    Villaのファイルフォーマット

    + +

    Villaの扱う全てのデータはDepotのデータベースに記録される。記録されるデータは、メタデータと論理ページに分類される。論理ページはリーフノードと非リーフノードに分類される。メタデータはレコード数等の管理情報を記録するもので、キーと値ともに `int' 型である。リーフノードはレコードを保持する。非リーフノードはページを参照する疎インデックスを保持する。

    + +

    Villaは、小さい自然数を直列化して扱う際に記憶領域を節約するために、可変長整数フォーマット(BER圧縮)を用いる。可変長整数のオブジェクトは、領域の先頭から解析し、値が正のバイトを読んだらそこで終端とする。各バイトは絶対値で評価され、リトルエンディアンの128進数として算出される。

    + +

    レコードはユーザデータの論理的な単位である。キーが重複する論理レコードは物理的には単一のレコードにまとめられる。物理レコードは以下の形式で直列化される。

    + +
      +
    1. キーのサイズ : 可変長整数型である。
    2. +
    3. キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
    4. +
    5. 値の数 : 可変長整数型である。
    6. +
    7. 値のリスト : 以下の表現を値の数だけ繰り返した一連のバイトである。
        +
      1. サイズ : 可変長整数型である。
      2. +
      3. 実データ : サイズで定義される長さを持つ一連のバイトである。
      4. +
    8. +
    + +

    リーフノードはレコードの集合を格納するための物理的な単位である。リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。レコードは常にキーの昇順に整列した状態で保持される。

    + +
      +
    1. 前のリーフのID : 可変長整数型である。
    2. +
    3. 次のリーフのID : 可変長整数型である。
    4. +
    5. レコードのリスト : 直列化したレコードを連結したもの。
    6. +
    + +

    インデックスはページを探索するためのポインタの論理的な単位である。インデックスは以下の形式で直列化される。

    + +
      +
    1. 参照先のページのID : 可変長整数型である。
    2. +
    3. キーのサイズ : 可変長整数型である。
    4. +
    5. キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
    6. +
    + +

    非リーフノードはインデックスの集合を格納するための物理的な単位である。非リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。インデックスは常にキーの昇順に整列した状態で保持される。

    + +
      +
    1. 最初の子ノードのID : 可変長整数型である。
    2. +
    3. インデックスのリスト : 直列化したインデックスを連結したもの。
    4. +
    + +

    注記

    + +

    データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができる。Depotはバイトオーダの調整をしないでファイルの読み書きを行っているので、バイトオーダの異なる環境にデータベースファイルを移設してもそのままでは利用できない。

    + +

    DepotやVillaのデータベースファイルをネットワークで配布する際には、MIMEタイプを `application/x-qdbm' にしてほしい。ファイル名の接尾辞は `.qdb' にしてほしい。Curiaのデータベースディレクトリをネットワークで配布する際には、TAR形式等を用いたアーカイブに変換して行うことができる。

    + +

    データベースファイルのマジックナンバを `file' コマンドに識別させたい場合は、`magic' ファイルに以下の行を追記するとよい。

    + +
    0       string          [DEPOT]\n\f     QDBM, big endian
    +>12     string          x               \b, version=%s
    +>19     byte            ^1              \b, Hash
    +>19     byte            &1              \b, B+ tree
    +>19     byte            &2              \b (deflated:ZLIB)
    +>19     byte            &4              \b (deflated:LZO)
    +>19     byte            &8              \b (deflated:BZIP2)
    +>24     belong          x               \b, filesize=%d
    +>32     belong          x               \b, buckets=%d
    +>40     belong          x               \b, records=%d
    +0       string          [depot]\n\f     QDBM, little endian
    +>12     string          x               \b, version=%s
    +>16     byte            ^1              \b, Hash
    +>16     byte            &1              \b, B+ tree
    +>16     byte            &2              \b (deflated:ZLIB)
    +>16     byte            &4              \b (deflated:LZO)
    +>16     byte            &8              \b (deflated:BZIP2)
    +>24     lelong          x               \b, filesize=%d
    +>32     lelong          x               \b, buckets=%d
    +>40     lelong          x               \b, records=%d
    +
    + +
    + +

    移植方法

    + +

    QDBMはPOSIX互換の全てのプラットフォームで動作することを目標としている。ただし、いくつかのAPIが実装されていないプラットフォームでも動作することが望ましい。また、GCC以外のコンパイラを利用してもビルドができることが望ましい。様々なプラットフォームへの移植作業は、新しい `Makefile' を追加したりソースファイルの一部を修正したりすることによってなされる。C言語のAPIであれば、おそらく以下のファイルのいくつかを修正することになる。もしくはそれらを基に新しいファイルを作ってもよい。

    + +
      +
    • Makefile.in : `./configure' に利用され、`Makefile' のベースとなる。
    • +
    • myconf.h : システム依存の設定ファイル。
    • +
    • depot.h : 基本APIのヘッダ。
    • +
    • curia.h : 拡張APIのヘッダ。
    • +
    • relic.h : NDBM互換APIのヘッダ。
    • +
    • hovel.h : GDBM互換APIのヘッダ。
    • +
    • cabin.h : ユーティリティAPIのヘッダ。
    • +
    • villa.h : 上級APIのヘッダ。
    • +
    • vista.h : 拡張上級APIのヘッダ。
    • +
    • odeum.h : 転置APIのヘッダ。
    • +
    • myconf.c : システム依存の実装。
    • +
    • depot.c : 基本APIの実装。
    • +
    • curia.c : 拡張APIの実装。
    • +
    • relic.c : NDBM互換APIの実装。
    • +
    • hovel.c : GDBM互換APIの実装。
    • +
    • cabin.c : ユーティリティAPIの実装。
    • +
    • villa.c : 上級APIの実装。
    • +
    • vista.c : 拡張上級APIの実装。
    • +
    • odeum.c : 転置APIの実装。
    • +
    + +

    `fcntl' コールによるファイルロックがサポートされていないプラットフォームでは、`Makefile' で定義される `CFLAGS' マクロに `-DMYNOLOCK' を追加するとよい。その際にはプロセス間の排他制御を行う別の方法を考える必要がある。同様に、`mmap' コールがないプラットフォームでは、`CFLAGS' に `-DMYNOMMAP' を追加するとよい。`mmap' に関しては `malloc' 等を用いたエミュレーションが用意されている。その他のシステムコールが実装されていない場合は、`myconf.h' と `myconf.c' を修正して該当のシステムコールのエミュレーションを行えばよい。

    + +

    C++用のAPIではPOSIXスレッドを使っているので、そのパッケージが実装されていない環境にはC++用APIは移植できない。Java用のAPIではJNIを使っているので、そのヘッダやライブラリの場所に注意すべきである。また、`long long' や `int64' といった型定義にも注意すべきである。PerlやRuby用のAPIでは各々の言語処理系で用意されたビルドコマンドを用いているので、その仕様に精通すべきである。

    + +
    + +

    バグ

    + +

    QDBMの各文書は英語を母国語とする人達によって校正されるべきである。

    + +

    segmentation faultによるクラッシュ、予期せぬデータの消失等の不整合、メモリリーク、その他諸々のバグに関して、既知のもので未修正のものはない。

    + +

    バグを発見したら、是非とも作者にフィードバックしてほしい。その際、QDBMのバージョンと、利用環境のOSとコンパイラのバージョンも教えてほしい。

    + +

    1.7.13より前のバージョンのQDBMで作成したデータベースは、それ以後のバージョンと互換性がない。

    + +
    + +

    よく聞かれる質問

    + +
    +
    Q. : QDBMはSQLをサポートするか。
    +
    A. : QDBMはSQLをサポートしない。QDBMはRDBMS(関係データベース管理システム)ではない。組み込みのRDBMSを求めるなら、SQLiteなどを利用するとよい。
    +
    Q. : 結局のところ、GDBM(NDBM、SDBM、Berkeley DB)とどう違うのか。
    +
    A. : 処理が速い。データベースファイルが小さい。APIが簡潔である。特筆すべきは、レコードの上書きを繰り返す場合の時間的および空間的効率がとてもよく、実用上のスケーラビリティが高いことである。また、レコード数が100万を越えるような大規模なデータベースを構築する際にも、処理が極端に遅くなったり、ファイルのサイズが極端に大きくなったりしない。とはいえ、用途によっては他のDBMやDBMSを使う方が適切かもしれないので、各自で性能や機能の比較をしてみてほしい。
    +
    Q. : 参考文献は何か。
    +
    A. : QDBMの各種アルゴリズムは、主にAho他の `Data Structures and Algorithms'(邦題は「データ構造とアルゴリズム」)およびSedgewickの `Algorithms in C'(邦題は「アルゴリズムC」)の記述に基礎を置いている。
    +
    Q. : どのAPIを使えばよいのか。
    +
    A. : レコードの検索が完全一致だけで済むのなら、Depotを試すとよい。その規模が大きいなら、Curiaを試すとよい。レコードを順序に基づいて参照したいなら、Villaを試すとよい。その規模が大きいなら、Vistaを試すとよい。最大のレコード数を追求するなら、ZLIBかLZOを有効にしてQDBMをビルドし、その上でVistaを用いるのがよい。
    +
    Q. : アプリケーションの良いサンプルコードはあるか。
    +
    A. : 各APIのコマンドのソースコードを参考にしてほしい。`dptsv.c' と `crtsv.c' と `vltsv.c' が最も簡潔である。
    +
    Q. : データベースが壊れたのだが、どうしてか。
    +
    A. : 大抵の場合、あなたのアプリケーションがきちんとデータベースを閉じていないのが原因である。デーモンプロセスであろうが、CGIスクリプトであろうが、アプリケーションが終了する際には必ずデータベースを閉じなければならない。なお、CGIのプロセスはSIGPIPEやSIGTERMによって殺されることがあることにも留意すべきである。
    +
    Q. : QDBMのデータベースはどのくらい堅牢なのか。
    +
    A. : QDBMは絶対的な堅牢性は保証しない。オペレーティングシステムが暴走した際にはデータベースが壊れる可能性がある。Villaのトランザクションはアプリケーションの暴走からデータベースを保護することができるが、オペレーティングシステムの暴走には対処できない。したがって、ミッションクリティカルな用途にQDBMを利用する場合は、データベースの多重化を考慮すべきである。
    +
    Q. : DepotとCuriaのアラインメントの使い方がよくわからないが。
    +
    A. : 上書きモードや連結モードでの書き込みを繰り返す場合に、アラインメントはデータベースファイルのサイズが急激に大きくなるのを防ぐ。アラインメントの適切なサイズはアプリケーションによって異なるので、各自で実験してみてほしい。さしあたりは32くらいにしておくとよい。
    +
    Q. : Villaの性能パラメータの調整がよくわからないが。
    +
    A. : レコードを順番に参照することが多いならば、`lrecmax' と `nidxmax' をより大きくした方がよい。レコードを無作為に参照することが多いならば、それらは小さくした方がよい。RAMに余裕があるならば、`lcnum' と `ncnum' を増やすと性能がかなり向上する。ZLIBやLZOやBZIP2を有効化した場合、`lrecmax' を大きくした方が圧縮効率がよくなる。
    +
    Q. : Villaの圧縮方式としてはZLIBとLZOとBZIP2のどれがよいのか。
    +
    A. : 圧縮率が最も良いのはBZIP2で、処理速度が最も高いのはLZOで、ZLIBはその中間的な特性を持つ。特に理由がない限りはZLIBを使うとよい。ただし、更新が頻繁なデータベースにはLZOが適切で、更新がほとんどないならばBZIP2が適切である。圧縮しないという選択肢よりはLZOを使う方がよい。LZOのライセンスはGNU GPLであることに注意すること。
    +
    Q. : スパースファイルとは何か。
    +
    A. : ホール(一度もデータが書き込まれていないブロック)があるファイルのことである。ファイルシステムがスパースファイルをサポートしている場合、ホールは物理的な記憶装置に割り当てられない。QDBMでは、DP_OSPARSEなどのフラグを用いると、ハッシュのバケット配列は初期化されずにホールとなる。このことを利用して、非常に巨大なハッシュ表を実現することができる。ただし、その性能はファイルシステムの設定に強く依存する。
    +
    Q. : なぜDepotとCuriaにはトランザクション機能がないのか。
    +
    A. : アプリケーションが独自のトランザクション機能を実装している場合には、データベース内部のトランザクションは邪魔になるからである。トランザクションはCabinのマップを使えば簡単に実装できる。
    +
    Q. : 性能を引き出すシステムの設定はどうであるか。
    +
    A. : データベースのサイズと同等以上のRAMをマシンに搭載することが望ましい。そして、I/Oバッファのサイズを大きくし、ダーティバッファをフラッシュする頻度が少なくするように設定するとよい。ファイルシステムの選択も重要である。Linux上では、通常はEXT2が最高速であるが、EXT3の `writeback' モードの方が速いこともある。ReiserFSはそれなりである。EXT3のその他のモードはかなり遅い。他のファイルシステムに関しては各自で実験してみてほしい。
    +
    Q. : `gcc' の代わりに `cc' を使ってビルドできるか。
    +
    A. : `LTmakefile' を使えばできる。
    +
    Q. : Visual C++を使ってビルドできるか。
    +
    A. : できる。`Makefile' の代わりに `VCmakefile' を使うこと。
    +
    Q. : 他にQDBMを利用できる言語はあるか。
    +
    A. : PHP、Scheme(Gauche)、OCaml用のインタフェースが既に公開されているようである。それ以外の言語については、必要なら自分で作ってほしい。
    +
    Q. : 「QDBM」とはどういう意味なのか。
    +
    A. : 「QDBM」は「Quick Database Manager」の略である。高速に動作するという意味と、アプリケーションの開発が迅速にできるという意味が込められている。
    +
    Q. : 各APIの名前はどういう意味なのか。どう発音するのか。
    +
    A. : 5文字の英単語から適当に選択しただけで、深い意味はない。なお、「depot」は、空港、倉庫、補給所など、物質が集まる場所を意味するらしい。発音を片仮名で表現するなら「ディーポゥ」が妥当だろう。「curia」は、宮廷、法廷など、権威が集まる場所を意味するらしい。発音を片仮名で表現するなら「キュリア」が妥当だろう。「relic」は、遺物、遺跡など、過去の残骸を意味するらしい。発音を片仮名で表現するなら「レリック」が妥当だろう。「hovel」は、小屋、物置、離れ家など、粗末な建物を意味するらしい。発音を片仮名で表現するなら「ハヴル」が妥当だろう。「cabin」は、機室、客室、小屋など、簡易的な居住空間を意味するらしい。発音を片仮名で表現するなら「キャビン」が妥当だろう。「villa」は、別荘、郊外住宅など、都会風でない住居を意味するらしい。発音を片仮名で表現するなら「ヴィラ」が妥当だろう。「vista」は、予想、展望など、遠くを見渡すことを意味するらしい。発音を片仮名で表現するなら「ヴィスタ」が妥当だろう。「odeum」は、音楽堂、劇場など、音楽や詩吟を行う建物を意味するらしい。発音を片仮名で表現するなら「オディアム」が妥当だろう。
    +
    + +
    + +

    ライセンス

    + +

    QDBMはフリーソフトウェアである。あなたは、Free Software Foundationが公表したGNU Lesser General Public Licenseのバージョン2.1あるいはそれ以降の各バージョンの中からいずれかを選択し、そのバージョンが定める条項に従ってQDBMを再頒布または変更することができる。

    + +

    QDBMは有用であると思われるが、頒布にあたっては、市場性及び特定目的適合性についての暗黙の保証を含めて、いかなる保証も行なわない。詳細についてはGNU Lesser General Public Licenseを読んでほしい。

    + +

    あなたは、QDBMと一緒にGNU Lesser General Public Licenseの写しを受け取っているはずである(`COPYING' ファイルを参照)。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA へ連絡してほしい。

    + +

    QDBMは平林幹雄が作成した。作者と連絡をとるには、`mikio@users.sourceforge.net' 宛に電子メールを送ってほしい。ただし、質問やバグレポートなど、他のユーザと共有できる話題はメーリングリストに送ってほしい。メーリングリストの参加方法については、`http://lists.sourceforge.net/lists/listinfo/qdbm-users' を参照すること。

    + +
    + + + + + + 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 @@ + + + + + + + + + + + + + + + +Specifications of QDBM Version 1 + + + + + +

    Fundamental Specifications of QDBM Version 1

    + +
    Copyright (C) 2000-2007 Mikio Hirabayashi
    +
    Last Update: Thu, 26 Oct 2006 15:00:20 +0900
    + + +
    + +

    Table of Contents

    + +
      +
    1. Overview
    2. +
    3. Features
    4. +
    5. Installation
    6. +
    7. Depot: Basic API
    8. +
    9. Commands for Depot
    10. +
    11. Curia: Extended API
    12. +
    13. Commands for Curia
    14. +
    15. Relic: NDBM-compatible API
    16. +
    17. Commands for Relic
    18. +
    19. Hovel: GDBM-compatible API
    20. +
    21. Commands for Hovel
    22. +
    23. Cabin: Utility API
    24. +
    25. Commands for Cabin
    26. +
    27. Villa: Advanced API
    28. +
    29. Commands for Villa
    30. +
    31. Odeum: Inverted API
    32. +
    33. Commands for Odeum
    34. +
    35. File Format
    36. +
    37. Porting
    38. +
    39. Bugs
    40. +
    41. Frequently Asked Questions
    42. +
    43. Copying
    44. +
    + +
    + +

    Overview

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +
    + +

    Features

    + +

    Effective Implementation of Hash Database

    + +

    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.

    + +

    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)'.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    Useful Implementation of B+ Tree Database

    + +

    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)'.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    Simple but Various Interfaces

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    Wide Portability

    + +

    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.

    + +
      +
    • Linux (2.2, 2.4, 2.6) (IA32, IA64, AMD64, PA-RISC, Alpha, PowerPC, M68000, ARM)
    • +
    • FreeBSD (4.9, 5.0, 5.1, 5.2, 5.3) (IA32, IA64, SPARC, Alpha)
    • +
    • NetBSD (1.6) (IA32)
    • +
    • OpenBSD (3.4) (IA32)
    • +
    • SunOS (5.6, 5.7, 5.8, 5.9, 5.10) (IA32, SPARC)
    • +
    • HP-UX (11.11, 11.23) (IA64, PA-RISC)
    • +
    • AIX (5.2) (POWER)
    • +
    • Windows (2000, XP) (IA32, IA64, AMD64) (Cygwin, MinGW, Visual C++)
    • +
    • Mac OS X (10.2, 10.3, 10.4) (IA32, PowerPC)
    • +
    • Tru64 (5.1) (Alpha)
    • +
    • RISC OS (5.03) (ARM)
    • +
    + +

    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.

    + +
    + +

    Installation

    + +

    Preparation

    + +

    To install QDBM from a source package, GCC of 2.8 or later version and `make' are required.

    + +

    When an archive file of QDBM is extracted, change the current working directory to the generated directory and perform installation.

    + +

    Usual Steps

    + +

    Follow the procedures below on Linux, BSD, or SunOS.

    + +

    Run the configuration script.

    + +
    ./configure
    +
    + +

    Build programs.

    + +
    make
    +
    + +

    Perform self-diagnostic test.

    + +
    make check
    +
    + +

    Install programs. This operation must be carried out by the root user.

    + +
    make install
    +
    + +

    Using GNU Libtool

    + +

    If above steps do not work, try the following steps. This way needs GNU Libtool of 1.5 or later version.

    + +

    Run the configuration script.

    + +
    ./configure
    +
    + +

    Build programs.

    + +
    make -f LTmakefile
    +
    + +

    Perform self-diagnostic test.

    + +
    make -f LTmakefile check
    +
    + +

    Install programs. This operation must be carried out by the root user.

    + +
    make -f LTmakefile install
    +
    + +

    Result

    + +

    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'.

    + +
    /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
    +
    + +

    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'.

    + +

    To uninstall QDBM, execute the following command after `./configure'. This operation must be carried out by the root user.

    + +
    make uninstall
    +
    + +

    If an old version of QDBM is installed on your system, uninstall it before installation of a new one.

    + +

    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.

    + +

    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.

    + +
    rpm -ivh qdbm-1.x.x-x.i386.rpm
    +
    + +

    For Windows

    + +

    On Windows (Cygwin), you should follow the procedures below for installation.

    + +

    Run the configuration script.

    + +
    ./configure
    +
    + +

    Build programs.

    + +
    make win
    +
    + +

    Perform self-diagnostic test.

    + +
    make check-win
    +
    + +

    Install programs. As well, perform `make uninstall-win' to uninstall them.

    + +
    make install-win
    +
    + +

    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'.

    + +

    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.

    + +

    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.

    + +

    For Mac OS X

    + +

    On Mac OS X (Darwin), you should follow the procedures below for installation.

    + +

    Run the configuration script.

    + +
    ./configure
    +
    + +

    Build programs.

    + +
    make mac
    +
    + +

    Perform self-diagnostic test.

    + +
    make check-mac
    +
    + +

    Install programs. As well, perform `make uninstall-mac' to uninstall them.

    + +
    make install-mac
    +
    + +

    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'.

    + +

    For HP-UX

    + +

    On HP-UX, you should follow the procedures below for installation.

    + +

    Run the configuration script.

    + +
    ./configure
    +
    + +

    Build programs.

    + +
    make hpux
    +
    + +

    Perform self-diagnostic test.

    + +
    make check-hpux
    +
    + +

    Install programs. As well, perform `make uninstall-hpux' to uninstall them.

    + +
    make install-hpux
    +
    + +

    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'.

    + +

    For RISC OS

    + +

    On RISC OS, you should follow the procedures below for installation.

    + +

    Build programs. As `cc' is used for compilation by default, if you want to use `gcc', add the argument `CC=gcc'.

    + +
    make -f RISCmakefile
    +
    + +

    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.

    + +

    Detail Configurations

    + +

    You can configure building processes by the following optional arguments of `./configure'.

    + +
      +
    • --enable-debug : build for debugging. Enable debugging symbols, do not perform optimization, and perform static linking.
    • +
    • --enable-devel : build for development. Enable debugging symbols, perform optimization, and perform dynamic linking.
    • +
    • --enable-stable : build for stable release. Perform conservative optimization, and perform dynamic linking.
    • +
    • --enable-pthread : feature POSIX thread and treat global variables as thread specific data.
    • +
    • --disable-lock : build for environments without file locking.
    • +
    • --disable-mmap : build for environments without memory mapping.
    • +
    • --enable-zlib : feature ZLIB compression for B+ tree and inverted index.
    • +
    • --enable-lzo : feature LZO compression for B+ tree and inverted index.
    • +
    • --enable-bzip : feature BZIP2 compression for B+ tree and inverted index.
    • +
    • --enable-iconv : feature ICONV utilities for conversion of character encodings.
    • +
    + +

    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.

    + +

    Because the license of LZO is GNU GPL, note that applications linking to `liblzo2.*' should meet commitments of GNU GPL.

    + +
    + +

    Depot: Basic API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +
    +
    #include <depot.h>
    +
    #include <stdlib.h>
    +
    + +

    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.

    + +

    API

    + +

    The external variable `dpversion' is the string containing the version information.

    + +
    +
    extern const char *dpversion;
    +
    + +

    The external variable `dpecode' is assigned with the last happened error code. Refer to `depot.h' for details of the error codes.

    + +
    +
    extern int dpecode;
    +
    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'.
    +
    + +

    The function `dperrmsg' is used in order to get a message string corresponding to an error code.

    + +
    +
    const char *dperrmsg(int ecode);
    +
    `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.
    +
    + +

    The function `dpopen' is used in order to get a database handle.

    + +
    +
    DEPOT *dpopen(const char *name, int omode, int bnum);
    +
    `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.
    +
    + +

    The function `dpclose' is used in order to close a database handle.

    + +
    +
    int dpclose(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dpput' is used in order to store a record.

    + +
    +
    int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `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.
    +
    + +

    The function `dpout' is used in order to delete a record.

    + +
    +
    int dpout(DEPOT *depot, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `dpget' is used in order to retrieve a record.

    + +
    +
    char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `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.
    +
    + +

    The function `dpgetwb' is used in order to 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);
    +
    `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.
    +
    + +

    The function `dpvsiz' is used in order to get the size of the value of a record.

    + +
    +
    int dpvsiz(DEPOT *depot, const char *kbuf, int ksiz);
    +
    `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'.
    +
    + +

    The function `dpiterinit' is used in order to initialize the iterator of a database handle.

    + +
    +
    int dpiterinit(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dpiternext' is used in order to get the next key of the iterator.

    + +
    +
    char *dpiternext(DEPOT *depot, int *sp);
    +
    `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.
    +
    + +

    The function `dpsetalign' is used in order to set alignment of a database handle.

    + +
    +
    int dpsetalign(DEPOT *depot, int align);
    +
    `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.
    +
    + +

    The function `dpsetfbpsiz' is used in order to set the size of the free block pool of a database handle.

    + +
    +
    int dpsetfbpsiz(DEPOT *depot, int size);
    +
    `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.
    +
    + +

    The function `dpsync' is used in order to synchronize updating contents with the file and the device.

    + +
    +
    int dpsync(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dpoptimize' is used in order to optimize a database.

    + +
    +
    int dpoptimize(DEPOT *depot, int bnum);
    +
    `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.
    +
    + +

    The function `dpname' is used in order to get the name of a database.

    + +
    +
    char *dpname(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dpfsiz' is used in order to get the size of a database file.

    + +
    +
    int dpfsiz(DEPOT *depot);
    +
    `depot' specifies a database handle. If successful, the return value is the size of the database file, else, it is -1.
    +
    + +

    The function `dpbnum' is used in order to get the number of the elements of the bucket array.

    + +
    +
    int dpbnum(DEPOT *depot);
    +
    `depot' specifies a database handle. If successful, the return value is the number of the elements of the bucket array, else, it is -1.
    +
    + +

    The function `dpbusenum' is used in order to get the number of the used elements of the bucket array.

    + +
    +
    int dpbusenum(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dprnum' is used in order to get the number of the records stored in a database.

    + +
    +
    int dprnum(DEPOT *depot);
    +
    `depot' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.
    +
    + +

    The function `dpwritable' is used in order to check whether a database handle is a writer or not.

    + +
    +
    int dpwritable(DEPOT *depot);
    +
    `depot' specifies a database handle. The return value is true if the handle is a writer, false if not.
    +
    + +

    The function `dpfatalerror' is used in order to check whether a database has a fatal error or not.

    + +
    +
    int dpfatalerror(DEPOT *depot);
    +
    `depot' specifies a database handle. The return value is true if the database has a fatal error, false if not.
    +
    + +

    The function `dpinode' is used in order to get the inode number of a database file.

    + +
    +
    int dpinode(DEPOT *depot);
    +
    `depot' specifies a database handle. The return value is the inode number of the database file.
    +
    + +

    The function `dpmtime' is used in order to get the last modified time of a database.

    + +
    +
    time_t dpmtime(DEPOT *depot);
    +
    `depot' specifies a database handle. The return value is the last modified time of the database.
    +
    + +

    The function `dpfdesc' is used in order to get the file descriptor of a database file.

    + +
    +
    int dpfdesc(DEPOT *depot);
    +
    `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.
    +
    + +

    The function `dpremove' is used in order to remove a database file.

    + +
    +
    int dpremove(const char *name);
    +
    `name' specifies the name of a database file. If successful, the return value is true, else, it is false.
    +
    + +

    The function `dprepair' is used in order to repair a broken database file.

    + +
    +
    int dprepair(const char *name);
    +
    `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.
    +
    + +

    The function `dpexportdb' is used in order to dump all records as endian independent data.

    + +
    +
    int dpexportdb(DEPOT *depot, const char *name);
    +
    `depot' specifies a database handle. `name' specifies the name of an output file. If successful, the return value is true, else, it is false.
    +
    + +

    The function `dpimportdb' is used in order to load all records from endian independent data.

    + +
    +
    int dpimportdb(DEPOT *depot, const char *name);
    +
    `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.
    +
    + +

    The function `dpsnaffle' is used in order to retrieve a record directly from a database file.

    + +
    +
    char *dpsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + +

    The function `dpinnerhash' is a hash function used inside Depot.

    + +
    +
    int dpinnerhash(const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `dpouterhash' is a hash function which is independent from the hash functions used inside Depot.

    + +
    +
    int dpouterhash(const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `dpprimenum' is used in order to get a natural prime number not less than a number.

    + +
    +
    int dpprimenum(int num);
    +
    `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.
    +
    + +

    Examples

    + +

    The following example stores and retrieves a phone number, using the name as the key.

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    The following example shows all records of the database.

    + +
    #include <depot.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    Notes

    + +

    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'.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +
    + +

    Commands for Depot

    + +

    Depot has the following command line interfaces.

    + +

    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.

    + +
    +
    dpmgr create [-s] [-bnum num] name
    +
    Create a database file.
    +
    dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val
    +
    Store a record with a key and a value.
    +
    dpmgr out [-kx|-ki] name key
    +
    Delete a record with a key.
    +
    dpmgr get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key
    +
    Retrieve a record with a key and output it to the standard output.
    +
    dpmgr list [-nl] [-k|-v] [-ox] name
    +
    List all keys and values delimited with tab and line-feed to the standard output.
    +
    dpmgr optimize [-bnum num] [-na] name
    +
    Optimize a database.
    +
    dpmgr inform [-nl] name
    +
    Output miscellaneous information to the standard output.
    +
    dpmgr remove name
    +
    Remove a database file.
    +
    dpmgr repair name
    +
    Repair a broken database file.
    +
    dpmgr exportdb name file
    +
    Dump all records as endian independent data.
    +
    dpmgr importdb [-bnum num] name file
    +
    Load all records from endian independent data.
    +
    dpmgr snaffle [-kx|-ki] [-ox] [-n] name key
    +
    Retrieve a record from a locked database with a key and output it to the standard output.
    +
    dpmgr version
    +
    Output version information of QDBM to the standard output.
    +
    + +

    Options feature the following.

    + +
      +
    • -s : make the file sparse.
    • +
    • -bnum num : specify the number of the elements of the bucket array.
    • +
    • -kx : treat `key' as a binary expression of hexadecimal notation.
    • +
    • -ki : treat `key' as an integer expression of decimal notation.
    • +
    • -vx : treat `val' as a binary expression of hexadecimal notation.
    • +
    • -vi : treat `val' as an integer expression of decimal notation.
    • +
    • -vf : read the value from a file specified with `val'.
    • +
    • -keep : specify the storing mode for `DP_DKEEP'.
    • +
    • -cat : specify the storing mode for `DP_DCAT'.
    • +
    • -na : do not set alignment.
    • +
    • -nl : open the database without file locking.
    • +
    • -start : specify the beginning offset of a value to fetch.
    • +
    • -max : specify the max size of a value to fetch.
    • +
    • -ox : treat the output as a binary expression of hexadecimal notation.
    • +
    • -n : do not output the tailing newline.
    • +
    • -k : output keys only.
    • +
    • -v : output values only.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    dptest write [-s] name rnum bnum
    +
    Store records with keys of 8 bytes. They change as `00000001', `00000002'...
    +
    dptest read [-wb] name
    +
    Retrieve all records of the database above.
    +
    dptest rcat [-c] name rnum bnum pnum align fbpsiz
    +
    Store records with partway duplicated keys using concatenate mode.
    +
    dptest combo name
    +
    Perform combination test of various operations.
    +
    dptest wicked [-c] name rnum
    +
    Perform updating operations selected at random.
    +
    + +

    Options feature the following.

    + +
      +
    • -s : make the file sparse.
    • +
    • -wb : use the function `dpgetwb' instead of the function `dpget'.
    • +
    • -c : perform comparison test with map of Cabin.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    dptsv import [-bnum num] [-bin] name
    +
    Create a database from TSV.
    +
    dptsv export [-bin] name
    +
    Write TSV data of a database.
    +
    + +

    Options feature the following.

    + +
      +
    • -bnum num : specify the number of the elements of the bucket array.
    • +
    • -bin : treat records as Base64 format.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    cat /etc/passwd | tr ':' '\t' | dptsv import casket
    +
    + +

    Thus, to retrieve the information of a user `mikio', perform the following command.

    + +
    dpmgr get casket mikio
    +
    + +

    It is easy to implement functions upsides with these commands, using the API of Depot.

    + +
    + +

    Curia: Extended API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +
    +
    #include <depot.h>
    +
    #include <curia.h>
    +
    #include <stdlib.h>
    +
    + +

    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.

    + +

    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.

    + +

    API

    + +

    The function `cropen' is used in order to get a database handle.

    + +
    +
    CURIA *cropen(const char *name, int omode, int bnum, int dnum);
    +
    `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.
    +
    + +

    The function `crclose' is used in order to close a database handle.

    + +
    +
    int crclose(CURIA *curia);
    +
    `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.
    +
    + +

    The function `crput' is used in order to store a record.

    + +
    +
    int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `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.
    +
    + +

    The function `crout' is used in order to delete a record.

    + +
    +
    int crout(CURIA *curia, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `crget' is used in order to retrieve a record.

    + +
    +
    char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `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.
    +
    + +

    The function `crgetwb' is used in order to 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);
    +
    `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.
    +
    + +

    The function `crvsiz' is used in order to get the size of the value of a record.

    + +
    +
    int crvsiz(CURIA *curia, const char *kbuf, int ksiz);
    +
    `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'.
    +
    + +

    The function `criterinit' is used in order to initialize the iterator of a database handle.

    + +
    +
    int criterinit(CURIA *curia);
    +
    `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.
    +
    + +

    The function `criternext' is used in order to get the next key of the iterator.

    + +
    +
    char *criternext(CURIA *curia, int *sp);
    +
    `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.
    +
    + +

    The function `crsetalign' is used in order to set alignment of a database handle.

    + +
    +
    int crsetalign(CURIA *curia, int align);
    +
    `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.
    +
    + +

    The function `crsetfbpsiz' is used in order to set the size of the free block pool of a database handle.

    + +
    +
    int crsetfbpsiz(CURIA *curia, int size);
    +
    `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.
    +
    + +

    The function `crsync' is used in order to synchronize updating contents with the files and the devices.

    + +
    +
    int crsync(CURIA *curia);
    +
    `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.
    +
    + +

    The function `croptimize' is used in order to optimize a database.

    + +
    +
    int croptimize(CURIA *curia, int bnum);
    +
    `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.
    +
    + +

    The function `crname' is used in order to get the name of a database.

    + +
    +
    char *crname(CURIA *curia);
    +
    `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.
    +
    + +

    The function `crfsiz' is used in order to get the total size of database files.

    + +
    +
    int crfsiz(CURIA *curia);
    +
    `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.
    +
    + +

    The function `crfsizd' is used in order to get the total size of database files as double-precision floating-point number.

    + +
    +
    double crfsizd(CURIA *curia);
    +
    `curia' specifies a database handle. If successful, the return value is the total size of the database files, else, it is -1.0.
    +
    + +

    The function `crbnum' is used in order to get the total number of the elements of each bucket array.

    + +
    +
    int crbnum(CURIA *curia);
    +
    `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.
    +
    + +

    The function `crbusenum' is used in order to get the total number of the used elements of each bucket array.

    + +
    +
    int crbusenum(CURIA *curia);
    +
    `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.
    +
    + +

    The function `crrnum' is used in order to get the number of the records stored in a database.

    + +
    +
    int crrnum(CURIA *curia);
    +
    `curia' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.
    +
    + +

    The function `crwritable' is used in order to check whether a database handle is a writer or not.

    + +
    +
    int crwritable(CURIA *curia);
    +
    `curia' specifies a database handle. The return value is true if the handle is a writer, false if not.
    +
    + +

    The function `crfatalerror' is used in order to check whether a database has a fatal error or not.

    + +
    +
    int crfatalerror(CURIA *curia);
    +
    `curia' specifies a database handle. The return value is true if the database has a fatal error, false if not.
    +
    + +

    The function `crinode' is used in order to get the inode number of a database directory.

    + +
    +
    int crinode(CURIA *curia);
    +
    `curia' specifies a database handle. The return value is the inode number of the database directory.
    +
    + +

    The function `crmtime' is used in order to get the last modified time of a database.

    + +
    +
    time_t crmtime(CURIA *curia);
    +
    `curia' specifies a database handle. The return value is the last modified time of the database.
    +
    + +

    The function `crremove' is used in order to remove a database directory.

    + +
    +
    int crremove(const char *name);
    +
    `name' specifies the name of a database directory. If successful, the return value is true, else, it is false.
    +
    + +

    The function `crrepair' is used in order to repair a broken database directory.

    + +
    +
    int crrepair(const char *name);
    +
    `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.
    +
    + +

    The function `crexportdb' is used in order to dump all records as endian independent data.

    + +
    +
    int crexportdb(CURIA *curia, const char *name);
    +
    `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.
    +
    + +

    The function `crimportdb' is used in order to load all records from endian independent data.

    + +
    +
    int crimportdb(CURIA *curia, const char *name);
    +
    `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.
    +
    + +

    The function `crsnaffle' is used in order to retrieve a record directly from a database directory.

    + +
    +
    char *crsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + +

    The function `crputlob' is used in order to store a large object.

    + +
    +
    int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `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.
    +
    + +

    The function `croutlob' is used in order to delete a large object.

    + +
    +
    int croutlob(CURIA *curia, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `crgetlob' is used in order to retrieve a large object.

    + +
    +
    char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
    +
    `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.
    +
    + +

    The function `crgetlobfd' is used in order to get the file descriptor of a large object.

    + +
    +
    int crgetlobfd(CURIA *curia, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `crvsizlob' is used in order to get the size of the value of a large object.

    + +
    +
    int crvsizlob(CURIA *curia, const char *kbuf, int ksiz);
    +
    `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'.
    +
    + +

    The function `crrnumlob' is used in order to get the number of the large objects stored in a database.

    + +
    +
    int crrnumlob(CURIA *curia);
    +
    `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.
    +
    + +

    Examples

    + +

    The following example stores and retrieves a phone number, using the name as the key.

    + +
    #include <depot.h>
    +#include <curia.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    The following example shows all records of the database.

    + +
    #include <depot.h>
    +#include <curia.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    Notes

    + +

    How to build programs using Curia is the same as the case of Depot.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +
    + +

    Commands for Curia

    + +

    Curia has the following command line interfaces.

    + +

    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.

    + +
    +
    crmgr create [-s] [-bnum num] [-dnum num] name
    +
    Create a database file.
    +
    crmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] name key val
    +
    Store a record with a key and a value.
    +
    crmgr out [-kx|-ki] [-lob] name key
    +
    Delete a record with a key.
    +
    crmgr get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key
    +
    Retrieve a record with a key and output it to the standard output.
    +
    crmgr list [-nl] [-k|-v] [-ox] name
    +
    List all keys and values delimited with tab and line-feed to the standard output.
    +
    crmgr optimize [-bnum num] [-na] name
    +
    Optimize a database.
    +
    crmgr inform [-nl] name
    +
    Output miscellaneous information to the standard output.
    +
    crmgr remove name
    +
    Remove a database directory.
    +
    crmgr repair name
    +
    Repair a broken database directory.
    +
    crmgr exportdb name dir
    +
    Dump all records as endian independent data.
    +
    crmgr importdb [-bnum num] [-dnum num] name dir
    +
    Load all records from endian independent data.
    +
    crmgr snaffle [-kx|-ki] [-ox] [-n] name key
    +
    Retrieve a record from a locked database with a key and output it to the standard output.
    +
    crmgr version
    +
    Output version information of QDBM to the standard output.
    +
    + +

    Options feature the following.

    + +
      +
    • -s : make the files sparse.
    • +
    • -bnum num : specify the number of elements of each bucket array.
    • +
    • -dnum num : specify the number of division of the database.
    • +
    • -kx : treat `key' as a binary expression of hexadecimal notation.
    • +
    • -ki : treat `key' as an integer expression of decimal notation.
    • +
    • -vx : treat `val' as a binary expression of hexadecimal notation.
    • +
    • -vi : treat `val' as an integer expression of decimal notation.
    • +
    • -vf : read the value from a file specified with `val'.
    • +
    • -keep : specify the storing mode for `CR_DKEEP'.
    • +
    • -cat : specify the storing mode for `CR_DCAT'.
    • +
    • -na : do not set alignment.
    • +
    • -nl : open the database without file locking.
    • +
    • -start : specify the beginning offset of a value to fetch.
    • +
    • -max : specify the max size of a value to fetch.
    • +
    • -ox : treat the output as a binary expression of hexadecimal notation.
    • +
    • -lob : handle large objects.
    • +
    • -n : do not output the tailing newline.
    • +
    • -k : output keys only.
    • +
    • -v : output values only.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    crtest write [-s] [-lob] name rnum bnum dnum
    +
    Store records with keys of 8 bytes. They change as `00000001', `00000002'...
    +
    crtest read [-wb] [-lob] name
    +
    Retrieve all records of the database above.
    +
    crtest rcat [-c] name rnum bnum dnum pnum align fbpsiz
    +
    Store records with partway duplicated keys using concatenate mode.
    +
    crtest combo name
    +
    Perform combination test of various operations.
    +
    crtest wicked [-c] name rnum
    +
    Perform updating operations selected at random.
    +
    + +

    Options feature the following.

    + +
      +
    • -s : make the files sparse.
    • +
    • -lob : handle large objects.
    • +
    • -wb : use the function `crgetwb' instead of the function `crget'.
    • +
    • -c : perform comparison test with map of Cabin.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    crtsv import [-bnum num] [-dnum num] [-bin] name
    +
    Create a database from TSV.
    +
    crtsv export [-bin] name
    +
    Write TSV data of a database.
    +
    + +

    Options feature the following.

    + +
      +
    • -bnum num : specify the number of the elements of the bucket array.
    • +
    • -dnum num : specify the number of division of the database.
    • +
    • -bin : treat records as Base64 format.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    cat /etc/passwd | tr ':' '\t' | crtsv import casket
    +
    + +

    Thus, to retrieve the information of a user `mikio', perform the following command.

    + +
    crmgr get casket mikio
    +
    + +

    It is easy to implement functions upsides with these commands, using the API of Curia.

    + +
    + +

    Relic: NDBM-compatible API

    + +

    Overview

    + +

    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'.

    + +

    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.

    + +

    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.

    + +
    +
    #include <relic.h>
    +
    #include <stdlib.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    #include <fcntl.h>
    +
    + +

    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.

    + +

    API

    + +

    Structures of `datum' type is used in order to give and receive data of keys and values with functions of Relic.

    + +
    +
    typedef struct { void *dptr; size_t dsize; } datum;
    +
    `dptr' specifies the pointer to the region of a key or a value. `dsize' specifies the size of the region.
    +
    + +

    The function `dbm_open' is used in order to get a database handle.

    + +
    +
    DBM *dbm_open(char *name, int flags, int mode);
    +
    `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.
    +
    + +

    The function `dbm_close' is used in order to close a database handle.

    + +
    +
    void dbm_close(DBM *db);
    +
    `db' specifies a database handle. Because the region of the closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `dbm_store' is used in order to store a record.

    + +
    +
    int dbm_store(DBM *db, datum key, datum content, int flags);
    +
    `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.
    +
    + +

    The function `dbm_delete' is used in order to delete a record.

    + +
    +
    int dbm_delete(DBM *db, datum key);
    +
    `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.
    +
    + +

    The function `dbm_fetch' is used in order to retrieve a record.

    + +
    +
    datum dbm_fetch(DBM *db, datum key);
    +
    `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.
    +
    + +

    The function `dbm_firstkey' is used in order to get the first key of a database.

    + +
    +
    datum dbm_firstkey(DBM *db);
    +
    `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.
    +
    + +

    The function `dbm_nextkey' is used in order to get the next key of a database.

    + +
    +
    datum dbm_nextkey(DBM *db);
    +
    `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.
    +
    + +

    The function `dbm_error' is used in order to check whether a database has a fatal error or not.

    + +
    +
    int dbm_error(DBM *db);
    +
    `db' specifies a database handle. The return value is true if the database has a fatal error, false if not.
    +
    + +

    The function `dbm_clearerr' has no effect.

    + +
    +
    int dbm_clearerr(DBM *db);
    +
    `db' specifies a database handle. The return value is 0. The function is only for compatibility.
    +
    + +

    The function `dbm_rdonly' is used in order to check whether a handle is read-only or not.

    + +
    +
    int dbm_rdonly(DBM *db);
    +
    `db' specifies a database handle. The return value is true if the handle is read-only, or false if not read-only.
    +
    + +

    The function `dbm_dirfno' is used in order to get the file descriptor of a directory file.

    + +
    +
    int dbm_dirfno(DBM *db);
    +
    `db' specifies a database handle. The return value is the file descriptor of the directory file.
    +
    + +

    The function `dbm_pagfno' is used in order to get the file descriptor of a data file.

    + +
    +
    int dbm_pagfno(DBM *db);
    +
    `db' specifies a database handle. The return value is the file descriptor of the data file.
    +
    + +

    Examples

    + +

    The following example stores and retrieves a phone number, using the name as the key.

    + +
    #include <relic.h>
    +#include <stdlib.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < val.dsize; i++){
    +      putchar(((char *)val.dptr)[i]);
    +    }
    +    putchar('\n');
    +  } else {
    +    perror("dbm_fetch");
    +  }
    +
    +  /* close the database */
    +  dbm_close(db);
    +
    +  return 0;
    +}
    +
    + +

    Notes

    + +

    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'.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +
    + +

    Commands for Relic

    + +

    Relic has the following command line interfaces.

    + +

    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.

    + +
    +
    rlmgr create name
    +
    Create a database file.
    +
    rlmgr store [-kx] [-vx|-vf] [-insert] name key val
    +
    Store a record with a key and a value.
    +
    rlmgr delete [-kx] name key
    +
    Delete a record with a key.
    +
    rlmgr fetch [-kx] [-ox] [-n] name key
    +
    Retrieve a record with a key and output to the standard output.
    +
    rlmgr list [-ox] name
    +
    List all keys and values delimited with tab and line-feed to the standard output.
    +
    + +

    Options feature the following.

    + +
      +
    • -kx : treat `key' as a binary expression of hexadecimal notation.
    • +
    • -vx : treat `val' as a binary expression of hexadecimal notation.
    • +
    • -vf : read the value from a file specified with `val'.
    • +
    • -insert : specify the storing mode for `DBM_INSERT'.
    • +
    • -ox : treat the output as a binary expression of hexadecimal notation.
    • +
    • -n : do not output the tailing newline.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    +
    rltest write name rnum
    +
    Store records with keys of 8 bytes. They change as `00000001', `00000002'...
    +
    rltest read name rnum
    +
    Retrieve records of the database above.
    +
    + +

    This command returns 0 on success, another on failure.

    + +
    + +

    Hovel: GDBM-compatible API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +
    +
    #include <hovel.h>
    +
    #include <stdlib.h>
    +
    #include <sys/types.h>
    +
    #include <sys/stat.h>
    +
    + +

    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.

    + +

    API

    + +

    Structures of `datum' type is used in order to give and receive data of keys and values with functions of Hovel.

    + +
    +
    typedef struct { char *dptr; size_t dsize; } datum;
    +
    `dptr' specifies the pointer to the region of a key or a value. `dsize' specifies the size of the region.
    +
    + +

    The external variable `gdbm_version' is the string containing the version information.

    + +
    +
    extern char *gdbm_version;
    +
    + +

    The external variable `gdbm_errno' is assigned with the last happened error code. Refer to `hovel.h' for details of the error codes.

    + +
    +
    extern gdbm_error gdbm_errno;
    +
    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'.
    +
    + +

    The function `gdbm_strerror' is used in order to get a message string corresponding to an error code.

    + +
    +
    char *gdbm_strerror(gdbm_error gdbmerrno);
    +
    `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.
    +
    + +

    The function `gdbm_open' is used in order to 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));
    +
    `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.
    +
    + +

    The function `gdbm_open2' is used in order to 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);
    +
    `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.
    +
    + +

    The function `gdbm_close' is used in order to close a database handle.

    + +
    +
    void gdbm_close(GDBM_FILE dbf);
    +
    `dbf' specifies a database handle. Because the region of the closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `gdbm_store' is used in order to store a record.

    + +
    +
    int gdbm_store(GDBM_FILE dbf, datum key, datum content, int flag);
    +
    `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.
    +
    + +

    The function `gdbm_delete' is used in order to delete a record.

    + +
    +
    int gdbm_delete(GDBM_FILE dbf, datum key);
    +
    `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.
    +
    + +

    The function `gdbm_fetch' is used in order to retrieve a record.

    + +
    +
    datum gdbm_fetch(GDBM_FILE dbf, datum key);
    +
    `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.
    +
    + +

    The function `gdbm_exists' is used in order to check whether a record exists or not.

    + +
    +
    int gdbm_exists(GDBM_FILE dbf, datum key);
    +
    `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.
    +
    + +

    The function `gdbm_firstkey' is used in order to get the first key of a database.

    + +
    +
    datum gdbm_firstkey(GDBM_FILE dbf);
    +
    `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.
    +
    + +

    The function `gdbm_nextkey' is used in order to get the next key of a database.

    + +
    +
    datum gdbm_nextkey(GDBM_FILE dbf, datum key);
    +
    `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.
    +
    + +

    The function `gdbm_sync' is used in order to synchronize updating contents with the file and the device.

    + +
    +
    void gdbm_sync(GDBM_FILE dbf);
    +
    `dbf' specifies a database handle connected as a writer.
    +
    + +

    The function `gdbm_reorganize' is used in order to reorganize a database.

    + +
    +
    int gdbm_reorganize(GDBM_FILE dbf);
    +
    `dbf' specifies a database handle connected as a writer. If successful, the return value is 0, else -1.
    +
    + +

    The function `gdbm_fdesc' is used in order to get the file descriptor of a database file.

    + +
    +
    int gdbm_fdesc(GDBM_FILE dbf);
    +
    `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.
    +
    + +

    The function `gdbm_setopt' has no effect.

    + +
    +
    int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size);
    +
    `dbf' specifies a database handle. `option' is ignored. `size' is ignored. The return value is 0. The function is only for compatibility.
    +
    + +

    Examples

    + +

    The following example stores and retrieves a phone number, using the name as the key.

    + +
    #include <hovel.h>
    +#include <stdlib.h>
    +#include <sys/types.h>
    +#include <sys/stat.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < 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;
    +}
    +
    + +

    Notes

    + +

    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'.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +
    + +

    Commands for Hovel

    + +

    Hovel has the following command line interfaces.

    + +

    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.

    + +
    +
    hvmgr create [-qdbm bnum dnum] [-s] name
    +
    Create a database file.
    +
    hvmgr store [-qdbm] [-kx] [-vx|-vf] [-insert] name key val
    +
    Store a record with a key and a value.
    +
    hvmgr delete [-qdbm] [-kx] name key
    +
    Delete a record with a key.
    +
    hvmgr fetch [-qdbm] [-kx] [-ox] [-n] name key
    +
    Retrieve a record with a key and output to the standard output.
    +
    hvmgr list [-qdbm] [-ox] name
    +
    List all keys and values delimited with tab and line-feed to the standard output.
    +
    hvmgr optimize [-qdbm] name
    +
    Optimize a database.
    +
    + +

    Options feature the following.

    + +
      +
    • -qdbm [bnum dnum] : 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.
    • +
    • -s : make the file sparse.
    • +
    • -kx : treat `key' as a binary expression of hexadecimal notation.
    • +
    • -vx : treat `val' as a binary expression of hexadecimal notation.
    • +
    • -vf : read the value from a file specified with `val'.
    • +
    • -insert : specify the storing mode for `GDBM_INSERT'.
    • +
    • -ox : treat the output as a binary expression of hexadecimal notation.
    • +
    • -n : do not output the trailing newline.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    +
    hvtest write [-qdbm] [-s] name rnum
    +
    Store records with keys of 8 bytes. They changes as `00000001', `00000002'...
    +
    hvtest read [-qdbm] name rnum
    +
    Retrieve records of the database above.
    +
    + +

    Options feature the following.

    + +
      +
    • -qdbm : use `gdbm_open2' and open the handle as Curia.
    • +
    • -s : make the file sparse.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +
    + +

    Cabin: Utility API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +
    +
    #include <cabin.h>
    +
    #include <stdlib.h>
    +
    + +

    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.

    + +

    API

    + +

    The external variable `cbfatalfunc' is the pointer to call back function for handling a fatal error.

    + +
    +
    extern void (*cbfatalfunc)(const char *);
    +
    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.
    +
    + +

    The function `cbmalloc' is used in order to allocate a region on memory.

    + +
    +
    void *cbmalloc(size_t size);
    +
    `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.
    +
    + +

    The function `cbrealloc' is used in order to re-allocate a region on memory.

    + +
    +
    void *cbrealloc(void *ptr, size_t size);
    +
    `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.
    +
    + +

    The function `cbmemdup' is used in order to duplicate a region on memory.

    + +
    +
    char *cbmemdup(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbfree' is used in order to free a region on memory.

    + +
    +
    void cbfree(void *ptr);
    +
    `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.
    +
    + +

    The function `cbglobalgc' is used in order to register the pointer or handle of an object to the global garbage collector.

    + +
    +
    void cbglobalgc(void *ptr, void (*func)(void *));
    +
    `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.
    +
    + +

    The function `cbggcsweep' is used in order to exercise the global garbage collector explicitly.

    + +
    +
    void cbggcsweep(void);
    +
    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.
    +
    + +

    The function `cbvmemavail' is used in order to check availability of allocation of the virtual memory.

    + +
    +
    int cbvmemavail(size_t size);
    +
    `size' specifies the size of region to be allocated newly. The return value is true if allocation should be success, or false if not.
    +
    + +

    The function `cbisort' is used in order to sort an array using insert sort.

    + +
    +
    void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `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.
    +
    + +

    The function `cbssort' is used in order to sort an array using shell sort.

    + +
    +
    void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `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.
    +
    + +

    The function `cbhsort' is used in order to sort an array using heap sort.

    + +
    +
    void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `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.
    +
    + +

    The function `cbqsort' is used in order to sort an array using quick sort.

    + +
    +
    void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
    +
    `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.
    +
    + +

    The function `cbstricmp' is used in order to compare two strings with case insensitive evaluation.

    + +
    +
    int cbstricmp(const char *astr, const char *bstr);
    +
    `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.
    +
    + +

    The function `cbstrfwmatch' is used in order to check whether a string begins with a key.

    + +
    +
    int cbstrfwmatch(const char *str, const char *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.
    +
    + +

    The function `cbstrfwimatch' is used in order to check whether a string begins with a key, with case insensitive evaluation.

    + +
    +
    int cbstrfwimatch(const char *str, const char *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. Upper cases and lower cases of alphabets in ASCII code are not distinguished.
    +
    + +

    The function `cbstrbwmatch' is used in order to check whether a string ends with a key.

    + +
    +
    int cbstrbwmatch(const char *str, const char *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.
    +
    + +

    The function `cbstrbwimatch' is used in order to check whether a string ends with a key, with case insensitive evaluation.

    + +
    +
    int cbstrbwimatch(const char *str, const char *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. Upper cases and lower cases of alphabets in ASCII code are not distinguished.
    +
    + +

    The function `cbstrstrkmp' is used in order to locate a substring in a string using KMP method.

    + +
    +
    char *cbstrstrkmp(const char *haystack, const char *needle);
    +
    `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.
    +
    + +

    The function `cbstrstrkmp' is used in order to locate a substring in a string using BM method.

    + +
    +
    char *cbstrstrbm(const char *haystack, const char *needle);
    +
    `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.
    +
    + +

    The function `cbstrtoupper' is used in order to convert the letters of a string to upper case.

    + +
    +
    char *cbstrtoupper(char *str);
    +
    `str' specifies the pointer of a string to convert. The return value is the pointer to the string.
    +
    + +

    The function `cbstrtolower' is used in order to convert the letters of a string to lower case.

    + +
    +
    char *cbstrtolower(char *str);
    +
    `str' specifies the pointer of a string to convert. The return value is the pointer to the string.
    +
    + +

    The function `cbstrtrim' is used in order to cut space characters at head or tail of a string.

    + +
    +
    char *cbstrtrim(char *str);
    +
    `str' specifies the pointer of a string to convert. The return value is the pointer to the string.
    +
    + +

    The function `cbstrsqzspc' is used in order to squeeze space characters in a string and trim it.

    + +
    +
    char *cbstrsqzspc(char *str);
    +
    `str' specifies the pointer of a string to convert. The return value is the pointer to the string.
    +
    + +

    The function `cbstrcountutf' is used in order to count the number of characters in a string of UTF-8.

    + +
    +
    int cbstrcountutf(const char *str);
    +
    `str' specifies the pointer of a string of UTF-8. The return value is the number of characters in the string.
    +
    + +

    The function `cbstrcututf' is used in order to cut a string of UTF-8 at the specified number of characters.

    + +
    +
    char *cbstrcututf(char *str, int num);
    +
    `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.
    +
    + +

    The function `cbdatumopen' is used in order to get a datum handle.

    + +
    +
    CBDATUM *cbdatumopen(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbdatumdup' is used in order to copy a datum.

    + +
    +
    CBDATUM *cbdatumdup(const CBDATUM *datum);
    +
    `datum' specifies a datum handle. The return value is a new datum handle.
    +
    + +

    The function `cbdatumclose' is used in order to free a datum handle.

    + +
    +
    void cbdatumclose(CBDATUM *datum);
    +
    `datum' specifies a datum handle. Because the region of a closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `cbdatumcat' is used in order to concatenate a datum and a region.

    + +
    +
    void cbdatumcat(CBDATUM *datum, const char *ptr, int size);
    +
    `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)'.
    +
    + +

    The function `cbdatumptr' is used in order to get the pointer of the region of a datum.

    + +
    +
    const char *cbdatumptr(const CBDATUM *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.
    +
    + +

    The function `cbdatumsize' is used in order to get the size of the region of a datum.

    + +
    +
    int cbdatumsize(const CBDATUM *datum);
    +
    `datum' specifies a datum handle. The return value is the size of the region of a datum.
    +
    + +

    The function `cbdatumsetsize' is used in order to change the size of the region of a datum.

    + +
    +
    void cbdatumsetsize(CBDATUM *datum, int size);
    +
    `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.
    +
    + +

    The function `cbdatumprintf' is used in order to perform formatted output into a datum.

    + +
    +
    void cbdatumprintf(CBDATUM *datum, const char *format, ...);
    +
    `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.
    +
    + +

    The function `cbdatumtomalloc' is used in order to convert a datum to an allocated region.

    + +
    +
    char *cbdatumtomalloc(CBDATUM *datum, int *sp);
    +
    `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.
    +
    + +

    The function `cblistopen' is used in order to get a list handle.

    + +
    +
    CBLIST *cblistopen(void);
    +
    The return value is a list handle.
    +
    + +

    The function `cblistdup' is used in order to copy a list.

    + +
    +
    CBLIST *cblistdup(const CBLIST *list);
    +
    `list' specifies a list handle. The return value is a new list handle.
    +
    + +

    The function `cblistclose' is used in order to close a list handle.

    + +
    +
    void cblistclose(CBLIST *list);
    +
    `list' specifies a list handle. Because the region of a closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `cblistnum' is used in order to get the number of elements of a list.

    + +
    +
    int cblistnum(const CBLIST *list);
    +
    `list' specifies a list handle. The return value is the number of elements of the list.
    +
    + +

    The function `cblistval' is used in order to get the pointer to the region of an element of a list.

    + +
    +
    const char *cblistval(const CBLIST *list, int index, int *sp);
    +
    `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'.
    +
    + +

    The function `cblistpush' is used in order to add an element at the end of a list.

    + +
    +
    void cblistpush(CBLIST *list, const char *ptr, int size);
    +
    `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)'.
    +
    + +

    The function `cblistpop' is used in order to remove an element of the end of a list.

    + +
    +
    char *cblistpop(CBLIST *list, int *sp);
    +
    `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'.
    +
    + +

    The function `cblistunshift' is used in order to add an element at the top of a list.

    + +
    +
    void cblistunshift(CBLIST *list, const char *ptr, int size);
    +
    `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)'.
    +
    + +

    The function `cblistshift' is used in order to remove an element of the top of a list.

    + +
    +
    char *cblistshift(CBLIST *list, int *sp);
    +
    `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'.
    +
    + +

    The function `cblistinsert' is used in order to add an element at the specified location of a list.

    + +
    +
    void cblistinsert(CBLIST *list, int index, const char *ptr, int size);
    +
    `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)'.
    +
    + +

    The function `cblistremove' is used in order to remove an element at the specified location of a list.

    + +
    +
    char *cblistremove(CBLIST *list, int index, int *sp);
    +
    `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'.
    +
    + +

    The function `cblistover' is used in order to overwrite an element at the specified location of a list.

    + +
    +
    void cblistover(CBLIST *list, int index, const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cblistsort' is used in order to sort elements of a list in lexical order.

    + +
    +
    void cblistsort(CBLIST *list);
    +
    `list' specifies a list handle. Quick sort is used for sorting.
    +
    + +

    The function `cblistlsearch' is used in order to search a list for an element using liner search.

    + +
    +
    int cblistlsearch(const CBLIST *list, const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cblistbsearch' is used in order to search a list for an element using binary search.

    + +
    +
    int cblistbsearch(const CBLIST *list, const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cblistdump' is used in order to serialize a list into a byte array.

    + +
    +
    char *cblistdump(const CBLIST *list, int *sp);
    +
    `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.
    +
    + +

    The function `cblistload' is used in order to redintegrate a serialized list.

    + +
    +
    CBLIST *cblistload(const char *ptr, int size);
    +
    `ptr' specifies the pointer to a byte array. `size' specifies the size of the region. The return value is a new list handle.
    +
    + +

    The function `cbmapopen' is used in order to get a map handle.

    + +
    +
    CBMAP *cbmapopen(void);
    +
    The return value is a map handle.
    +
    + +

    The function `cbmapdup' is used in order to copy a map.

    + +
    +
    CBMAP *cbmapdup(CBMAP *map);
    +
    `map' specifies a map handle. The return value is a new map handle. The iterator of the source map is initialized.
    +
    + +

    The function `cbmapclose' is used in order to close a map handle.

    + +
    +
    void cbmapclose(CBMAP *map);
    +
    `map' specifies a map handle. Because the region of a closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `cbmapput' is used in order to store a record into a map.

    + +
    +
    int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over);
    +
    `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.
    +
    + +

    The function `cbmapputcat' is used in order to 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);
    +
    `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.
    +
    + +

    The function `cbmapout' is used in order to delete a record of a map.

    + +
    +
    int cbmapout(CBMAP *map, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `cbmapget' is used in order to retrieve a record of a map.

    + +
    +
    const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + +

    The function `cbmapmove' is used in order to move a record to the edge of a map.

    + +
    +
    int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head);
    +
    `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.
    +
    + +

    The function `cbmapiterinit' is used in order to initialize the iterator of a map.

    + +
    +
    void cbmapiterinit(CBMAP *map);
    +
    `map' specifies a map handle. The iterator is used in order to access the key of every record stored in a map.
    +
    + +

    The function `cbmapiternext' is used in order to get the next key of the iterator of a map.

    + +
    +
    const char *cbmapiternext(CBMAP *map, int *sp);
    +
    `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.
    +
    + +

    The function `cbmapiterval' is used in order to get the value binded to the key fetched from the iterator of a map.

    + +
    +
    const char *cbmapiterval(const char *kbuf, int *sp);
    +
    `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.
    +
    + +

    The function `cbmaprnum' is used in order to get the number of the records stored in a map.

    + +
    +
    int cbmaprnum(const CBMAP *map);
    +
    `map' specifies a map handle. The return value is the number of the records stored in the map.
    +
    + +

    The function `cbmapkeys' is used in order to get the list handle contains all keys in a map.

    + +
    +
    CBLIST *cbmapkeys(CBMAP *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.
    +
    + +

    The function `cbmapvals' is used in order to get the list handle contains all values in a map.

    + +
    +
    CBLIST *cbmapvals(CBMAP *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.
    +
    + +

    The function `cbmapdump' is used in order to serialize a map into a byte array.

    + +
    +
    char *cbmapdump(const CBMAP *map, int *sp);
    +
    `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.
    +
    + +

    The function `cbmapload' is used in order to redintegrate a serialized map.

    + +
    +
    CBMAP *cbmapload(const char *ptr, int size);
    +
    `ptr' specifies the pointer to a byte array. `size' specifies the size of the region. The return value is a new map handle.
    +
    + +

    The function `cbmaploadone' is used in order to extract a record from a serialized map.

    + +
    +
    char *cbmaploadone(const char *ptr, int size, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + + +

    The function `cbheapopen' is used in order to get a heap handle.

    + +
    +
    CBHEAP *cbheapopen(int size, int max, int(*compar)(const void *, const void *));
    +
    `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.
    +
    + +

    The function `cbheapdup' is used in order to copy a heap.

    + +
    +
    CBHEAP *cbheapdup(CBHEAP *heap);
    +
    `heap' specifies a heap handle. The return value is a new heap handle.
    +
    + +

    The function `cbheapclose' is used in order to close a heap handle.

    + +
    +
    void cbheapclose(CBHEAP *heap);
    +
    `heap' specifies a heap handle. Because the region of a closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `cbheapnum' is used in order to get the number of the records stored in a heap.

    + +
    +
    int cbheapnum(CBHEAP *heap);
    +
    `heap' specifies a heap handle. The return value is the number of the records stored in the heap.
    +
    + +

    The function `cbheapinsert' is used in order to insert a record into a heap.

    + +
    +
    int cbheapinsert(CBHEAP *heap, const void *ptr);
    +
    `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.
    +
    + +

    The function `cbheapval' is used in order to get the pointer to the region of a record in a heap.

    + +
    +
    void *cbheapval(CBHEAP *heap, int index);
    +
    `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.
    +
    + +

    The function `cbheaptomalloc' is used in order to convert a heap to an allocated region.

    + +
    +
    void *cbheaptomalloc(CBHEAP *heap, int *np);
    +
    `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.
    +
    + +

    The function `cbsprintf' is used in order to allocate a formatted string on memory.

    + +
    +
    char *cbsprintf(const char *format, ...);
    +
    `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.
    +
    + +

    The function `cbreplace' is used in order to replace some patterns in a string.

    + +
    +
    char *cbreplace(const char *str, CBMAP *pairs);
    +
    `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.
    +
    + +

    The function `cbsplit' is used in order to make a list by splitting a serial datum.

    + +
    +
    CBLIST *cbsplit(const char *ptr, int size, const char *delim);
    +
    `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'.
    +
    + +

    The function `cbreadfile' is used in order to read whole data of a file.

    + +
    +
    char *cbreadfile(const char *name, int *sp);
    +
    `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.
    +
    + +

    The function `cbwritefile' is used in order to write a serial datum into a file.

    + +
    +
    int cbwritefile(const char *name, const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbreadlines' is used in order to read every line of a file.

    + +
    +
    CBLIST *cbreadlines(const char *name);
    +
    `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.
    +
    + +

    The function `cbdirlist' is used in order to read names of files in a directory.

    + +
    +
    CBLIST *cbdirlist(const char *name);
    +
    `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.
    +
    + +

    The function `cbfilestat' is used in order to get the status of a file or a directory.

    + +
    +
    int cbfilestat(const char *name, int *isdirp, int *sizep, int *mtimep);
    +
    `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.
    +
    + +

    The function `cbremove' is used in order to remove a file or a directory and its sub ones recursively.

    + +
    +
    int cbremove(const char *name);
    +
    `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.
    +
    + +

    The function `cburlbreak' is used in order to break up a URL into elements.

    + +
    +
    CBMAP *cburlbreak(const char *str);
    +
    `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.
    +
    + +

    The runction `cburlresolve' is used in order to resolve a relative URL with another absolute URL.

    + +
    +
    char *cburlresolve(const char *base, const char *target);
    +
    `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.
    +
    + +

    The function `cburlencode' is used in order to encode a serial object with URL encoding.

    + +
    +
    char *cburlencode(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cburldecode' is used in order to decode a string encoded with URL encoding.

    + +
    +
    char *cburldecode(const char *str, int *sp);
    +
    `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.
    +
    + +

    The function `cbbaseencode' is used in order to encode a serial object with Base64 encoding.

    + +
    +
    char *cbbaseencode(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbbasedecode' is used in order to decode a string encoded with Base64 encoding.

    + +
    +
    char *cbbasedecode(const char *str, int *sp);
    +
    `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.
    +
    + +

    The function `cbquoteencode' is used in order to encode a serial object with quoted-printable encoding.

    + +
    +
    char *cbquoteencode(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbquotedecode' is used in order to decode a string encoded with quoted-printable encoding.

    + +
    +
    char *cbquotedecode(const char *str, int *sp);
    +
    `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.
    +
    + + +

    The function `cbmimebreak' is used in order to split a string of MIME into headers and the body.

    + +
    +
    char *cbmimebreak(const char *ptr, int size, CBMAP *attrs, int *sp);
    +
    `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.
    +
    + +

    The function `cbmimeparts' is used in order to split multipart data of MIME into its parts.

    + +
    +
    CBLIST *cbmimeparts(const char *ptr, int size, const char *boundary);
    +
    `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.
    +
    + +

    The function `cbmimeencode' is used in order to encode a string with MIME encoding.

    + +
    +
    char *cbmimeencode(const char *str, const char *encname, int base);
    +
    `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.
    +
    + +

    The function `cbmimedecode' is used in order to decode a string encoded with MIME encoding.

    + +
    +
    char *cbmimedecode(const char *str, char *enp);
    +
    `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.
    +
    + +

    The function `cbcsvrows' is used in order to split a string of CSV into rows.

    + +
    +
    CBLIST *cbcsvrows(const char *str);
    +
    `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.
    +
    + +

    The function `cbcsvcells' is used in order to split the string of a row of CSV into cells.

    + +
    +
    CBLIST *cbcsvcells(const char *str);
    +
    `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.
    +
    + +

    The function `cbcsvescape' is used in order to escape a string with the meta characters of CSV.

    + +
    +
    char *cbcsvescape(const char *str);
    +
    `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.
    +
    + +

    The function `cbcsvunescape' is used in order to unescape a string with the escaped meta characters of CSV.

    + +
    +
    char *cbcsvunescape(const char *str);
    +
    `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.
    +
    + +

    The function `cbxmlbreak' is used in order to split a string of XML into tags and text sections.

    + +
    +
    CBLIST *cbxmlbreak(const char *str, int cr);
    +
    `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.
    +
    + +

    The function `cbxmlattrs' is used in order to get the map of attributes of an XML tag.

    + +
    +
    CBMAP *cbxmlattrs(const char *str);
    +
    `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.
    +
    + +

    The function `cbxmlescape' is used in order to escape a string with the meta characters of XML.

    + +
    +
    char *cbxmlescape(const char *str);
    +
    `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.
    +
    + +

    The function `cbxmlunescape' is used in order to unescape a string with the entity references of XML.

    + +
    +
    char *cbxmlunescape(const char *str);
    +
    `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;', `&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.
    +
    + +

    The function `cbdeflate' is used in order to compress a serial object with ZLIB.

    + +
    +
    char *cbdeflate(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbinflate' is used in order to decompress a serial object compressed with ZLIB.

    + +
    +
    char *cbinflate(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbgzencode' is used in order to compress a serial object with GZIP.

    + +
    +
    char *cbgzencode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbgzdecode' is used in order to decompress a serial object compressed with GZIP.

    + +
    +
    char *cbgzdecode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbgetcrc' is used in order to get the CRC32 checksum of a serial object.

    + +
    +
    unsigned int cbgetcrc(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cblzoencode' is used in order to compress a serial object with LZO.

    + +
    +
    char *cblzoencode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cblzodecode' is used in order to decompress a serial object compressed with LZO.

    + +
    +
    char *cblzodecode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbbzencode' is used in order to compress a serial object with BZIP2.

    + +
    +
    char *cbbzencode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbbzdecode' is used in order to decompress a serial object compressed with BZIP2.

    + +
    +
    char *cbbzdecode(const char *ptr, int size, int *sp);
    +
    `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.
    +
    + +

    The function `cbiconv' is used in order to convert the character encoding of a string.

    + +
    +
    char *cbiconv(const char *ptr, int size, const char *icode, const char *ocode, int *sp, int *mp);
    +
    `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.
    +
    + +

    The function `cbencname' is used in order to detect the encoding of a string automatically.

    + +
    +
    const char *cbencname(const char *ptr, int size);
    +
    `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.
    +
    + +

    The function `cbjetlag' is used in order to get the jet lag of the local time in seconds.

    + +
    +
    int cbjetlag(void);
    +
    The return value is the jet lag of the local time in seconds.
    +
    + +

    The function `cbcalendar' is used in order to 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);
    +
    `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.
    +
    + +

    The function `cbdayofweek' is used in order to get the day of week of a date.

    + +
    +
    int cbdayofweek(int year, int mon, int day);
    +
    `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.
    +
    + +

    The function `cbdatestrwww' is used in order to get the string for a date in W3CDTF.

    + +
    +
    char *cbdatestrwww(time_t t, int jl);
    +
    `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.
    +
    + +

    The function `cbdatestrhttp' is used in order to get the string for a date in RFC 1123 format.

    + +
    +
    char *cbdatestrhttp(time_t t, int jl);
    +
    `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.
    +
    + +

    The function `cbstrmktime' is used in order to get the time value of a date string in decimal, hexadecimal, W3CDTF, or RFC 822 (1123).

    + +
    +
    time_t cbstrmktime(const char *str);
    +
    `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.
    +
    + +

    The function `cbproctime' is used in order to get user and system processing times.

    + +
    +
    void cbproctime(double *usrp, double *sysp);
    +
    `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.
    +
    + +

    The function `cbstdiobin' is used in order to ensure that the standard I/O is binary mode.

    + +
    +
    void cbstdiobin(void);
    +
    This function is useful for applications on dosish file systems.
    +
    + +

    Examples

    + +

    The following example is typical use.

    + +
    #include <cabin.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +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 < 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 < cblistnum(list); i++){
    +    printf("%s\n", cblistval(list, i, NULL));    
    +  }
    +
    +  return 0;
    +}
    +
    + +

    Notes

    + +

    How to build programs using Cabin is the same as the case of Depot.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +
    + +

    Commands for Cabin

    + +

    Cabin has the following command line interfaces.

    + +

    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.

    + +
    +
    cbtest sort [-d] rnum
    +
    Perform test of sorting algorithms.
    +
    cbtest strstr [-d] rnum
    +
    Perform test of string locating algorithms.
    +
    cbtest list [-d] rnum
    +
    Perform writing test of list.
    +
    cbtest map [-d] rnum
    +
    Perform writing test of map.
    +
    cbtest wicked rnum
    +
    Perform updating operations of list and map selected at random.
    +
    cbtest misc
    +
    Perform test of miscellaneous routines.
    +
    + +

    Options feature the following.

    + +
      +
    • -d : read and show data of the result.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    +
    cbcodec url [-d] [-br] [-rs base target] [-l] [-e expr] [file]
    +
    Perform URL encoding and its decoding.
    +
    cbcodec base [-d] [-l] [-c num] [-e expr] [file]
    +
    Perform Base64 encoding and its decoding.
    +
    cbcodec quote [-d] [-l] [-c num] [-e expr] [file]
    +
    Perform quoted-printable encoding and its decoding.
    +
    cbcodec mime [-d] [-hd] [-bd] [-part num] [-l] [-ec code] [-qp] [-dc] [-e expr] [file]
    +
    Perform MIME encoding and its decoding.
    +
    cbcodec csv [-d] [-t] [-l] [-e expr] [-html] [file]
    +
    Process CSV. By default, escape meta characters.
    +
    cbcodec xml [-d] [-p] [-l] [-e expr] [-tsv] [file]
    +
    Process XML. By default, escape meta characters.
    +
    cbcodec zlib [-d] [-gz] [-crc] [file]
    +
    Perform deflation and inflation with ZLIB. It is available only if QDBM was built with ZLIB enabled.
    +
    cbcodec lzo [-d] [file]
    +
    Perform compression and decompression with LZO. It is available only if QDBM was built with LZO enabled.
    +
    cbcodec bzip [-d] [file]
    +
    Perform compression and decompression with BZIP2. It is available only if QDBM was built with BZIP2 enabled.
    +
    cbcodec iconv [-ic code] [-oc code] [-ol ltype] [-cn] [-um] [-wc] [file]
    +
    Convert character encoding with ICONV. It is available only if QDBM was built with ICONV enabled.
    +
    cbcodec date [-wf] [-rf] [-utc] [str]
    +
    Convert a date string specified `str'. By default, UNIX time is output. If `str' is omitted, the current date is dealt.
    +
    + +

    Options feature the following.

    + +
      +
    • -d : perform decoding (unescaping), not encoding (escaping).
    • +
    • -br : break up URL into elements.
    • +
    • -rs : resolve relative URL.
    • +
    • -l : output the tailing newline.
    • +
    • -e expr : specify input data directly.
    • +
    • -c num : limit the number of columns of the encoded data.
    • +
    • -hd : parse MIME and extract headers in TSV format.
    • +
    • -bd : parse MIME and extract the body.
    • +
    • -part num : parse MIME and extract a part.
    • +
    • -ec code : specify the input encoding, which is UTF-8 by default.
    • +
    • -qp : use quoted-printable encoding, which is Base64 by default.
    • +
    • -dc : output the encoding name instead of the result string when decoding.
    • +
    • -t : parse CSV. Convert the data into TSV. Tab and new-line in a cell are deleted.
    • +
    • -html : parse CSV. Convert the data into HTML.
    • +
    • -p : parse XML. Show tags and text sections with dividing headers.
    • +
    • -tsv : parse XML. Show the result in TSV format. Characters of tabs and new-lines are URL-encoded.
    • +
    • -gz : use GZIP format.
    • +
    • -crc : output the CRC32 checksum as hexadecimal and big endian.
    • +
    • -ic code : specify the input encoding, which is detected automatically by default.
    • +
    • -oc code : specify the output encoding, which is UTF-8 by default.
    • +
    • -ol ltype : convert line feed characters, with `unix'(LF), `dos'(CRLF), and `mac'(CR).
    • +
    • -cn : detect the input encoding and show its name.
    • +
    • -wc : count the number of characters of the input string of UTF-8.
    • +
    • -um : output mappings of UCS-2 characters and C strings of UTF-16BE and UTF-8.
    • +
    • -wf : output in W3CDTF format.
    • +
    • -rf : output in RFC 1123 format.
    • +
    • -utc : output the coordinate universal time.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +
    + +

    Villa: Advanced API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +

    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.

    + +
    +
    #include <depot.h>
    +
    #include <cabin.h>
    +
    #include <villa.h>
    +
    #include <stdlib.h>
    +
    + +

    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.

    + +

    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.

    + +

    API

    + +

    You can define a comparing function to specify the order of records. The function should be the following type.

    + +
    +
    typedef int(*VLCFUNC)(const char *aptr, int asiz, const char *bptr, int bsiz);
    +
    `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.
    +
    + +

    The function `vlopen' is used in order to get a database handle.

    + +
    +
    VILLA *vlopen(const char *name, int omode, VLCFUNC cmp);
    +
    `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.
    +
    + +

    The function `vlclose' is used in order to close a database handle.

    + +
    +
    int vlclose(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlput' is used in order to store a record.

    + +
    +
    int vlput(VILLA *villa, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
    +
    `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.
    +
    + +

    The function `vlout' is used in order to delete a record.

    + +
    +
    int vlout(VILLA *villa, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `vlget' is used in order to retrieve a record.

    + +
    +
    char *vlget(VILLA *villa, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + +

    The function `vlvsiz' is used in order to get the size of the value of a record.

    + +
    +
    int vlvsiz(VILLA *villa, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `vlvnum' is used in order to get the number of records corresponding a key.

    + +
    +
    int vlvnum(VILLA *villa, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `vlputlist' is used in order to store plural records corresponding a key.

    + +
    +
    int vlputlist(VILLA *villa, const char *kbuf, int ksiz, const CBLIST *vals);
    +
    `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.
    +
    + +

    The function `vloutlist' is used in order to delete all records corresponding a key.

    + +
    +
    int vloutlist(VILLA *villa, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `vlgetlist' is used in order to retrieve values of all records corresponding a key.

    + +
    +
    CBLIST *vlgetlist(VILLA *villa, const char *kbuf, int ksiz);
    +
    `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.
    +
    + +

    The function `vlgetcat' is used in order to retrieve concatenated values of all records corresponding a key.

    + +
    +
    char *vlgetcat(VILLA *villa, const char *kbuf, int ksiz, int *sp);
    +
    `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.
    +
    + +

    The function `vlcurfirst' is used in order to move the cursor to the first record.

    + +
    +
    int vlcurfirst(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlcurlast' is used in order to move the cursor to the last record.

    + +
    +
    int vlcurlast(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlcurprev' is used in order to move the cursor to the previous record.

    + +
    +
    int vlcurprev(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlcurnext' is used in order to move the cursor to the next record.

    + +
    +
    int vlcurnext(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlcurjump' is used in order to move the cursor to a position around a record.

    + +
    +
    int vlcurjump(VILLA *villa, const char *kbuf, int ksiz, int jmode);
    +
    `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.
    +
    + +

    The function `vlcurkey' is used in order to get the key of the record where the cursor is.

    + +
    +
    char *vlcurkey(VILLA *villa, int *sp);
    +
    `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.
    +
    + +

    The function `vlcurval' is used in order to get the value of the record where the cursor is.

    + +
    +
    char *vlcurval(VILLA *villa, int *sp);
    +
    `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.
    +
    + +

    The function `vlcurput' is used in order to insert a record around the cursor.

    + +
    +
    int vlcurput(VILLA *villa, const char *vbuf, int vsiz, int cpmode);
    +
    `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.
    +
    + +

    The function `vlcurout' is used in order to delete the record where the cursor is.

    + +
    +
    int vlcurout(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlsettuning' is used in order to set the tuning parameters for performance.

    + +
    +
    void vlsettuning(VILLA *villa, int lrecmax, int nidxmax, int lcnum, int ncnum);
    +
    `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.
    +
    + +

    The function `vlsetfbpsiz' is used in order to set the size of the free block pool of a database handle.

    + +
    +
    int vlsetfbpsiz(VILLA *villa, int size);
    +
    `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.
    +
    + +

    The function `vlsync' is used in order to synchronize updating contents with the file and the device.

    + +
    +
    int vlsync(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vloptimize' is used in order to optimize a database.

    + +
    +
    int vloptimize(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlname' is used in order to get the name of a database.

    + +
    +
    char *vlname(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlfsiz' is used in order to get the size of a database file.

    + +
    +
    int vlfsiz(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vllnum' is used in order to get the number of the leaf nodes of B+ tree.

    + +
    +
    int vllnum(VILLA *villa);
    +
    `villa' specifies a database handle. If successful, the return value is the number of the leaf nodes, else, it is -1.
    +
    + +

    The function `vlnnum' is used in order to get the number of the non-leaf nodes of B+ tree.

    + +
    +
    int vlnnum(VILLA *villa);
    +
    `villa' specifies a database handle. If successful, the return value is the number of the non-leaf nodes, else, it is -1.
    +
    + +

    The function `vlrnum' is used in order to get the number of the records stored in a database.

    + +
    +
    int vlrnum(VILLA *villa);
    +
    `villa' specifies a database handle. If successful, the return value is the number of the records stored in the database, else, it is -1.
    +
    + +

    The function `vlwritable' is used in order to check whether a database handle is a writer or not.

    + +
    +
    int vlwritable(VILLA *villa);
    +
    `villa' specifies a database handle. The return value is true if the handle is a writer, false if not.
    +
    + +

    The function `vlfatalerror' is used in order to check whether a database has a fatal error or not.

    + +
    +
    int vlfatalerror(VILLA *villa);
    +
    `villa' specifies a database handle. The return value is true if the database has a fatal error, false if not.
    +
    + +

    The function `vlinode' is used in order to get the inode number of a database file.

    + +
    +
    int vlinode(VILLA *villa);
    +
    `villa' specifies a database handle. The return value is the inode number of the database file.
    +
    + +

    The function `vlmtime' is used in order to get the last modified time of a database.

    + +
    +
    time_t vlmtime(VILLA *villa);
    +
    `villa' specifies a database handle. The return value is the last modified time of the database.
    +
    + +

    The function `vltranbegin' is used in order to begin the transaction.

    + +
    +
    int vltranbegin(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vltrancommit' is used in order to commit the transaction.

    + +
    +
    int vltrancommit(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vltranabort' is used in order to abort the transaction.

    + +
    +
    int vltranabort(VILLA *villa);
    +
    `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.
    +
    + +

    The function `vlremove' is used in order to remove a database file.

    + +
    +
    int vlremove(const char *name);
    +
    `name' specifies the name of a database file. If successful, the return value is true, else, it is false.
    +
    + +

    The function `vlrepair' is used in order to repair a broken database file.

    + +
    +
    int vlrepair(const char *name, VLCFUNC cmp);
    +
    `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.
    +
    + +

    The function `vlexportdb' is used in order to dump all records as endian independent data.

    + +
    +
    int vlexportdb(VILLA *villa, const char *name);
    +
    `villa' specifies a database handle. `name' specifies the name of an output file. If successful, the return value is true, else, it is false.
    +
    + +

    The function `vlimportdb' is used in order to load all records from endian independent data.

    + +
    +
    int vlimportdb(VILLA *villa, const char *name);
    +
    `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.
    +
    + +

    Examples

    + +

    The following example stores and retrieves a phone number, using the name as the key.

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +
    +#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;
    +}
    +
    + +

    The following example performs forward matching search for strings.

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <villa.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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;
    +}
    +
    + +

    Notes

    + +

    How to build programs using Villa is the same as the case of Depot.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +

    Vista: Extended Advanced API

    + +

    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.

    + +

    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').

    + +
    + +

    Commands for Villa

    + +

    Villa has the following command line interfaces.

    + +

    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.

    + +
    +
    vlmgr create [-cz|-cy|-cx] name
    +
    Create a database file.
    +
    vlmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat|-dup] name key val
    +
    Store a record with a key and a value.
    +
    vlmgr out [-l] [-kx|-ki] name key
    +
    Delete a record with a key.
    +
    vlmgr get [-nl] [-l] [-kx|-ki] [-ox] [-n] name key
    +
    Retrieve a record with a key and output it to the standard output.
    +
    vlmgr list [-nl] [-k|-v] [-kx|-ki] [-ox] [-top key] [-bot key] [-gt] [-lt] [-max num] [-desc] name
    +
    List all keys and values delimited with tab and line-feed to the standard output.
    +
    vlmgr optimize name
    +
    Optimize a database.
    +
    vlmgr inform [-nl] name
    +
    Output miscellaneous information to the standard output.
    +
    vlmgr remove name
    +
    Remove a database file.
    +
    vlmgr repair [-ki] name
    +
    Repair a broken database file.
    +
    vlmgr exportdb [-ki] name file
    +
    Dump all records as endian independent data.
    +
    vlmgr importdb [-ki] name file
    +
    Load all records from endian independent data.
    +
    vlmgr version
    +
    Output version information of QDBM to the standard output.
    +
    + +

    Options feature the following.

    + +
      +
    • -cz : compress leaves in the database with ZLIB.
    • +
    • -cy : compress leaves in the database with LZO.
    • +
    • -cx : compress leaves in the database with BZIP2.
    • +
    • -l : all records corresponding the key are dealt.
    • +
    • -kx : treat `key' as a binary expression of hexadecimal notation.
    • +
    • -ki : treat `key' as an integer expression of decimal notation.
    • +
    • -vx : treat `val' as a binary expression of hexadecimal notation.
    • +
    • -vi : treat `val' as an integer expression of decimal notation.
    • +
    • -vf : read the value from a file specified with `val'.
    • +
    • -keep : specify the storing mode for `VL_DKEEP'.
    • +
    • -cat : specify the storing mode for `VL_DCAT'.
    • +
    • -dup : specify the storing mode for `VL_DDUP'.
    • +
    • -nl : open the database without file locking.
    • +
    • -top key : specify the top key of listing.
    • +
    • -bot key : specify the bottom key of listing.
    • +
    • -gt : do not include the top key of listing.
    • +
    • -lt : do not include the bottom key of listing.
    • +
    • -max num : specify the max number of listing.
    • +
    • -desc : list in descending order.
    • +
    • -ox : treat the output as a binary expression of hexadecimal notation.
    • +
    • -n : do not output the tailing newline.
    • +
    • -k : output keys only.
    • +
    • -v : output values only.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    vltest write [-int] [-cz|-cy|-cx] [-tune lrecmax nidxmax lcnum ncnum] [-fbp num] name rnum
    +
    Store records with keys of 8 bytes. They change as `00000001', `00000002'...
    +
    vltest read [-int] [-vc] name
    +
    Retrieve all records of the database above.
    +
    vltest rdup [-int] [-cz|-cy|-cx] [-cc] [-tune lrecmax nidxmax lcnum ncnum] [-fbp num] name rnum pnum
    +
    Store records with partway duplicated keys using duplicate mode.
    +
    vltest combo [-cz|-cy|-cx] name
    +
    Perform combination test of various operations.
    +
    vltest wicked [-cz|-cy|-cx] name rnum
    +
    Perform updating operations selected at random.
    +
    + +

    Options feature the following.

    + +
      +
    • -int : treat keys and values as objects of `int', and use comparing function `VL_CMPINT'.
    • +
    • -cz : compress leaves in the database with ZLIB.
    • +
    • -cy : compress leaves in the database with LZO.
    • +
    • -cx : compress leaves in the database with BZIP2.
    • +
    • -vc : refer to volatile cache.
    • +
    • -cc : select `VL_DCAT' or `VL_DDUP' at random.
    • +
    • -tune lrecmax nidxmax lcnum ncnum : set tuning parameters.
    • +
    • -fbp num : set the size of the free block pool.
    • +
    • -c : perform comparison test with map of Cabin.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    vltsv import [-bin] name
    +
    Create a database from TSV.
    +
    vltsv export [-bin] name
    +
    Write TSV data of a database.
    +
    + +

    Options feature the following.

    + +
      +
    • -bin : treat records as Base64 format.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    cat /etc/passwd | tr ':' '\t' | vltsv import casket
    +
    + +

    Thus, to retrieve the information of a user `mikio', perform the following command.

    + +
    vlmgr get casket mikio
    +
    + +

    It is easy to implement functions upsides with these commands, using the API of Villa.

    + +

    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.

    + +
    +
    qmttest name rnum tnum
    +
    Check multi-thread safety.
    +
    + +

    This command returns 0 on success, another on failure.

    + +
    + +

    Odeum: Inverted API

    + +

    Overview

    + +

    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.

    + +

    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.

    + +

    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.

    + +

    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.

    + +
    +
    #include <depot.h>
    +
    #include <cabin.h>
    +
    #include <odeum.h>
    +
    #include <stdlib.h>
    +
    + +

    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.

    + +

    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.

    + +

    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.

    + +

    API

    + +

    Structures of `ODPAIR' type is used in order to handle results of search.

    + +
    +
    typedef struct { int id; int score; } ODPAIR;
    +
    `id' specifies the ID number of a document. `score' specifies the score calculated from the number of searching words in the document.
    +
    + +

    The function `odopen' is used in order to get a database handle.

    + +
    +
    ODEUM *odopen(const char *name, int omode);
    +
    `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.
    +
    + +

    The function `odclose' is used in order to close a database handle.

    + +
    +
    int odclose(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odput' is used in order to store a document.

    + +
    +
    int odput(ODEUM *odeum, const ODDOC *doc, int wmax, int over);
    +
    `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.
    +
    + +

    The function `odout' is used in order to delete a document specified by a URI.

    + +
    +
    int odout(ODEUM *odeum, const char *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.
    +
    + +

    The function `odoutbyid' is used in order to delete a document specified by an ID number.

    + +
    +
    int odoutbyid(ODEUM *odeum, int id);
    +
    `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.
    +
    + +

    The function `odget' is used in order to retrieve a document specified by a URI.

    + +
    +
    ODDOC *odget(ODEUM *odeum, const char *uri);
    +
    `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'.
    +
    + +

    The function `odgetbyid' is used in order to retrieve a document by an ID number.

    + +
    +
    ODDOC *odgetbyid(ODEUM *odeum, int id);
    +
    `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'.
    +
    + +

    The function `odgetidbyuri' is used in order to retrieve the ID of the document specified by a URI.

    + +
    +
    int odgetidbyuri(ODEUM *odeum, const char *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.
    +
    + +

    The function `odcheck' is used in order to check whether the document specified by an ID number exists.

    + +
    +
    int odcheck(ODEUM *odeum, int id);
    +
    `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.
    +
    + +

    The function `odsearch' is used in order to search the inverted index for documents including a particular word.

    + +
    +
    ODPAIR *odsearch(ODEUM *odeum, const char *word, int max, int *np);
    +
    `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.
    +
    + +

    The function `odsearchnum' is used in order to get the number of documents including a word.

    + +
    +
    int odsearchdnum(ODEUM *odeum, const char *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'.
    +
    + +

    The function `oditerinit' is used in order to initialize the iterator of a database handle.

    + +
    +
    int oditerinit(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `oditernext' is used in order to get the next key of the iterator.

    + +
    +
    ODDOC *oditernext(ODEUM *odeum);
    +
    `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'.
    +
    + +

    The function `odsync' is used in order to synchronize updating contents with the files and the devices.

    + +
    +
    int odsync(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odoptimize' is used in order to optimize a database.

    + +
    +
    int odoptimize(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odname' is used in order to get the name of a database.

    + +
    +
    char *odname(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odfsiz' is used in order to get the total size of database files.

    + +
    +
    double odfsiz(ODEUM *odeum);
    +
    `odeum' specifies a database handle. If successful, the return value is the total size of the database files, else, it is -1.0.
    +
    + +

    The function `odbnum' is used in order to get the total number of the elements of the bucket arrays in the inverted index.

    + +
    +
    int odbnum(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odbusenum' is used in order to get the total number of the used elements of the bucket arrays in the inverted index.

    + +
    +
    int odbusenum(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `oddnum' is used in order to get the number of the documents stored in a database.

    + +
    +
    int oddnum(ODEUM *odeum);
    +
    `odeum' specifies a database handle. If successful, the return value is the number of the documents stored in the database, else, it is -1.
    +
    + +

    The function `odwnum' is used in order to get the number of the words stored in a database.

    + +
    +
    int odwnum(ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odwritable' is used in order to check whether a database handle is a writer or not.

    + +
    +
    int odwritable(ODEUM *odeum);
    +
    `odeum' specifies a database handle. The return value is true if the handle is a writer, false if not.
    +
    + +

    The function `odfatalerror' is used in order to check whether a database has a fatal error or not.

    + +
    +
    int odfatalerror(ODEUM *odeum);
    +
    `odeum' specifies a database handle. The return value is true if the database has a fatal error, false if not.
    +
    + +

    The function `odinode' is used in order to get the inode number of a database directory.

    + +
    +
    int odinode(ODEUM *odeum);
    +
    `odeum' specifies a database handle. The return value is the inode number of the database directory.
    +
    + +

    The function `odmtime' is used in order to get the last modified time of a database.

    + +
    +
    time_t odmtime(ODEUM *odeum);
    +
    `odeum' specifies a database handle. The return value is the last modified time of the database.
    +
    + +

    The function `odmerge' is used in order to merge plural database directories.

    + +
    +
    int odmerge(const char *name, const CBLIST *elemnames);
    +
    `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.
    +
    + +

    The function `odremove' is used in order to remove a database directory.

    + +
    +
    int odremove(const char *name);
    +
    `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.
    +
    + +

    The function `oddocopen' is used in order to get a document handle.

    + +
    +
    ODDOC *oddocopen(const char *uri);
    +
    `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.
    +
    + +

    The function `oddocclose' is used in order to close a document handle.

    + +
    +
    void oddocclose(ODDOC *doc);
    +
    `doc' specifies a document handle. Because the region of a closed handle is released, it becomes impossible to use the handle.
    +
    + +

    The function `oddocaddattr' is used in order to add an attribute to a document.

    + +
    +
    void oddocaddattr(ODDOC *doc, const char *name, const char *value);
    +
    `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.
    +
    + +

    The function `oddocaddword' is used in order to add a word to a document.

    + +
    +
    void oddocaddword(ODDOC *doc, const char *normal, const char *asis);
    +
    `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.
    +
    + +

    The function `oddocid' is used in order to get the ID number of a document.

    + +
    +
    int oddocid(const ODDOC *doc);
    +
    `doc' specifies a document handle. The return value is the ID number of a document.
    +
    + +

    The function `oddocuri' is used in order to get the URI of a document.

    + +
    +
    const char *oddocuri(const ODDOC *doc);
    +
    `doc' specifies a document handle. The return value is the string of the URI of a document.
    +
    + +

    The function `oddocgetattr' is used in order to get the value of an attribute of a document.

    + +
    +
    const char *oddocgetattr(const ODDOC *doc, const char *name);
    +
    `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.
    +
    + +

    The function `oddocnwords' is used in order to get the list handle contains words in normalized form of a document.

    + +
    +
    const CBLIST *oddocnwords(const ODDOC *doc);
    +
    `doc' specifies a document handle. The return value is the list handle contains words in normalized form.
    +
    + +

    The function `oddocawords' is used in order to get the list handle contains words in appearance form of a document.

    + +
    +
    const CBLIST *oddocawords(const ODDOC *doc);
    +
    `doc' specifies a document handle. The return value is the list handle contains words in appearance form.
    +
    + +

    The function `oddocscores' is used in order to get the map handle contains keywords in normalized form and their scores.

    + +
    +
    CBMAP *oddocscores(const ODDOC *doc, int max, ODEUM *odeum);
    +
    `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.
    +
    + +

    The function `odbreaktext' is used in order to break a text into words in appearance form.

    + +
    +
    CBLIST *odbreaktext(const char *text);
    +
    `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.
    +
    + +

    The function `odnormalizeword' is used in order to make the normalized form of a word.

    + +
    +
    char *odnormalizeword(const char *asis);
    +
    `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.
    +
    + +

    The function `odpairsand' is used in order to get the common elements of two sets of documents.

    + +
    +
    ODPAIR *odpairsand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `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.
    +
    + +

    The function `odpairsor' is used in order to get the sum of elements of two sets of documents.

    + +
    +
    ODPAIR *odpairsor(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `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.
    +
    + +

    The function `odpairsnotand' is used in order to get the difference set of documents.

    + +
    +
    ODPAIR *odpairsnotand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
    +
    `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.
    +
    + +

    The function `odpairssort' is used in order to sort a set of documents in descending order of scores.

    + +
    +
    void odpairssort(ODPAIR *pairs, int pnum);
    +
    `pairs' specifies the pointer to a document array. `pnum' specifies the number of the elements of the document array.
    +
    + +

    The function `odlogarithm' is used in order to get the natural logarithm of a number.

    + +
    +
    double odlogarithm(double x);
    +
    `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.
    +
    + +

    The function `odvectorcosine' is used in order to get the cosine of the angle of two vectors.

    + +
    +
    double odvectorcosine(const int *avec, const int *bvec, int vnum);
    +
    `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.
    +
    + +

    The function `odsettuning' is used in order to set the global tuning parameters.

    + +
    +
    void odsettuning(int ibnum, int idnum, int cbnum, int csiz);
    +
    `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.
    +
    + +

    The function `odanalyzetext' is used in order to 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);
    +
    `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.
    +
    + +

    The function `odsetcharclass' is used in order to set the classes of characters used by `odanalyzetext'.

    + +
    +
    void odsetcharclass(ODEUM *odeum, const char *spacechars, const char *delimchars, const char *gluechars);
    +
    `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.
    +
    + +

    The function `odquery' is used in order to query a database using a small boolean query language.

    + +
    +
    ODPAIR *odquery(ODEUM *odeum, const char *query, int *np, CBLIST *errors);
    +
    `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.
    +
    + +

    Examples

    + +

    The following example stores a document into the database.

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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 < 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;
    +}
    +
    + +

    The following example retrieves documents.

    + +
    #include <depot.h>
    +#include <cabin.h>
    +#include <odeum.h>
    +#include <stdlib.h>
    +#include <stdio.h>
    +#include <string.h>
    +
    +#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, &pnum)) != NULL){
    +
    +    /* scan each element of the document array */
    +    for(i = 0; i < 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 < 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;
    +}
    +
    + +

    Notes

    + +

    How to build programs using Odeum is the same as the case of Depot.

    + +
    gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
    +
    + +

    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.

    + +

    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.

    + +

    Query Language

    + +

    The query language of the function `odquery' is a basic language following this grammar:

    + +
    expr ::= subexpr ( op subexpr )*
    +subexpr ::= WORD
    +subexpr ::= LPAREN expr RPAREN
    +
    + +

    Operators are "&" (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 "&", "|", "!", "(", and ")" to be delimiter characters. Consecutive words are treated as having an implicit "&" operator between them, so "zed shaw" is actually "zed & shaw".

    + +

    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.

    + +
    + +

    Commands for Odeum

    + +

    Odeum has the following command line interfaces.

    + +

    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.

    + +
    +
    odmgr create name
    +
    Create a database file.
    +
    odmgr put [-uri str] [-title str] [-author str] [-date str] [-wmax num] [-keep] name [file]
    +
    Add a document by reading a file. If `file' is omitted, the standard input is read and URI is needed.
    +
    odmgr out [-id] name expr
    +
    Delete a document specified by a URI.
    +
    odmgr get [-id] [-t|-h] name expr
    +
    Show a document specified by a URI. The output is the ID number and the URI of a document, in tab separated format.
    +
    odmgr search [-max num] [-or] [-idf] [-t|-h|-n] name words...
    +
    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.
    +
    odmgr list [-t|-h] name
    +
    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.
    +
    odmgr optimize name
    +
    Optimize a database.
    +
    odmgr inform name
    +
    Output miscellaneous information.
    +
    odmgr merge name elems...
    +
    Merge plural databases.
    +
    odmgr remove name
    +
    Remove a database directory.
    +
    odmgr break [-h|-k|-s] [file]
    +
    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.
    +
    odmgr version
    +
    Output version information of QDBM.
    +
    + +

    Options feature the following.

    + +
      +
    • -uri str : specify the URI of the document explicitly.
    • +
    • -title str : specify the title of the document.
    • +
    • -author str : specify the author of the document.
    • +
    • -date str : specify the modified date of the document.
    • +
    • -wmax num : specify the max number of words to be stored.
    • +
    • -keep : the storing mode is not to be overwrite.
    • +
    • -id : specify a document not by a URI but by an ID number.
    • +
    • -t : output the details of a document in tab separated format.
    • +
    • -h : output the details of a document in human-readable format.
    • +
    • -k : output keywords of a document.
    • +
    • -s : output summary of a document.
    • +
    • -max num : specify the max number of documents of the output.
    • +
    • -or : perform OR search, nut AND search.
    • +
    • -idf : tune scores with IDF.
    • +
    • -n : show ID numbers and scores only.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    odtest write [-tune ibnum idnum cbnum csiz] name dnum wnum pnum
    +
    Store documents with random attributes and random words.
    +
    odtest read name
    +
    Retrieve all documents of the database above.
    +
    odtest combo name
    +
    Perform combination test of various operations.
    +
    odtest wicked name dnum
    +
    Perform updating operations selected at random.
    +
    + +

    Options feature the following.

    + +
      +
    • -tune ibnum idnum cbnum csiz : set tuning parameters.
    • +
    + +

    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'.

    + +

    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.

    + +
    +
    odidx register [-l file] [-wmax num] [-tsuf sufs] [-hsuf sufs] name [dir]
    +
    Register files in the specified directory. If `dir' is omitted, the current directory is specified.
    +
    odidx relate name
    +
    Add score information for relational document search to each documents in the database.
    +
    odidx purge name
    +
    Purge documents which are not existing on the local files system.
    +
    + +

    Options feature the following.

    + +
      +
    • -l file : read a file and get list of paths of files to register. If `-' is specified, the standard input is read.
    • +
    • -wmax num : specify the max number of words to be stored in the document database.
    • +
    • -tsuf sufs : specify suffixes of plain text files in comma separated format. The default is `-tsuf .txt,.text'.
    • +
    • -hsuf sufs : specify suffixes of HTML files in comma separated format. The default is `-hsuf .html,.htm'.
    • +
    + +

    This command returns 0 on success, another on failure.

    + +

    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.

    + +
    odidx register -tsuf ".txt,.c,.h" -hsuf "" casket /home/mikio
    +
    + +

    Thus, to retrieve documents which include `unix' and `posix' and show the top 8 terms, perform the following command.

    + +
    odmgr search -max 8 -h casket "unix posix"
    +
    + +

    A database generated by `odidx' is available with the CGI script which is included in QDBM for full-text search.

    + +
    + +

    File Format

    + +

    File Format of Depot

    + +

    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.

    + +

    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.

    + +
      +
    1. magic number: from offset 0, contains "[DEPOT]\n\f" for big endian or "[depot]\n\f" for little endian.
    2. +
    3. version number: decimal string of the version number of the library.
    4. +
    5. flags for wrappers: from offset 16, type of `int'.
    6. +
    7. file size: from offset 24, type of `int'.
    8. +
    9. number of the bucket: from offset 32, type of `int'.
    10. +
    11. number of records: from offset 40, type of `int'.
    12. +
    + +

    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.

    + +

    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.

    + +
      +
    1. flags: type of `int'.
    2. +
    3. second hash value: type of `int'.
    4. +
    5. size of the key: type of `int'.
    6. +
    7. size of the value: type of `int'.
    8. +
    9. size of the padding: type of `int'.
    10. +
    11. offset of the left child: type of `int'.
    12. +
    13. offset of the right child: type of `int'.
    14. +
    15. entity of the key: serial bytes with variable length.
    16. +
    17. entity of the value: serial bytes with variable length.
    18. +
    19. padding data: void serial bytes with variable length.
    20. +
    + +

    File Format of Villa

    + +

    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.

    + +

    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.

    + +

    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.

    + +
      +
    1. size of the key: type of variable length number
    2. +
    3. entity of the key: serial bytes with variable length
    4. +
    5. number of values: type of variable length number
    6. +
    7. list of values: serial bytes repeating the following expressions
        +
      1. size: type of variable length number
      2. +
      3. entity of the key: serial bytes with variable length
      4. +
    8. +
    + +

    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.

    + +
      +
    1. ID of the previous leaf: type of variable length number
    2. +
    3. ID of the next leaf: type of variable length number
    4. +
    5. list of records: concatenation of serialized records
    6. +
    + +

    Index is logical unit of a pointer to search for pages. An index is serialized int the following format.

    + +
      +
    1. ID of the referring page: type of variable length number
    2. +
    3. size of the key: type of variable length number
    4. +
    5. entity of the key: serial bytes with variable length
    6. +
    + +

    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.

    + +
      +
    1. ID of the first child node: type of variable length number
    2. +
    3. list of indexes: concatenation of serialized indexes
    4. +
    + +

    Notes

    + +

    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.

    + +

    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.

    + +

    For the command `file' to recognize database files, append the following expressions into `magic' file.

    + +
    0       string          [DEPOT]\n\f     QDBM, big endian
    +>12     string          x               \b, version=%s
    +>19     byte            ^1              \b, Hash
    +>19     byte            &1              \b, B+ tree
    +>19     byte            &2              \b (deflated:ZLIB)
    +>19     byte            &4              \b (deflated:LZO)
    +>19     byte            &8              \b (deflated:BZIP2)
    +>24     belong          x               \b, filesize=%d
    +>32     belong          x               \b, buckets=%d
    +>40     belong          x               \b, records=%d
    +0       string          [depot]\n\f     QDBM, little endian
    +>12     string          x               \b, version=%s
    +>16     byte            ^1              \b, Hash
    +>16     byte            &1              \b, B+ tree
    +>16     byte            &2              \b (deflated:ZLIB)
    +>16     byte            &4              \b (deflated:LZO)
    +>16     byte            &8              \b (deflated:BZIP2)
    +>24     lelong          x               \b, filesize=%d
    +>32     lelong          x               \b, buckets=%d
    +>40     lelong          x               \b, records=%d
    +
    + +
    + +

    Porting

    + +

    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.

    + +
      +
    • Makefile.in : base of `Makefile', used by `./configure'.
    • +
    • myconf.h : configuration of system dependency.
    • +
    • depot.h : header of the basic API.
    • +
    • curia.h : header of the extended API.
    • +
    • relic.h : header of the NDBM-compatible API.
    • +
    • hovel.h : header of the GDBM-compatible API.
    • +
    • cabin.h : header of the utility API.
    • +
    • villa.h : header of the advanced API.
    • +
    • vista.h : header of the extended advanced API.
    • +
    • odeum.h : header of the inverted API.
    • +
    • myconf.c : implementation of system dependency.
    • +
    • depot.c : implementation of the basic API.
    • +
    • curia.c : implementation of the extended API.
    • +
    • relic.c : implementation of the NDBM-compatible API.
    • +
    • hovel.c : implementation of the GDBM-compatible API.
    • +
    • cabin.c : implementation of the utility API.
    • +
    • villa.c : implementation of the advanced API.
    • +
    • vista.c : implementation of the extended advanced API.
    • +
    • odeum.c : implementation of the inverted API.
    • +
    + +

    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'.

    + +

    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.

    + +
    + +

    Bugs

    + +

    Each document of QDBM should be calibrated by native English speakers.

    + +

    There is no such bug which are found but not fixed, as crash by segmentation fault, unexpected data vanishing, memory leak and so on.

    + +

    If you find any bug, report it to the author, with the information of the version of QDBM, the operating system and the compiler.

    + +

    Databases created with QDBM version 1.7.13 or earlier are not compatible to ones of the later versions.

    + +
    + +

    Frequently Asked Questions

    + +
    +
    Q. : Does QDBM support SQL?
    +
    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.
    +
    Q. : After all, how different from GDBM (NDBM, SDBM, Berkeley DB)?
    +
    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.
    +
    Q. : Which API should I use?
    +
    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.
    +
    Q. : What is bibliography?
    +
    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.
    +
    Q. : Are there good sample codes for applications?
    +
    A. : Refer to the source code of commands of each API. `dptsv.c', `crtsv.c' and `vltsv.c' are simplest.
    +
    Q. : My database file has been broken. Why?
    +
    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.
    +
    Q. : How robust are databases of QDBM?
    +
    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.
    +
    Q: How should I use alignment of Depot and Curia?
    +
    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.
    +
    Q. : How should I tune performance parameters of Villa?
    +
    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.
    +
    Q. : Which is the most preferable of ZLIB, LZO or BZIP2 for Villa?
    +
    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.
    +
    Q. : What is `sparse file'?
    +
    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.
    +
    Q. : Why Depot and Curia do not feature transaction?
    +
    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.
    +
    Q. : How should I tune the system for performance?
    +
    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.
    +
    Q. : Can I build QDBM using `cc' instead of `gcc'?
    +
    A. : Yes. Try to build QDBM with `LTmakefile'.
    +
    Q. : Can I build QDBM using Visual C++?
    +
    A. : Yes. Use `VCmakefile' instead of `Makefile'.
    +
    Q. : Can I use QDBM in other languages?
    +
    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.
    +
    Q. : What does `QDBM' mean?
    +
    A. : `QDBM' stands for `Quick Database Manager'. It means that processing speed is high, and that you can write applications quickly.
    +
    + +
    + +

    Copying

    + +

    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 (See the file `COPYING'); if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

    + +

    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'.

    + +
    + + + + + + 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 +#include +#include +#include + + +#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 +#include +#include +#include + +#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 +#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 +#include +#include +#include +#include +#include + +#undef TRUE +#define TRUE 1 /* boolean true */ +#undef FALSE +#define FALSE 0 /* boolean false */ + + +/* for RISC OS */ +#if defined(__riscos__) || defined(__riscos) +#include +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +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("\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("\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("\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("\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("\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 +#include +#include +#include +#include +#include + +#undef TRUE +#define TRUE 1 /* boolean true */ +#undef FALSE +#define FALSE 0 /* boolean false */ + + +/* for RISC OS */ +#if defined(__riscos__) || defined(__riscos) +#include +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 new file mode 100755 index 00000000..2ab60963 --- /dev/null +++ b/runtest.pl.in @@ -0,0 +1,263 @@ +#!@PERL@ + +use strict; +use warnings; + +use File::Basename; +use Getopt::Std; + +chdir(dirname($0)); +use lib dirname($0).'/infrastructure'; + +use BoxPlatform; + +my $prepare_only = 0; +my $verbose_build = 0; +our ($opt_n, $opt_v); +getopts('nv'); + +# Don't actually run the test, just prepare for it. +$prepare_only = $opt_n; +$verbose_build = $opt_v; + +my ($test_name,$test_mode) = @ARGV; +$test_mode = 'debug' if not defined $test_mode or $test_mode eq ''; +$test_mode = lc($test_mode); + +if($test_name eq '' || ($test_mode ne 'debug' && $test_mode ne 'release')) +{ + print <<__E; +Run Test utility -- bad usage. + +runtest.pl (test|ALL) [release|debug] + +Mode defaults to debug. + +__E + exit(2); +} + +my @results; +my $exit_code = 0; + +if($test_name ne 'ALL') +{ + # run one or more specified test + if ($test_name =~ m/,/) + { + foreach my $test (split m/,/, $test_name) + { + runtest($test); + } + } + else + { + runtest($test_name); + } +} +else +{ + # run all tests + my @tests; + open MODULES,'modules.txt' or die "Can't open modules file"; + while() + { + # omit bits on some platforms? + next if m/\AEND-OMIT/; + if(m/\AOMIT:(.+)/) + { + if($1 eq $build_os or $1 eq $ac_target_os) + { + while() + { + last if m/\AEND-OMIT/; + } + } + next; + } + push @tests,$1 if m~\Atest/(\w+)\s~; + } + close MODULES; + + runtest($_) for(@tests) +} + +# report results +print "--------\n",join("\n",@results),"\n"; + +if ($exit_code != 0) +{ + print <<__E; + +One or more tests have failed. Please check the following common causes: + +* Check that no instances of bbstored or bbackupd are already running + on this machine. +* Make sure there isn't a firewall blocking incoming or outgoing connections + on port 2201. +* Check that there is sufficient space in the filesystem that the tests + are being run from (at least 1 GB free). +* The backupdiff test fails if it takes too long, so it's sensitive to + the speed of the host and your connection to it. + +After checking all the above, if you still have problems please contact +us on the mailing list, boxbackup\@boxbackup.org. Thanks! +__E +} + +exit $exit_code; + +sub runtest +{ + my ($t) = @_; + + # Attempt to make this test. + my $flag = ($test_mode eq 'release')?(BoxPlatform::make_flag('RELEASE')):''; + my ($make_res, $test_project_exe); + + if($target_msvc) + { + $test_project_exe = "test_$t"; + # Assume that MSVC projects are built with CMake, so we can use + # MSBuild to run the tests. + my $test_src_dir = "test\\$t"; + my $test_dst_dir = "$test_mode\\test\\$t"; + my $quiet = $verbose_build ? "" : "/consoleloggerparameters:ErrorsOnly"; + + my @commands = ( + "msbuild /nologo $quiet ". + "infrastructure\\cmake\\build\\INSTALL.vcxproj", + "xcopy /s /i /y /q $test_src_dir $test_dst_dir", + "copy infrastructure\\cmake\\build\\$test_mode\\$test_project_exe.exe $test_dst_dir" + ); + + if(-d $test_dst_dir) + { + unshift @commands, "rd /s /q $test_dst_dir"; + } + + foreach my $command (@commands) + { + $make_res = system($command); + if ($make_res != 0) + { + push @results, "$t: make failed: $command"; + last; + } + } + + # Windows doesn't support testextra files either, so fake it. + if ($make_res == 0 and -r "$test_src_dir/testextra") + { + open EXTRA, "$test_src_dir/testextra" + or die "$test_src_dir/testextra: $!"; + foreach my $line () + { + chomp $line; + if ($line =~ m/^mkdir (.*)/) + { + mkdir("$test_dst_dir/$1") + or die "$test_dst_dir/$1: $!"; + } + elsif ($line =~ m/^rm -rf (.*)/) + { + if(-d "$test_dst_dir\\$1") + { + my $cmd = "rd /s/q $test_dst_dir\\$1"; + my $status = system($cmd); + $status == 0 or die "$cmd: failed with ". + "status $status"; + } + } + elsif ($line =~ m/^cp (.*) (.*)/) + { + my ($src, $dst) = ($1, $2); + $src =~ s|/|\\|g; + $dst =~ s|/|\\|g; + my $cmd = "xcopy /s /i /y /q ". + "$test_dst_dir\\$src $test_dst_dir\\$dst"; + my $status = system($cmd); + $status == 0 or die "$cmd: failed with ". + "status $status"; + } + else + { + die "Unsupported command in ". + "$test_src_dir/testextra: $line"; + } + } + } + } + else + { + my $quiet = $verbose_build ? "VERBOSE=1" : ""; + $make_res = system("cd test/$t && $make_command $quiet $flag"); + } + + if($make_res != 0) + { + push @results,"$t: make failed"; + $exit_code = 2; + return; + } + + my $logfile = "test-$t.log"; + my $test_res; + + if($prepare_only) + { + return; + } + + # run it + if($target_msvc) + { + # no tee.exe, so let's do it ourselves. + open LOG, ">$logfile" or die "$logfile: $!"; + chdir("$test_mode/test/$t"); + open TEE, "$test_project_exe.exe |" + or die "$test_project_exe.exe: $!"; + while (my $line = ) + { + print $line; + print LOG $line; + } + close LOG; + close TEE; + chdir("../../.."); + } + else + { + $test_res = system("cd $test_mode/test/$t ; ./t 2>&1 " . + "| tee ../../../$logfile"); + } + + # open test results + if(open RESULTS, $logfile) + { + my $last; + while() + { + $last = $_ if m/\w/; + } + close RESULTS; + + chomp $last; + $last =~ s/\r//; + push @results, "$t: $last"; + + if ($last ne "PASSED") + { + $exit_code = 1; + } + } + else + { + push @results, + "$t: failed to open test log file: $logfile: $!"; + } + + # delete test results + # unlink $logfile; +} + diff --git a/test/backupdiff/difftestfiles.cpp b/test/backupdiff/difftestfiles.cpp new file mode 100644 index 00000000..33690f6b --- /dev/null +++ b/test/backupdiff/difftestfiles.cpp @@ -0,0 +1,295 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: createtestfiles.cpp +// Purpose: Create the test files for the backupdiff test +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "FileStream.h" +#include "PartialReadStream.h" +#include "Test.h" +#include "RollingChecksum.h" + +#include "MemLeakFindOn.h" + +#define ACT_END 0 +#define ACT_COPY 1 +#define ACT_NEW 2 +#define ACT_SKIP 3 +#define ACT_COPYEND 4 + +typedef struct +{ + int action, length, seed; +} gen_action; + +#define INITIAL_FILE_LENGTH (128*1024 + 342) + + +gen_action file1actions[] = { + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file2actions[] = { + {ACT_COPY, 16*1024, 0}, + // Do blocks on block boundaries, but swapped around a little + {ACT_SKIP, 4*1024, 0}, + {ACT_COPY, 8*1024, 0}, + {ACT_SKIP, -12*1024, 0}, + {ACT_COPY, 4*1024, 0}, + {ACT_SKIP, 8*1024, 0}, + // Get rest of file with some new data inserted + {ACT_COPY, 37*1024 + 12, 0}, + {ACT_NEW, 23*1024 + 129, 23990}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file3actions[] = { + {ACT_COPY, 12*1024 + 983, 0}, + {ACT_SKIP, 37*1024 + 12, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file4actions[] = { + {ACT_COPY, 20*1024 + 2385, 0}, + {ACT_NEW, 12, 2334}, + {ACT_COPY, 16*1024 + 385, 0}, + {ACT_SKIP, 9*1024 + 42, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +// insert 1 byte a block into the file, between two other blocks +gen_action file5actions[] = { + {ACT_COPY, 4*1024, 0}, + {ACT_NEW, 1, 2334}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +gen_action file6actions[] = { + {ACT_NEW, 6*1024, 12353452}, + {ACT_COPYEND, 0, 0}, + {ACT_END, 0, 0} }; + +// but delete that one byte block, it's annoying +gen_action file7actions[] = { + {ACT_COPY, 10*1024, 0}, + {ACT_SKIP, 1, 0}, + {ACT_COPYEND, 0, 0}, + {ACT_NEW, 7*1024, 1235352}, + {ACT_END, 0, 0} }; + +gen_action file8actions[] = { + {ACT_NEW, 54*1024 + 9, 125352}, + {ACT_END, 0, 0} }; + +gen_action file9actions[] = { + {ACT_END, 0, 0} }; + +gen_action *testfiles[] = {file1actions, file2actions, file3actions, file4actions, + file5actions, file6actions, file7actions, file8actions, file9actions, 0}; + + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +void make_random_data(void *buffer, int size, int seed) +{ + R250 rand(seed); + + int n = size / sizeof(int); + int *b = (int*)buffer; + for(int l = 0; l < n; ++l) + { + b[l] = rand.next(); + } +} + +void write_test_data(IOStream &rstream, int size, int seed) +{ + R250 rand(seed); + + while(size > 0) + { + // make a nice buffer of data + int buffer[2048/sizeof(int)]; + for(unsigned int l = 0; l < (sizeof(buffer) / sizeof(int)); ++l) + { + buffer[l] = rand.next(); + } + + // Write out... + unsigned int w = size; + if(w > sizeof(buffer)) w = sizeof(buffer); + rstream.Write(buffer, w); + + size -= w; + } +} + +void gen_varient(IOStream &out, char *sourcename, gen_action *pact) +{ + // Open source + FileStream source(sourcename); + + while(true) + { + switch(pact->action) + { + case ACT_END: + { + // all done + return; + } + case ACT_COPY: + { + PartialReadStream copy(source, pact->length); + copy.CopyStreamTo(out); + break; + } + case ACT_NEW: + { + write_test_data(out, pact->length, pact->seed); + break; + } + case ACT_SKIP: + { + source.Seek(pact->length, IOStream::SeekType_Relative); + break; + } + case ACT_COPYEND: + { + source.CopyStreamTo(out); + break; + } + } + + ++pact; + } +} + +void create_test_files() +{ + // First, the keys for the crypto + { + FileStream keys("testfiles/backup.keys", O_WRONLY | O_CREAT); + write_test_data(keys, 1024, 237); + } + + // Create the initial file -- needs various special properties... + // 1) Two blocks much be the different, but have the same weak checksum + // 2) A block must exist twice, but at an offset which isn't a multiple of the block size. + { + FileStream f0("testfiles/f0", O_WRONLY | O_CREAT); + // Write first bit. + write_test_data(f0, (16*1024), 20012); + // Now repeated checksum blocks + uint8_t blk[4096]; + make_random_data(blk, sizeof(blk), 12201); + // Three magic numbers which make the checksum work: Use this perl to find them: + /* + for($z = 1; $z < 4096; $z++) + { + for($n = 0; $n <= 255; $n++) + { + for($m = 0; $m <= 255; $m++) + { + if($n != $m && (($n*4096 + $m*(4096-$z)) % (64*1024) == ($n*(4096-$z) + $m*4096) % (64*1024))) + { + print "$z: $n $m\n"; + } + } + } + } + */ + blk[0] = 255; + blk[1024] = 191; + // Checksum to check + RollingChecksum c1(blk, sizeof(blk)); + // Write + f0.Write(blk, sizeof(blk)); + // Adjust block and write again + uint8_t blk2[4096]; + memcpy(blk2, blk, sizeof(blk2)); + blk2[1024] = 255; + blk2[0] = 191; + TEST_THAT(::memcmp(blk2, blk, sizeof(blk)) != 0); + RollingChecksum c2(blk2, sizeof(blk2)); + f0.Write(blk2, sizeof(blk2)); + // Check checksums + TEST_THAT(c1.GetChecksum() == c2.GetChecksum()); + + // Another 4k block + write_test_data(f0, (4*1024), 99209); + // Offset block + make_random_data(blk, 2048, 1234199); + f0.Write(blk, 2048); + f0.Write(blk, 2048); + f0.Write(blk, 2048); + make_random_data(blk, 2048, 1343278); + f0.Write(blk, 2048); + + write_test_data(f0, INITIAL_FILE_LENGTH - (16*1024) - ((4*1024)*2) - (4*1024) - (2048*4), 202); + + } + + // Then... create the varients + for(int l = 0; testfiles[l] != 0; ++l) + { + char n1[256]; + char n2[256]; + sprintf(n1, "testfiles/f%d", l + 1); + sprintf(n2, "testfiles/f%d", l); + + FileStream f1(n1, O_WRONLY | O_CREAT); + gen_varient(f1, n2, testfiles[l]); + } +} + + diff --git a/test/backupdiff/testbackupdiff.cpp b/test/backupdiff/testbackupdiff.cpp new file mode 100644 index 00000000..ec3f24e2 --- /dev/null +++ b/test/backupdiff/testbackupdiff.cpp @@ -0,0 +1,606 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupdiff.cpp +// Purpose: Test diffing routines for backup store files +// Created: 12/1/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "BackupClientCryptoKeys.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileEncodeStream.h" +#include "BackupStoreFilenameClear.h" +#include "FileStream.h" +#include "BackupStoreFileWire.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreFileCryptVar.h" +#include "BackupStoreException.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +using namespace BackupStoreFileCryptVar; + + +// from another file +void create_test_files(); + +bool files_identical(const char *file1, const char *file2) +{ + FileStream f1(file1); + FileStream f2(file2); + + if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) + { + return false; + } + + while(f1.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1.Read(buffer1, sizeof(buffer1)); + if(f2.Read(buffer2, s) != s) + { + return false; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + return false; + } + } + + if(f2.StreamDataLeft()) + { + return false; + } + + return true; +} + +bool make_file_of_zeros(const char *filename, size_t size) +{ + #ifdef WIN32 + HANDLE handle = openfile(filename, O_WRONLY | O_CREAT | O_EXCL, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + TEST_THAT(SetFilePointer(handle, size, NULL, FILE_BEGIN) + != INVALID_SET_FILE_POINTER); + TEST_THAT(GetLastError() == NO_ERROR); + BOOL result = SetEndOfFile(handle); + if (result != TRUE) + { + BOX_ERROR("Failed to create large file " << filename << + " (" << (size >> 20) << " MB): " << + GetErrorMessage(GetLastError())); + } + TEST_THAT(result == TRUE); + TEST_THAT(CloseHandle(handle) == TRUE); + #else + int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) perror(filename); + TEST_THAT(fd >= 0); + TEST_THAT(ftruncate(fd, size) == 0); + TEST_THAT(close(fd) == 0); + #endif + + bool correct_size = ((size_t)TestGetFileSize(filename) == size); + TEST_THAT(correct_size); + if (!correct_size) + { + BOX_ERROR("Failed to create large file " << filename << + " (" << (size >> 20) << " MB): " << + "got " << (TestGetFileSize(filename) >> 20) << + " MB instead"); + } + return correct_size; +} + + +void check_encoded_file(const char *filename, int64_t OtherFileID, int new_blocks_expected, int old_blocks_expected) +{ + FileStream enc(filename); + + // Use the interface verify routine + int64_t otherIDFromFile = 0; + TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc, &otherIDFromFile)); + TEST_THAT(otherIDFromFile == OtherFileID); + + // Now do our own reading + enc.Seek(0, IOStream::SeekType_Absolute); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + // Read in header to check magic value is as expected + file_BlockIndexHeader hdr; + TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); + TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); + TEST_THAT((uint64_t)box_ntoh64(hdr.mOtherFileID) == (uint64_t)OtherFileID); + // number of blocks + int64_t nblocks = box_ntoh64(hdr.mNumBlocks); + BOX_TRACE("Reading index from '" << filename << "', has " << + nblocks << " blocks"); + BOX_TRACE("======== ===== ========== ======== ========"); + BOX_TRACE(" Index Where EncSz/Idx Size WChcksm"); + // Read them all in + int64_t nnew = 0, nold = 0; + for(int64_t b = 0; b < nblocks; ++b) + { + file_BlockIndexEntry en; + TEST_THAT(enc.ReadFullBuffer(&en, sizeof(en), 0)); + int64_t s = box_ntoh64(en.mEncodedSize); + + // Decode the rest + uint64_t iv = box_ntoh64(hdr.mEntryIVBase); + iv += b; + sBlowfishDecryptBlockEntry.SetIV(&iv); + file_BlockIndexEntryEnc entryEnc; + sBlowfishDecryptBlockEntry.TransformBlock(&entryEnc, + sizeof(entryEnc), en.mEnEnc, sizeof(en.mEnEnc)); + + + if(s > 0) + { + nnew++; + BOX_TRACE(std::setw(8) << b << " this s=" << + std::setw(8) << s << " " << + std::setw(8) << ntohl(entryEnc.mSize) << " " << + std::setw(8) << std::setfill('0') << + std::hex << ntohl(entryEnc.mWeakChecksum)); + } + else + { + nold++; + BOX_TRACE(std::setw(8) << b << " other i=" << + std::setw(8) << (0-s) << " " << + std::setw(8) << ntohl(entryEnc.mSize) << " " << + std::setw(8) << std::setfill('0') << + std::hex << ntohl(entryEnc.mWeakChecksum)); + } + } + BOX_TRACE("======== ===== ========== ======== ========"); + TEST_THAT(new_blocks_expected == nnew); + TEST_THAT(old_blocks_expected == nold); +} + +void test_diff(int from, int to, int new_blocks_expected, int old_blocks_expected, bool expect_completely_different = false) +{ + // First, get the block index of the thing it's comparing against + char from_encoded[256]; + sprintf(from_encoded, "testfiles/f%d.encoded", from); + FileStream blockindex(from_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + // make filenames + char from_orig[256]; + sprintf(from_orig, "testfiles/f%d", from); + char to_encoded[256]; + sprintf(to_encoded, "testfiles/f%d.encoded", to); + char to_diff[256]; + sprintf(to_diff, "testfiles/f%d.diff", to); + char to_orig[256]; + sprintf(to_orig, "testfiles/f%d", to); + char rev_diff[256]; + sprintf(rev_diff, "testfiles/f%d.revdiff", to); + char from_rebuild[256]; + sprintf(from_rebuild, "testfiles/f%d.rebuilt", to); + char from_rebuild_dec[256]; + sprintf(from_rebuild_dec, "testfiles/f%d.rebuilt_dec", to); + + // Then call the encode varient for diffing files + bool completelyDifferent = !expect_completely_different; // oposite of what we want + { + BackupStoreFilenameClear f1name("filename"); + FileStream out(to_diff, O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded( + BackupStoreFile::EncodeFileDiff( + to_orig, + 1 /* dir ID */, + f1name, + 1000 + from /* object ID of the file diffing from */, + blockindex, + IOStream::TimeOutInfinite, + NULL, // DiffTimer interface + 0, + &completelyDifferent)); + encoded->CopyStreamTo(out); + } + TEST_THAT(completelyDifferent == expect_completely_different); + + // Test that the number of blocks in the file match what's expected + check_encoded_file(to_diff, expect_completely_different?(0):(1000 + from), new_blocks_expected, old_blocks_expected); + + // filename + char to_testdec[256]; + sprintf(to_testdec, "testfiles/f%d.testdec", to); + + if(!completelyDifferent) + { + // Then produce a combined file + { + FileStream diff(to_diff); + FileStream diff2(to_diff); + FileStream from(from_encoded); + FileStream out(to_encoded, O_WRONLY | O_CREAT | O_EXCL); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + + // And check it + check_encoded_file(to_encoded, 0, new_blocks_expected + old_blocks_expected, 0); + } + else + { +#ifdef WIN32 + // Emulate the above stage! + char src[256], dst[256]; + sprintf(src, "testfiles\\f%d.diff", to); + sprintf(dst, "testfiles\\f%d.encoded", to); + TEST_THAT(CopyFile(src, dst, FALSE) != 0) +#else + // Emulate the above stage! + char cmd[256]; + sprintf(cmd, "cp testfiles/f%d.diff testfiles/f%d.encoded", to, to); + ::system(cmd); +#endif + } + + // Decode it + { + FileStream enc(to_encoded); + BackupStoreFile::DecodeFile(enc, to_testdec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(to_orig, to_testdec)); + } + + // Then do some comparisons against the block index + { + FileStream index(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(index); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(to_orig, index, IOStream::TimeOutInfinite) == true); + } + { + char from_orig[256]; + sprintf(from_orig, "testfiles/f%d", from); + FileStream index(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(index); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(from_orig, index, IOStream::TimeOutInfinite) == files_identical(from_orig, to_orig)); + } + + // Check that combined index creation works as expected + { + // Load a combined index into memory + FileStream diff(to_diff); + FileStream from(from_encoded); + std::auto_ptr indexCmbStr(BackupStoreFile::CombineFileIndices(diff, from)); + CollectInBufferStream indexCmb; + indexCmbStr->CopyStreamTo(indexCmb); + // Then check that it's as expected! + FileStream result(to_encoded); + BackupStoreFile::MoveStreamPositionToBlockIndex(result); + CollectInBufferStream index; + result.CopyStreamTo(index); + TEST_THAT(indexCmb.GetSize() == index.GetSize()); + TEST_THAT(::memcmp(indexCmb.GetBuffer(), index.GetBuffer(), index.GetSize()) == 0); + } + + // Check that reverse delta can be made, and that it decodes OK + { + // Create reverse delta + { + bool reversedCompletelyDifferent = !completelyDifferent; + FileStream diff(to_diff); + FileStream from(from_encoded); + FileStream from2(from_encoded); + FileStream reversed(rev_diff, O_WRONLY | O_CREAT); + BackupStoreFile::ReverseDiffFile(diff, from, from2, reversed, to, &reversedCompletelyDifferent); + TEST_THAT(reversedCompletelyDifferent == completelyDifferent); + } + // Use it to combine a file + { + FileStream diff(rev_diff); + FileStream diff2(rev_diff); + FileStream from(to_encoded); + FileStream out(from_rebuild, O_WRONLY | O_CREAT | O_EXCL); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + // And then confirm that this file is actually the one we want + { + FileStream enc(from_rebuild); + BackupStoreFile::DecodeFile(enc, from_rebuild_dec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(from_orig, from_rebuild_dec)); + } + // Do some extra checking + { + TEST_THAT(files_identical(from_rebuild, from_encoded)); + } + } +} + +void test_combined_diff(int version1, int version2, int serial) +{ + char combined_file[256]; + char last_diff[256]; + sprintf(last_diff, "testfiles/f%d.diff", version1 + 1); // ie from version1 to version1 + 1 + + for(int v = version1 + 2; v <= version2; ++v) + { + FileStream diff1(last_diff); + char next_diff[256]; + sprintf(next_diff, "testfiles/f%d.diff", v); + FileStream diff2(next_diff); + FileStream diff2b(next_diff); + sprintf(combined_file, "testfiles/comb%d_%d.cmbdiff", version1, v); + FileStream out(combined_file, O_WRONLY | O_CREAT); + BackupStoreFile::CombineDiffs(diff1, diff2, diff2b, out); + strcpy(last_diff, combined_file); + } + + // Then do a combine on it, and check that it decodes to the right thing + char orig_enc[256]; + sprintf(orig_enc, "testfiles/f%d.encoded", version1); + char combined_out[256]; + sprintf(combined_out, "testfiles/comb%d_%d.out", version1, version2); + + { + FileStream diff(combined_file); + FileStream diff2(combined_file); + FileStream from(orig_enc); + FileStream out(combined_out, O_WRONLY | O_CREAT); + BackupStoreFile::CombineFile(diff, diff2, from, out); + } + + char combined_out_dec[256]; + sprintf(combined_out_dec, "testfiles/comb%d_%d_s%d.dec", version1, version2, serial); + char to_orig[256]; + sprintf(to_orig, "testfiles/f%d", version2); + + { + FileStream enc(combined_out); + BackupStoreFile::DecodeFile(enc, combined_out_dec, IOStream::TimeOutInfinite); + TEST_THAT(files_identical(to_orig, combined_out_dec)); + } + +} + +#define MAX_DIFF 9 +void test_combined_diffs() +{ + int serial = 0; + + // Number of items to combine at once + for(int stages = 2; stages <= 4; ++stages) + { + // Offset to get complete coverage + for(int offset = 0; offset < stages; ++offset) + { + // And then actual end file number + for(int f = 0; f <= (MAX_DIFF - stages - offset); ++f) + { + // And finally, do something! + test_combined_diff(offset + f, offset + f + stages, ++serial); + } + } + } +} + +int test(int argc, const char *argv[]) +{ + // Want to trace out all the details + #ifndef BOX_RELEASE_BUILD + #ifndef WIN32 + BackupStoreFile::TraceDetailsOfDiffProcess = true; + #endif + #endif + + // Create all the test files + create_test_files(); + + // Setup the crypto + BackupClientCryptoKeys_Setup("testfiles/backup.keys"); + + // Encode the first file + { + BackupStoreFilenameClear f0name("f0"); + FileStream out("testfiles/f0.encoded", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f0", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + out.Close(); + check_encoded_file("testfiles/f0.encoded", 0, 33, 0); + } + + // Check the "seek to index" code + { + FileStream enc("testfiles/f0.encoded"); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + // Read in header to check magic value is as expected + file_BlockIndexHeader hdr; + TEST_THAT(enc.ReadFullBuffer(&hdr, sizeof(hdr), 0)); + TEST_THAT(hdr.mMagicValue == (int32_t)htonl(OBJECTMAGIC_FILE_BLOCKS_MAGIC_VALUE_V1)); + } + + // Diff some files -- parameters are from number, to number, + // then the number of new blocks expected, and the number of old blocks expected. + + // Diff the original file to a copy of itself, and check that there is no data in the file + // This checks that the hash table is constructed properly, because two of the blocks share + // the same weak checksum. + test_diff(0, 1, 0, 33); + + // Insert some new data + // Blocks from old file moved whole, but put in different order + test_diff(1, 2, 7, 32); + + // Delete some data, but not on block boundaries + test_diff(2, 3, 1, 29); + + // Add a very small amount of data, not on block boundary + // delete a little data + test_diff(3, 4, 3, 25); + + // 1 byte insertion between two blocks + test_diff(4, 5, 1, 28); + + // a file with some new content at the very beginning + // NOTE: You might expect the last numbers to be 2, 29, but the small 1 byte block isn't searched for + test_diff(5, 6, 3, 28); + + // some new content at the very end + // NOTE: 1 byte block deleted, so number aren't what you'd initial expect. + test_diff(6, 7, 2, 30); + + // a completely different file, with no blocks matching. + test_diff(7, 8, 14, 0, true /* completely different expected */); + + // diff to zero sized file + test_diff(8, 9, 0, 0, true /* completely different expected */); + + // Test that combining diffs works + test_combined_diffs(); + + // Check zero sized file works OK to encode on its own, using normal encoding + { + { + // Encode + BackupStoreFilenameClear fn("filename"); + FileStream out("testfiles/f9.zerotest", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/f9", 1 /* dir ID */, fn)); + encoded->CopyStreamTo(out); + out.Close(); + check_encoded_file("testfiles/f9.zerotest", 0, 0, 0); + } + { + // Decode + FileStream enc("testfiles/f9.zerotest"); + BackupStoreFile::DecodeFile(enc, "testfiles/f9.testdec.zero", IOStream::TimeOutInfinite); + TEST_THAT(files_identical("testfiles/f9", "testfiles/f9.testdec.zero")); + } + } + +#ifndef WIN32 + // Check that symlinks aren't diffed + TEST_THAT(::symlink("f2", "testfiles/f2.symlink") == 0) + // And go and diff it against the previous encoded file + { + bool completelyDifferent = false; + { + FileStream blockindex("testfiles/f1.encoded"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("filename"); + FileStream out("testfiles/f2.symlink.diff", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded( + BackupStoreFile::EncodeFileDiff( + "testfiles/f2.symlink", + 1 /* dir ID */, + f1name, + 1001 /* object ID of the file diffing from */, + blockindex, + IOStream::TimeOutInfinite, + NULL, // DiffTimer interface + 0, + &completelyDifferent)); + encoded->CopyStreamTo(out); + } + TEST_THAT(completelyDifferent == true); + check_encoded_file("testfiles/f2.symlink.diff", 0, 0, 0); + } +#endif + + // Check that diffing against a file which isn't "complete" and + // references another isn't allowed + { + FileStream blockindex("testfiles/f1.diff"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("filename"); + FileStream out("testfiles/f2.testincomplete", O_WRONLY | O_CREAT | O_EXCL); + TEST_CHECK_THROWS(BackupStoreFile::EncodeFileDiff("testfiles/f2", 1 /* dir ID */, f1name, + 1001 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0), BackupStoreException, CannotDiffAnIncompleteStoreFile); + } + + // Found a nasty case where files of lots of the same thing + // suck up lots of processor time -- because of lots of matches + // found. Check this out! + + #ifdef WIN32 + BOX_WARNING("Testing diffing two large streams, may take a while!"); + ::fflush(stderr); + #endif + + if (!make_file_of_zeros("testfiles/zero.0", 20*1024*1024)) + { + return 1; + } + + if (!make_file_of_zeros("testfiles/zero.1", 200*1024*1024)) + { + remove("testfiles/zero.0"); + return 1; + } + + // Generate a first encoded file + { + BackupStoreFilenameClear f0name("zero.0"); + FileStream out("testfiles/zero.0.enc", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/zero.0", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + } + // Then diff from it -- time how long it takes... + { + int beginTime = time(0); + FileStream blockindex("testfiles/zero.0.enc"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("zero.1"); + FileStream out("testfiles/zero.1.enc", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("testfiles/zero.1", 1 /* dir ID */, f1name, + 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0)); + encoded->CopyStreamTo(out); + + printf("Time taken: %d seconds\n", (int)(time(0) - beginTime)); + + #ifdef WIN32 + TEST_THAT(time(0) < (beginTime + 300)); + #else + TEST_THAT(time(0) < (beginTime + 40)); + #endif + } + // Remove zero-files to save disk space + remove("testfiles/zero.0"); + remove("testfiles/zero.1"); + +#if 0 + // Code for a nasty real world example! (16Mb files, won't include them in the distribution + // for obvious reasons...) + // Generate a first encoded file + { + BackupStoreFilenameClear f0name("0000000000000000.old"); + FileStream out("testfiles/0000000000000000.enc.0", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("/Users/ben/Desktop/0000000000000000.old", 1 /* dir ID */, f0name)); + encoded->CopyStreamTo(out); + } + // Then diff from it -- time how long it takes... + { + int beginTime = time(0); + FileStream blockindex("testfiles/0000000000000000.enc.0"); + BackupStoreFile::MoveStreamPositionToBlockIndex(blockindex); + + BackupStoreFilenameClear f1name("0000000000000000.new"); + FileStream out("testfiles/0000000000000000.enc.1", O_WRONLY | O_CREAT | O_EXCL); + std::auto_ptr encoded(BackupStoreFile::EncodeFileDiff("/Users/ben/Desktop/0000000000000000.new", 1 /* dir ID */, f1name, + 2000 /* object ID of the file diffing from */, blockindex, IOStream::TimeOutInfinite, + 0, 0)); + encoded->CopyStreamTo(out); + TEST_THAT(time(0) < (beginTime + 20)); + } +#endif // 0 + + return 0; +} + + diff --git a/test/backupdiff/testextra b/test/backupdiff/testextra new file mode 100644 index 00000000..165cacb9 --- /dev/null +++ b/test/backupdiff/testextra @@ -0,0 +1,2 @@ +rm -rf testfiles +mkdir testfiles diff --git a/test/backupstore/testbackupstore.cpp b/test/backupstore/testbackupstore.cpp new file mode 100644 index 00000000..6441d66c --- /dev/null +++ b/test/backupstore/testbackupstore.cpp @@ -0,0 +1,3305 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstore.cpp +// Purpose: Test backup store server +// Created: 2003/08/20 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Archive.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientFileAttributes.h" +#include "BackupProtocol.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreConfigVerify.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFilenameClear.h" +#include "BackupStoreFileEncodeStream.h" +#include "BackupStoreInfo.h" +#include "BackupStoreObjectMagic.h" +#include "BackupStoreRefCountDatabase.h" +#include "BoxPortsAndFiles.h" +#include "CollectInBufferStream.h" +#include "Configuration.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 "StoreStructure.h" +#include "StoreTestUtils.h" +#include "TLSContext.h" +#include "Test.h" +#include "ZeroStream.h" + +#include "MemLeakFindOn.h" + +#define ENCFILE_SIZE 2765 + +// Make some test attributes +#define ATTR1_SIZE 245 +#define ATTR2_SIZE 23 +#define ATTR3_SIZE 122 + +#define SHORT_TIMEOUT 5000 + +int attr1[ATTR1_SIZE]; +int attr2[ATTR2_SIZE]; +int attr3[ATTR3_SIZE]; + +typedef struct +{ + BackupStoreFilenameClear fn; + box_time_t mod; + int64_t id; + int64_t size; + int16_t flags; + box_time_t attrmod; +} dirtest; + +static dirtest ens[] = +{ + {BackupStoreFilenameClear(), 324324, 3432, 324, BackupStoreDirectory::Entry::Flags_File, 458763243422LL}, + {BackupStoreFilenameClear(), 3432, 32443245645LL, 78, BackupStoreDirectory::Entry::Flags_Dir, 3248972347LL}, + {BackupStoreFilenameClear(), 544435, 234234, 23324, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 2348974782LL}, + {BackupStoreFilenameClear(), 234, 235436, 6523, BackupStoreDirectory::Entry::Flags_File, 32458923175634LL}, + {BackupStoreFilenameClear(), 0x3242343532144LL, 8978979789LL, 21345, BackupStoreDirectory::Entry::Flags_File, 329483243432LL}, + {BackupStoreFilenameClear(), 324265765734LL, 12312312321LL, 324987324329874LL, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Deleted, 32489747234LL}, + {BackupStoreFilenameClear(), 3452134, 7868578768LL, 324243, BackupStoreDirectory::Entry::Flags_Dir, 34786457432LL}, + {BackupStoreFilenameClear(), 43543543, 324234, 21432, BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_Deleted, 3489723478327LL}, + {BackupStoreFilenameClear(), 325654765874324LL, 4353543, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 32489734789237LL}, + {BackupStoreFilenameClear(), 32144325, 436547657, 9, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 234897347234LL} +}; +static const char *ens_filenames[] = {"obj1ertewt", "obj2", "obj3", "obj4dfedfg43", "obj5", "obj6dfgs", "obj7", "obj8xcvbcx", "obj9", "obj10fgjhfg"}; +#define DIR_NUM 10 +#define DIR_DIRS 3 +#define DIR_FILES 7 +#define DIR_OLD 2 +#define DIR_DELETED 3 + +typedef struct +{ + const char *fnextra; + BackupStoreFilenameClear name; + int seed; + int size; + box_time_t mod_time; + int64_t allocated_objid; + bool should_be_old_version; + bool delete_file; +} uploadtest; + +#define TEST_FILE_FOR_PATCHING "testfiles/test2" +// a few bytes will be inserted at this point: +#define TEST_FILE_FOR_PATCHING_PATCH_AT ((64*1024)-128) +#define TEST_FILE_FOR_PATCHING_SIZE ((128*1024)+2564) +#define UPLOAD_PATCH_EN 2 + +uploadtest uploads[] = +{ + {"0", BackupStoreFilenameClear(), 324, 455, 0, 0, false, false}, + {"1", BackupStoreFilenameClear(), 3232432, 2674, 0, 0, true, false}, // old ver + {"2", BackupStoreFilenameClear(), 234, TEST_FILE_FOR_PATCHING_SIZE, 0, 0, false, false}, + {"3", BackupStoreFilenameClear(), 324324, 6763, 0, 0, false, false}, + {"4", BackupStoreFilenameClear(), 23456, 124, 0, 0, true, false}, // old ver + {"5", BackupStoreFilenameClear(), 675745, 1, 0, 0, false, false}, // will upload new attrs for this one! + {"6", BackupStoreFilenameClear(), 345213, 0, 0, 0, false, false}, + {"7", BackupStoreFilenameClear(), 12313, 3246, 0, 0, true, true}, // old ver, will get deleted + {"8", BackupStoreFilenameClear(), 457, 3434, 0, 0, false, false}, // overwrites + {"9", BackupStoreFilenameClear(), 12315, 446, 0, 0, false, false}, + {"a", BackupStoreFilenameClear(), 3476, 2466, 0, 0, false, false}, + {"b", BackupStoreFilenameClear(), 124334, 4562, 0, 0, false, false}, + {"c", BackupStoreFilenameClear(), 45778, 234, 0, 0, false, false}, // overwrites + {"d", BackupStoreFilenameClear(), 2423425, 435, 0, 0, false, true} // overwrites, will be deleted +}; +static const char *uploads_filenames[] = {"49587fds", "cvhjhj324", "sdfcscs324", "dsfdsvsdc3214", "XXsfdsdf2342", "dsfdsc232", + "sfdsdce2345", "YYstfbdtrdf76", "cvhjhj324", "fbfd098.ycy", "dfs98732hj", "svd987kjsad", "XXsfdsdf2342", "YYstfbdtrdf76"}; +#define UPLOAD_NUM 14 +#define UPLOAD_LATEST_FILES 12 +// file we'll upload some new attributes for +#define UPLOAD_ATTRS_EN 5 +#define UPLOAD_DELETE_EN 13 +// file which will be moved (as well as it's old version) +#define UPLOAD_FILE_TO_MOVE 8 + +#define UNLINK_IF_EXISTS(filename) \ + if (FileExists(filename)) { TEST_THAT(unlink(filename) == 0); } + +//! Simplifies calling setUp() with the current function name in each test. +#define SETUP_TEST_BACKUPSTORE() \ + SETUP(); \ + if (ServerIsAlive(bbstored_pid)) \ + TEST_THAT_OR(StopServer(), FAIL); \ + ExpectedRefCounts.resize(BACKUPSTORE_ROOT_DIRECTORY_ID + 1); \ + set_refcount(BACKUPSTORE_ROOT_DIRECTORY_ID, 1); \ + TEST_THAT_OR(create_account(10000, 20000), FAIL); + +//! Checks account for errors and shuts down daemons at end of every test. +bool teardown_test_backupstore() +{ + bool status = true; + + if (FileExists("testfiles/0_0/backup/01234567/info.rf")) + { + TEST_THAT_OR(check_reference_counts(), status = false); + TEST_THAT_OR(check_account(), status = false); + } + + return status; +} + +#define TEARDOWN_TEST_BACKUPSTORE() \ + if (ServerIsAlive(bbstored_pid)) \ + StopServer(); \ + TEST_THAT(teardown_test_backupstore()); \ + TEARDOWN(); + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + + +int SkipEntries(int e, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) +{ + if(e >= DIR_NUM) return e; + + bool skip = false; + do + { + skip = false; + + if(FlagsMustBeSet != BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING) + { + if((ens[e].flags & FlagsMustBeSet) != FlagsMustBeSet) + { + skip = true; + } + } + if((ens[e].flags & FlagsNotToBeSet) != 0) + { + skip = true; + } + + if(skip) + { + ++e; + } + } while(skip && e < DIR_NUM); + + return e; +} + +void CheckEntries(BackupStoreDirectory &rDir, int16_t FlagsMustBeSet, int16_t FlagsNotToBeSet) +{ + int e = 0; + + BackupStoreDirectory::Iterator i(rDir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + TEST_THAT(e < DIR_NUM); + + // Skip to entry in the ens array which matches + e = SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet); + + // Does it match? + TEST_THAT(en->GetName() == ens[e].fn && en->GetModificationTime() == ens[e].mod && en->GetObjectID() == ens[e].id && en->GetFlags() == ens[e].flags && en->GetSizeInBlocks() == ens[e].size); + + // next + ++e; + } + + // Got them all? + TEST_THAT(en == 0); + TEST_THAT(DIR_NUM == SkipEntries(e, FlagsMustBeSet, FlagsNotToBeSet)); +} + +bool test_filename_encoding() +{ + SETUP_TEST_BACKUPSTORE(); + + // test some basics -- encoding and decoding filenames + { + // Make some filenames in various ways + BackupStoreFilenameClear fn1; + fn1.SetClearFilename(std::string("filenameXYZ")); + BackupStoreFilenameClear fn2(std::string("filenameXYZ")); + BackupStoreFilenameClear fn3(fn1); + TEST_THAT(fn1 == fn2); + TEST_THAT(fn1 == fn3); + + // Check that it's been encrypted + std::string name(fn2.GetEncodedFilename()); + TEST_THAT(name.find("name") == name.npos); + + // Bung it in a stream, get it out in a Clear filename + { + CollectInBufferStream stream; + fn1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn4; + fn4.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn4.GetClearFilename() == "filenameXYZ"); + TEST_THAT(fn4 == fn1); + } + + // Bung it in a stream, get it out in a server non-Clear filename (two of them into the same var) + { + BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); + CollectInBufferStream stream; + fn1.WriteToStream(stream); + fno.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilename fn5; + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5 == fn1); + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5 == fno); + } + + // Same again with clear strings + { + BackupStoreFilenameClear fno("pinglet dksfnsf jksjdf "); + CollectInBufferStream stream; + fn1.WriteToStream(stream); + fno.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn5; + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5.GetClearFilename() == "filenameXYZ"); + fn5.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn5.GetClearFilename() == "pinglet dksfnsf jksjdf "); + } + + // Test a very big filename + { + const char *fnr = "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789" + "01234567890123456789012345678901234567890123456789"; + BackupStoreFilenameClear fnLong(fnr); + CollectInBufferStream stream; + fnLong.WriteToStream(stream); + stream.SetForReading(); + BackupStoreFilenameClear fn9; + fn9.ReadFromStream(stream, IOStream::TimeOutInfinite); + TEST_THAT(fn9.GetClearFilename() == fnr); + TEST_THAT(fn9 == fnLong); + } + + // Test a filename which went wrong once + { + BackupStoreFilenameClear dodgy("content-negotiation.html"); + } + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_backupstore_directory() +{ + SETUP_TEST_BACKUPSTORE(); + + { + // Now play with directories + + // Fill in... + BackupStoreDirectory dir1(12, 98); + for(int e = 0; e < DIR_NUM; ++e) + { + dir1.AddEntry(ens[e].fn, ens[e].mod, ens[e].id, ens[e].size, ens[e].flags, ens[e].attrmod); + } + // Got the right number + TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM); + + // Stick it into a stream and get it out again + { + CollectInBufferStream stream; + dir1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreDirectory dir2(stream); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_NUM); + TEST_THAT(dir2.GetObjectID() == 12); + TEST_THAT(dir2.GetContainerID() == 98); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + + // Then do selective writes and reads + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2(stream); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_INCLUDE_EVERYTHING, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2(stream); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_DIRS); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_Dir, BackupStoreDirectory::Entry::Flags_EXCLUDE_NOTHING); + } + { + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); + stream.SetForReading(); + BackupStoreDirectory dir2(stream); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - DIR_OLD); + CheckEntries(dir2, BackupStoreDirectory::Entry::Flags_File, BackupStoreDirectory::Entry::Flags_OldVersion); + } + + // Finally test deleting items + { + dir1.DeleteEntry(12312312321LL); + // Verify + TEST_THAT(dir1.GetNumberOfEntries() == DIR_NUM - 1); + CollectInBufferStream stream; + dir1.WriteToStream(stream, BackupStoreDirectory::Entry::Flags_File); + stream.SetForReading(); + BackupStoreDirectory dir2(stream); + TEST_THAT(dir2.GetNumberOfEntries() == DIR_FILES - 1); + } + + // Check attributes + { + int attrI[4] = {1, 2, 3, 4}; + StreamableMemBlock attr(attrI, sizeof(attrI)); + BackupStoreDirectory d1(16, 546); + d1.SetAttributes(attr, 56234987324232LL); + TEST_THAT(d1.GetAttributes() == attr); + TEST_THAT(d1.GetAttributesModTime() == 56234987324232LL); + CollectInBufferStream stream; + d1.WriteToStream(stream); + stream.SetForReading(); + BackupStoreDirectory d2(stream); + TEST_THAT(d2.GetAttributes() == attr); + TEST_THAT(d2.GetAttributesModTime() == 56234987324232LL); + } + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +void write_test_file(int t) +{ + std::string filename("testfiles/test"); + filename += uploads[t].fnextra; + BOX_TRACE("Writing " << filename); + + FileStream write(filename.c_str(), O_WRONLY | O_CREAT); + + R250 r(uploads[t].seed); + + unsigned char *data = (unsigned char*)malloc(uploads[t].size); + for(int l = 0; l < uploads[t].size; ++l) + { + data[l] = r.next() & 0xff; + } + write.Write(data, uploads[t].size); + + free(data); +} + +void test_test_file(int t, IOStream &rStream) +{ + // Decode to a file + BackupStoreFile::DecodeFile(rStream, "testfiles/test_download", SHORT_TIMEOUT); + + // Compare... + FileStream in("testfiles/test_download"); + TEST_THAT(in.BytesLeftToRead() == uploads[t].size); + + R250 r(uploads[t].seed); + + unsigned char *data = (unsigned char*)malloc(uploads[t].size); + TEST_THAT(in.ReadFullBuffer(data, uploads[t].size, 0 /* not interested in bytes read if this fails */)); + + for(int l = 0; l < uploads[t].size; ++l) + { + TEST_THAT(data[l] == (r.next() & 0xff)); + } + + free(data); + in.Close(); + TEST_THAT(unlink("testfiles/test_download") == 0); +} + +void assert_everything_deleted(BackupProtocolCallable &protocol, int64_t DirID) +{ + BOX_TRACE("Test for del: " << BOX_FORMAT_OBJECTID(DirID)); + + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + DirID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir(protocol.ReceiveStream(), SHORT_TIMEOUT); + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + int files = 0; + int dirs = 0; + while((en = i.Next()) != 0) + { + if(en->GetFlags() & BackupProtocolListDirectory::Flags_Dir) + { + dirs++; + // Recurse + assert_everything_deleted(protocol, en->GetObjectID()); + } + else + { + files++; + } + + // Check it's deleted + TEST_THAT(en->IsDeleted()); + } + + // Check there were the right number of files and directories + TEST_THAT(files == 3); + TEST_THAT(dirs == 0 || dirs == 2); +} + +void create_file_in_dir(std::string name, std::string source, int64_t parentId, + BackupProtocolCallable &protocol, BackupStoreRefCountDatabase* pRefCount) +{ + BackupStoreFilenameClear name_encoded("file_One"); + std::auto_ptr upload(BackupStoreFile::EncodeFile( + source.c_str(), parentId, name_encoded)); + std::auto_ptr stored( + protocol.QueryStoreFile( + parentId, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + name_encoded, + upload)); + int64_t objectId = stored->GetObjectID(); + if (pRefCount) + { + TEST_EQUAL(objectId, pRefCount->GetLastObjectIDUsed()); + TEST_EQUAL(1, pRefCount->GetRefCount(objectId)) + } + set_refcount(objectId, 1); +} + +const box_time_t FAKE_MODIFICATION_TIME = 0xfeedfacedeadbeefLL; +const box_time_t FAKE_ATTR_MODIFICATION_TIME = 0xdeadbeefcafebabeLL; + +int64_t create_test_data_subdirs(BackupProtocolCallable &protocol, + int64_t indir, const char *name, int depth, + BackupStoreRefCountDatabase* pRefCount) +{ + // Create a directory + int64_t subdirid = 0; + BackupStoreFilenameClear dirname(name); + { + // Create with dummy attributes + int attrS = 0; + std::auto_ptr attr(new MemBlockStream(&attrS, sizeof(attrS))); + std::auto_ptr dirCreate(protocol.QueryCreateDirectory( + indir, FAKE_ATTR_MODIFICATION_TIME, dirname, attr)); + subdirid = dirCreate->GetObjectID(); + } + + BOX_TRACE("Creating subdirs, depth = " << depth << ", dirid = " << + BOX_FORMAT_OBJECTID(subdirid)); + + if (pRefCount) + { + TEST_EQUAL(subdirid, pRefCount->GetLastObjectIDUsed()); + TEST_EQUAL(1, pRefCount->GetRefCount(subdirid)) + } + + set_refcount(subdirid, 1); + + // Put more directories in it, if we haven't gone down too far + if(depth > 0) + { + create_test_data_subdirs(protocol, subdirid, "dir_One", + depth - 1, pRefCount); + create_test_data_subdirs(protocol, subdirid, "dir_Two", + depth - 1, pRefCount); + } + + // Stick some files in it + create_file_in_dir("file_One", "testfiles/test1", subdirid, protocol, + pRefCount); + create_file_in_dir("file_Two", "testfiles/test1", subdirid, protocol, + pRefCount); + create_file_in_dir("file_Three", "testfiles/test1", subdirid, protocol, + pRefCount); + return subdirid; +} + +void check_dir_after_uploads(BackupProtocolCallable &protocol, + const StreamableMemBlock &Attributes) +{ + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + TEST_THAT(dirreply->GetObjectID() == BACKUPSTORE_ROOT_DIRECTORY_ID); + // Stream + BackupStoreDirectory dir(protocol.ReceiveStream(), SHORT_TIMEOUT); + TEST_EQUAL(UPLOAD_NUM, dir.GetNumberOfEntries()); + TEST_THAT(!dir.HasAttributes()); + + // Check them! + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en; + + for(int t = 0; t < UPLOAD_NUM; ++t) + { + en = i.Next(); + TEST_THAT(en != 0); + if (en == 0) continue; + TEST_LINE(uploads[t].name == en->GetName(), + "uploaded file " << t << " name"); + BackupStoreFilenameClear clear(en->GetName()); + TEST_EQUAL_LINE(uploads[t].name.GetClearFilename(), + clear.GetClearFilename(), + "uploaded file " << t << " name"); + TEST_EQUAL_LINE(uploads[t].allocated_objid, en->GetObjectID(), + "uploaded file " << t << " ID"); + TEST_EQUAL_LINE(uploads[t].mod_time, en->GetModificationTime(), + "uploaded file " << t << " modtime"); + 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; + TEST_EQUAL_LINE(correct_flags, en->GetFlags(), + "uploaded file " << t << " flags"); + if(t == UPLOAD_ATTRS_EN) + { + TEST_THAT(en->HasAttributes()); + TEST_THAT(en->GetAttributesHash() == 32498749832475LL); + TEST_THAT(en->GetAttributes() == Attributes); + } + else + { + // No attributes on this one + TEST_THAT(!en->HasAttributes()); + } + } + en = i.Next(); + TEST_THAT(en == 0); +} + +typedef struct +{ + int objectsNotDel; + int deleted; + int old; +} recursive_count_objects_results; + +void recursive_count_objects_r(BackupProtocolCallable &protocol, int64_t id, + recursive_count_objects_results &results) +{ + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + id, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir(protocol.ReceiveStream(), SHORT_TIMEOUT); + + // Check them! + BackupStoreDirectory::Iterator i(dir); + // Discard first + BackupStoreDirectory::Entry *en = 0; + + while((en = i.Next()) != 0) + { + if((en->GetFlags() & (BackupStoreDirectory::Entry::Flags_Deleted | BackupStoreDirectory::Entry::Flags_OldVersion)) == 0) results.objectsNotDel++; + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Deleted) results.deleted++; + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_OldVersion) results.old++; + + if(en->GetFlags() & BackupStoreDirectory::Entry::Flags_Dir) + { + recursive_count_objects_r(protocol, en->GetObjectID(), results); + } + } +} + +TLSContext context; + +void recursive_count_objects(int64_t id, recursive_count_objects_results &results) +{ + // Get a connection + BackupProtocolLocal2 protocolReadOnly(0x01234567, "test", + "backup/01234567/", 0, false); + + // Count objects + recursive_count_objects_r(protocolReadOnly, id, results); + + // Close it + protocolReadOnly.QueryFinished(); +} + +bool check_block_index(const char *encoded_file, IOStream &rBlockIndex) +{ + // Open file, and move to the right position + FileStream enc(encoded_file); + BackupStoreFile::MoveStreamPositionToBlockIndex(enc); + + bool same = true; + + // Now compare the two... + while(enc.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = enc.Read(buffer1, sizeof(buffer1), SHORT_TIMEOUT); + if(rBlockIndex.Read(buffer2, s, SHORT_TIMEOUT) != s) + { + same = false; + break; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + same = false; + break; + } + } + + if(rBlockIndex.StreamDataLeft()) + { + same = false; + + // Absorb all this excess data so procotol is in the first state + char buffer[2048]; + while(rBlockIndex.StreamDataLeft()) + { + rBlockIndex.Read(buffer, sizeof(buffer), SHORT_TIMEOUT); + } + } + + return same; +} + +bool check_files_same(const char *f1, const char *f2) +{ + // Open file, and move to the right position + FileStream f1s(f1); + FileStream f2s(f2); + + bool same = true; + + // Now compare the two... + while(f1s.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1s.Read(buffer1, sizeof(buffer1)); + if(f2s.Read(buffer2, s) != s) + { + same = false; + break; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + same = false; + break; + } + } + + if(f2s.StreamDataLeft()) + { + same = false; + } + + return same; +} + +std::auto_ptr get_raid_file(int64_t ObjectID) +{ + std::string filename; + StoreStructure::MakeObjectFilename(ObjectID, + "backup/01234567/" /* mStoreRoot */, 0 /* mStoreDiscSet */, + filename, false /* EnsureDirectoryExists */); + return RaidFileRead::Open(0, filename); +} + +int64_t create_directory(BackupProtocolCallable& protocol, + int64_t parent_dir_id = BACKUPSTORE_ROOT_DIRECTORY_ID); +int64_t create_file(BackupProtocolCallable& protocol, int64_t subdirid, + const std::string& remote_filename = ""); + +bool run_housekeeping_and_check_account(BackupProtocolLocal2& protocol) +{ + protocol.QueryFinished(); + bool check_account_status; + TEST_THAT(check_account_status = run_housekeeping_and_check_account()); + bool check_refcount_status; + TEST_THAT(check_refcount_status = check_reference_counts()); + protocol.Reopen(); + return check_account_status & check_refcount_status; +} + +bool test_temporary_refcount_db_is_independent() +{ + SETUP_TEST_BACKUPSTORE(); + + std::auto_ptr apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + std::auto_ptr temp( + BackupStoreRefCountDatabase::Create( + apAccounts->GetEntry(0x1234567))); + std::auto_ptr perm( + BackupStoreRefCountDatabase::Load( + apAccounts->GetEntry(0x1234567), + true // ReadOnly + )); + + TEST_CHECK_THROWS(temp->GetRefCount(2), + BackupStoreException, UnknownObjectRefCountRequested); + TEST_CHECK_THROWS(perm->GetRefCount(2), + BackupStoreException, UnknownObjectRefCountRequested); + temp->AddReference(2); + TEST_EQUAL(1, temp->GetRefCount(2)); + TEST_CHECK_THROWS(perm->GetRefCount(2), + BackupStoreException, UnknownObjectRefCountRequested); + temp->Discard(); + + // Need to delete perm object so it doesn't keep a filehandle open, + // preventing tearDown from rewriting the refcount DB and thus causing + // test failure. + perm.reset(); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_server_housekeeping() +{ + SETUP_TEST_BACKUPSTORE(); + + int encfile[ENCFILE_SIZE]; + { + for(int l = 0; l < ENCFILE_SIZE; ++l) + { + encfile[l] = l * 173; + } + + // Write this to a file + { + FileStream f("testfiles/file1", O_WRONLY | O_CREAT); + f.Write(encfile, sizeof(encfile)); + } + } + + // We need complete control over housekeeping, so use a local client + // instead of a network client + daemon. + + BackupProtocolLocal2 protocol(0x01234567, "test", "backup/01234567/", + 0, false); + + int root_dir_blocks = get_raid_file(BACKUPSTORE_ROOT_DIRECTORY_ID)->GetDiscUsageInBlocks(); + TEST_THAT(check_num_files(0, 0, 0, 1)); + TEST_THAT(check_num_blocks(protocol, 0, 0, 0, root_dir_blocks, + root_dir_blocks)); + + // Store a file -- first make the encoded file + BackupStoreFilenameClear store1name("file1"); + { + FileStream out("testfiles/file1_upload1", O_WRONLY | O_CREAT); + std::auto_ptr encoded( + BackupStoreFile::EncodeFile("testfiles/file1", + BACKUPSTORE_ROOT_DIRECTORY_ID, store1name)); + encoded->CopyStreamTo(out); + } + + // TODO FIXME move most of the code immediately below into + // test_server_commands. + + // TODO FIXME use COMMAND macro for all commands to check the returned + // object ID. + #define COMMAND(command, objectid) \ + TEST_EQUAL(objectid, protocol.command->GetObjectID()); + + // Then send it + int64_t store1objid; + { + std::auto_ptr upload(new FileStream("testfiles/file1_upload1")); + std::auto_ptr stored(protocol.QueryStoreFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + store1name, + upload)); + store1objid = stored->GetObjectID(); + TEST_EQUAL_LINE(2, store1objid, "wrong ObjectID for newly " + "uploaded file"); + } + + // Update expected reference count of this new object + set_refcount(store1objid, 1); + + // And retrieve it + { + // Retrieve as object + COMMAND(QueryGetObject(store1objid), store1objid); + + // BLOCK + { + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Need to put it in another stream, because it's not in stream order + CollectInBufferStream f; + filestream->CopyStreamTo(f); + f.SetForReading(); + // Get and decode + UNLINK_IF_EXISTS("testfiles/file1_upload_retrieved"); + BackupStoreFile::DecodeFile(f, "testfiles/file1_upload_retrieved", IOStream::TimeOutInfinite); + } + + // Retrieve as file + COMMAND(QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, store1objid), store1objid); + + // BLOCK + { + UNLINK_IF_EXISTS("testfiles/file1_upload_retrieved_str"); + + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Get and decode + BackupStoreFile::DecodeFile(*filestream, "testfiles/file1_upload_retrieved_str", IOStream::TimeOutInfinite); + } + + // Read in rebuilt original, and compare contents + { + FileStream in("testfiles/file1_upload_retrieved"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + + { + FileStream in("testfiles/file1_upload_retrieved_str"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + + // Retrieve the block index, by ID + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(store1objid)); + TEST_THAT(getblockindex->GetObjectID() == store1objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + // Check against uploaded file + TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); + } + + // and again, by name + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByName(BACKUPSTORE_ROOT_DIRECTORY_ID, store1name)); + TEST_THAT(getblockindex->GetObjectID() == store1objid); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + // Check against uploaded file + TEST_THAT(check_block_index("testfiles/file1_upload1", *blockIndexStream)); + } + } + + // Get the directory again, and see if the entry is in it + { + // Command + std::auto_ptr dirreply(protocol.QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir(protocol.ReceiveStream(), SHORT_TIMEOUT); + TEST_THAT(dir.GetNumberOfEntries() == 1); + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + TEST_THAT(i.Next() == 0); + if(en != 0) + { + TEST_THAT(en->GetName() == store1name); + TEST_THAT(en->GetModificationTime() == 0x123456789abcdefLL); + TEST_THAT(en->GetAttributesHash() == 0x7362383249872dfLL); + TEST_THAT(en->GetObjectID() == store1objid); + TEST_EQUAL(6, en->GetSizeInBlocks()); + TEST_THAT(en->GetFlags() == BackupStoreDirectory::Entry::Flags_File); + } + } + + int file1_blocks = get_raid_file(store1objid)->GetDiscUsageInBlocks(); + TEST_THAT(check_num_files(1, 0, 0, 1)); + TEST_THAT(check_num_blocks(protocol, file1_blocks, 0, 0, root_dir_blocks, + file1_blocks + root_dir_blocks)); + + // Upload again, as a patch to the original file. + int64_t patch1_id = BackupStoreFile::QueryStoreFileDiff(protocol, + "testfiles/file1", // LocalFilename + BACKUPSTORE_ROOT_DIRECTORY_ID, // DirectoryObjectID + store1objid, // DiffFromFileID + 0x7362383249872dfLL, // AttributesHash + store1name // StoreFilename + ); + TEST_EQUAL_LINE(3, patch1_id, "wrong ObjectID for newly uploaded " + "patch file"); + + // We need to check the old file's size, because it's been replaced + // by a reverse diff, and patch1_id is a complete file, not a diff. + int patch1_blocks = get_raid_file(store1objid)->GetDiscUsageInBlocks(); + + // It will take extra blocks, even though there are no changes, because + // the server code is not smart enough to realise that the file + // contents are identical, so it will create an empty patch. + + TEST_THAT(check_num_files(1, 1, 0, 1)); + TEST_THAT(check_num_blocks(protocol, file1_blocks, patch1_blocks, 0, + root_dir_blocks, file1_blocks + patch1_blocks + root_dir_blocks)); + + // Change the file and upload again, as a patch to the original file. + { + FileStream out("testfiles/file1", O_WRONLY | O_APPEND); + std::string appendix = "appendix!"; + out.Write(appendix.c_str(), appendix.size()); + out.Close(); + } + + int64_t patch2_id = BackupStoreFile::QueryStoreFileDiff(protocol, + "testfiles/file1", // LocalFilename + BACKUPSTORE_ROOT_DIRECTORY_ID, // DirectoryObjectID + patch1_id, // DiffFromFileID + 0x7362383249872dfLL, // AttributesHash + store1name // StoreFilename + ); + TEST_EQUAL_LINE(4, patch2_id, "wrong ObjectID for newly uploaded " + "patch file"); + + // How many blocks used by the new file? + // We need to check the old file's size, because it's been replaced + // by a reverse diff, and patch1_id is a complete file, not a diff. + int patch2_blocks = get_raid_file(patch1_id)->GetDiscUsageInBlocks(); + + TEST_THAT(check_num_files(1, 2, 0, 1)); + TEST_THAT(check_num_blocks(protocol, file1_blocks, patch1_blocks + patch2_blocks, 0, + root_dir_blocks, file1_blocks + patch1_blocks + patch2_blocks + + root_dir_blocks)); + + // Housekeeping should not change anything just yet + protocol.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + + TEST_THAT(check_num_files(1, 2, 0, 1)); + TEST_THAT(check_num_blocks(protocol, file1_blocks, patch1_blocks + patch2_blocks, 0, + root_dir_blocks, file1_blocks + patch1_blocks + patch2_blocks + + root_dir_blocks)); + + // Upload not as a patch, but as a completely different file. This + // marks the previous file as old (because the filename is the same) + // but used to not adjust the number of old/deleted files properly. + int64_t replaced_id = BackupStoreFile::QueryStoreFileDiff(protocol, + "testfiles/file1", // LocalFilename + BACKUPSTORE_ROOT_DIRECTORY_ID, // DirectoryObjectID + 0, // DiffFromFileID + 0x7362383249872dfLL, // AttributesHash + store1name // StoreFilename + ); + TEST_EQUAL_LINE(5, replaced_id, "wrong ObjectID for newly uploaded " + "full file"); + + // How many blocks used by the new file? This time we need to check + // the new file, because it's not a patch. + int replaced_blocks = get_raid_file(replaced_id)->GetDiscUsageInBlocks(); + + TEST_THAT(check_num_files(1, 3, 0, 1)); + TEST_THAT(check_num_blocks(protocol, replaced_blocks, // current + file1_blocks + patch1_blocks + patch2_blocks, // old + 0, // deleted + root_dir_blocks, // directories + file1_blocks + patch1_blocks + patch2_blocks + replaced_blocks + + root_dir_blocks)); // total + + // Housekeeping should not change anything just yet + protocol.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + + TEST_THAT(check_num_files(1, 3, 0, 1)); + TEST_THAT(check_num_blocks(protocol, replaced_blocks, // current + file1_blocks + patch1_blocks + patch2_blocks, // old + 0, // deleted + root_dir_blocks, // directories + file1_blocks + patch1_blocks + patch2_blocks + replaced_blocks + + root_dir_blocks)); // total + + // But if we reduce the limits, then it will + protocol.QueryFinished(); + TEST_THAT(change_account_limits( + "14B", // replaced_blocks + file1_blocks + root_dir_blocks + "2000B")); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + + TEST_THAT(check_num_files(1, 1, 0, 1)); + TEST_THAT(check_num_blocks(protocol, replaced_blocks, // current + file1_blocks, // old + 0, // deleted + root_dir_blocks, // directories + file1_blocks + replaced_blocks + root_dir_blocks)); // total + + // Check that deleting files is accounted for as well + protocol.QueryDeleteFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, // InDirectory + store1name); // Filename + + // The old version file is deleted as well! + TEST_THAT(check_num_files(0, 1, 2, 1)); + TEST_THAT(check_num_blocks(protocol, 0, // current + file1_blocks, // old + replaced_blocks + file1_blocks, // deleted + root_dir_blocks, // directories + file1_blocks + replaced_blocks + root_dir_blocks)); + + // Reduce limits again, check that removed files are subtracted from + // block counts. + protocol.QueryFinished(); + TEST_THAT(change_account_limits("0B", "2000B")); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + + TEST_THAT(check_num_files(0, 0, 0, 1)); + TEST_THAT(check_num_blocks(protocol, 0, 0, 0, root_dir_blocks, root_dir_blocks)); + + // Used to not consume the stream + std::auto_ptr upload(new ZeroStream(1000)); + TEST_COMMAND_RETURNS_ERROR(protocol, QueryStoreFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + 0, + 0, /* use for attr hash too */ + 99999, /* diff from ID */ + uploads[0].name, + upload), + Err_DiffFromFileDoesNotExist); + + // TODO FIXME These tests should not be here, but in + // test_server_commands. But make sure you use a network protocol, + // not a local one, when you move them. + + // Try using GetFile on a directory + { + int64_t subdirid = create_directory(protocol); + TEST_COMMAND_RETURNS_ERROR(protocol, + QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, subdirid), + Err_FileDoesNotVerify); + } + + // Try retrieving an object that doesn't exist. That used to return + // BackupProtocolSuccess(NoObject) for no apparent reason. + TEST_COMMAND_RETURNS_ERROR(protocol, QueryGetObject(store1objid + 1), + Err_DoesNotExist); + + // Close the protocol, so we can housekeep the account + protocol.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + + ExpectedRefCounts.resize(3); // stop test failure in teardown_test_backupstore() + TEARDOWN_TEST_BACKUPSTORE(); +} + +int64_t create_directory(BackupProtocolCallable& protocol, int64_t parent_dir_id) +{ + // Create a directory + BackupStoreFilenameClear dirname("lovely_directory"); + // Attributes + std::auto_ptr attr(new MemBlockStream(attr1, sizeof(attr1))); + + std::auto_ptr dirCreate( + protocol.QueryCreateDirectory2( + parent_dir_id, FAKE_ATTR_MODIFICATION_TIME, + FAKE_MODIFICATION_TIME, dirname, attr)); + + int64_t subdirid = dirCreate->GetObjectID(); + set_refcount(subdirid, 1); + return subdirid; +} + +int64_t create_file(BackupProtocolCallable& protocol, int64_t subdirid, + const std::string& remote_filename) +{ + // Stick a file in it + write_test_file(0); + + BackupStoreFilenameClear remote_filename_encoded; + if (remote_filename.empty()) + { + remote_filename_encoded = uploads[0].name; + } + else + { + remote_filename_encoded = remote_filename; + } + + std::string filename("testfiles/test0"); + int64_t modtime; + std::auto_ptr upload(BackupStoreFile::EncodeFile(filename, + subdirid, remote_filename_encoded, &modtime)); + + std::auto_ptr stored(protocol.QueryStoreFile( + subdirid, + modtime, + modtime, /* use for attr hash too */ + 0, /* diff from ID */ + remote_filename_encoded, + upload)); + + int64_t subdirfileid = stored->GetObjectID(); + set_refcount(subdirfileid, 1); + return subdirfileid; +} + +bool assert_writable_connection_fails(BackupProtocolCallable& protocol) +{ + std::auto_ptr serverVersion + (protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + TEST_COMMAND_RETURNS_ERROR_OR(protocol, QueryLogin(0x01234567, 0), + Err_CannotLockStoreForWriting, return false); + protocol.QueryFinished(); + return true; +} + +int64_t assert_readonly_connection_succeeds(BackupProtocolCallable& protocol) +{ + // TODO FIXME share code with test_server_login + std::auto_ptr serverVersion + (protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + std::auto_ptr loginConf + (protocol.QueryLogin(0x01234567, BackupProtocolLogin::Flags_ReadOnly)); + return loginConf->GetClientStoreMarker(); +} + +bool test_multiple_uploads() +{ + SETUP_TEST_BACKUPSTORE(); + TEST_THAT_OR(StartServer(), FAIL); + + std::auto_ptr apProtocol = + connect_and_login(context); + + // TODO FIXME replace protocolReadOnly with apProtocolReadOnly. + BackupProtocolLocal2 protocolReadOnly(0x01234567, "test", + "backup/01234567/", 0, true); // ReadOnly + + // Read the root directory a few times (as it's cached, so make sure it doesn't hurt anything) + for(int l = 0; l < 3; ++l) + { + // Command + apProtocol->QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */); + // Stream + BackupStoreDirectory dir(apProtocol->ReceiveStream(), + apProtocol->GetTimeout()); + TEST_THAT(dir.GetNumberOfEntries() == 0); + } + + // Read the dir from the readonly connection (make sure it gets in the cache) + // Command + protocolReadOnly.QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes */); + // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + protocolReadOnly.GetTimeout()); + TEST_THAT(dir.GetNumberOfEntries() == 0); + + // TODO FIXME dedent + { + TEST_THAT(check_num_files(0, 0, 0, 1)); + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // Create and upload some test files + int64_t maxID = 0; + for(int t = 0; t < UPLOAD_NUM; ++t) + { + write_test_file(t); + + std::string filename("testfiles/test"); + filename += uploads[t].fnextra; + int64_t modtime = 0; + + std::auto_ptr upload(BackupStoreFile::EncodeFile(filename.c_str(), BACKUPSTORE_ROOT_DIRECTORY_ID, uploads[t].name, &modtime)); + TEST_THAT(modtime != 0); + + std::auto_ptr stored(apProtocol->QueryStoreFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + modtime, + modtime, /* use it for attr hash too */ + 0, /* diff from ID */ + uploads[t].name, + upload)); + uploads[t].allocated_objid = stored->GetObjectID(); + uploads[t].mod_time = modtime; + if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); + set_refcount(stored->GetObjectID(), 1); + BOX_TRACE("wrote file " << filename << " to server " + "as object " << + BOX_FORMAT_OBJECTID(stored->GetObjectID())); + + // Some of the uploaded files replace old ones, increasing + // the old file count instead of the current file count. + int expected_num_old_files = 0; + if (t >= 8) expected_num_old_files++; + if (t >= 12) expected_num_old_files++; + if (t >= 13) expected_num_old_files++; + int expected_num_current_files = t + 1 - expected_num_old_files; + + TEST_THAT(check_num_files(expected_num_current_files, + expected_num_old_files, 0, 1)); + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + apProtocol = connect_and_login(context); + protocolReadOnly.Reopen(); + + TEST_THAT(check_num_files(expected_num_current_files, + expected_num_old_files, 0, 1)); + } + + // Add some attributes onto one of them + { + TEST_THAT(check_num_files(UPLOAD_NUM - 3, 3, 0, 1)); + std::auto_ptr attrnew( + new MemBlockStream(attr3, sizeof(attr3))); + std::auto_ptr set(apProtocol->QuerySetReplacementFileAttributes( + BACKUPSTORE_ROOT_DIRECTORY_ID, + 32498749832475LL, + uploads[UPLOAD_ATTRS_EN].name, + attrnew)); + TEST_THAT(set->GetObjectID() == uploads[UPLOAD_ATTRS_EN].allocated_objid); + TEST_THAT(check_num_files(UPLOAD_NUM - 3, 3, 0, 1)); + } + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + apProtocol = connect_and_login(context); + protocolReadOnly.Reopen(); + + // Delete one of them (will implicitly delete an old version) + { + std::auto_ptr del(apProtocol->QueryDeleteFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + uploads[UPLOAD_DELETE_EN].name)); + TEST_THAT(del->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); + TEST_THAT(check_num_files(UPLOAD_NUM - 4, 3, 2, 1)); + } + +#ifdef _MSC_VER + BOX_TRACE("1"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + apProtocol = connect_and_login(context); + protocolReadOnly.Reopen(); + +#ifdef _MSC_VER + BOX_TRACE("2"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + + // 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); + std::auto_ptr getobj(apProtocol->QueryGetObject(uploads[UPLOAD_DELETE_EN].allocated_objid)); + std::auto_ptr objstream(apProtocol->ReceiveStream()); + objstream->CopyStreamTo(out, apProtocol->GetTimeout()); + } + // query index and test + std::auto_ptr getblockindex(apProtocol->QueryGetBlockIndexByName( + BACKUPSTORE_ROOT_DIRECTORY_ID, uploads[UPLOAD_DELETE_EN].name)); + TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_DELETE_EN].allocated_objid); + std::auto_ptr blockIndexStream(apProtocol->ReceiveStream()); + TEST_THAT(check_block_index("testfiles/downloaddelobj", *blockIndexStream)); + } + + // Download them all... (even deleted files) + for(int t = 0; t < UPLOAD_NUM; ++t) + { + printf("%d\n", t); + std::auto_ptr getFile(apProtocol->QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, uploads[t].allocated_objid)); + TEST_THAT(getFile->GetObjectID() == uploads[t].allocated_objid); + std::auto_ptr filestream(apProtocol->ReceiveStream()); + test_test_file(t, *filestream); + } + +#ifdef _MSC_VER + BOX_TRACE("3"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + + { + StreamableMemBlock attrtest(attr3, sizeof(attr3)); + + // Use the read only connection to verify that the directory is as we expect + printf("\n\n==== Reading directory using read-only connection\n"); + check_dir_after_uploads(protocolReadOnly, attrtest); + printf("done.\n\n"); + // And on the read/write one + check_dir_after_uploads(*apProtocol, attrtest); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + +#ifdef _MSC_VER + BOX_TRACE("4"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + + // Check diffing and rsync like stuff... + // Build a modified file + { + // Basically just insert a bit in the middle + TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING) == TEST_FILE_FOR_PATCHING_SIZE); + FileStream in(TEST_FILE_FOR_PATCHING); + void *buf = ::malloc(TEST_FILE_FOR_PATCHING_SIZE); + FileStream out(TEST_FILE_FOR_PATCHING ".mod", O_WRONLY | O_CREAT); + TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_PATCH_AT); + out.Write(buf, TEST_FILE_FOR_PATCHING_PATCH_AT); + char insert[13] = "INSERTINSERT"; + out.Write(insert, sizeof(insert)); + TEST_THAT(in.Read(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT) == TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); + out.Write(buf, TEST_FILE_FOR_PATCHING_SIZE - TEST_FILE_FOR_PATCHING_PATCH_AT); + ::free(buf); + } + + TEST_THAT(check_num_files(UPLOAD_NUM - 4, 3, 2, 1)); + + // Run housekeeping (for which we need to disconnect + // ourselves) and check that it doesn't change the numbers + // of files + +#ifdef _MSC_VER + BOX_TRACE("5"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + + std::auto_ptr apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccountDatabase::Entry account = + apAccounts->GetEntry(0x1234567); +#ifdef _MSC_VER + BOX_TRACE("6"); + system("dir testfiles\\0_0\\backup\\01234567"); +#endif + TEST_EQUAL(0, run_housekeeping(account)); + + // Also check that bbstoreaccounts doesn't change anything, + // using an external process instead of the internal one. + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf check 01234567 fix") == 0, + FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + apProtocol = connect_and_login(context); + protocolReadOnly.Reopen(); + + TEST_THAT(check_num_files(UPLOAD_NUM - 4, 3, 2, 1)); + + { + // Fetch the block index for this one + std::auto_ptr getblockindex(apProtocol->QueryGetBlockIndexByName( + BACKUPSTORE_ROOT_DIRECTORY_ID, uploads[UPLOAD_PATCH_EN].name)); + TEST_THAT(getblockindex->GetObjectID() == uploads[UPLOAD_PATCH_EN].allocated_objid); + std::auto_ptr blockIndexStream(apProtocol->ReceiveStream()); + + // Do the patching + bool isCompletelyDifferent = false; + int64_t modtime; + std::auto_ptr patchstream( + BackupStoreFile::EncodeFileDiff( + TEST_FILE_FOR_PATCHING ".mod", + BACKUPSTORE_ROOT_DIRECTORY_ID, + uploads[UPLOAD_PATCH_EN].name, + uploads[UPLOAD_PATCH_EN].allocated_objid, + *blockIndexStream, + SHORT_TIMEOUT, + NULL, // pointer to DiffTimer impl + &modtime, &isCompletelyDifferent)); + TEST_THAT(isCompletelyDifferent == false); + // Sent this to a file, so we can check the size, rather than uploading it directly + { + FileStream patch(TEST_FILE_FOR_PATCHING ".patch", O_WRONLY | O_CREAT); + patchstream->CopyStreamTo(patch); + } + // Make sure the stream is a plausible size for a patch containing only one new block + TEST_THAT(TestGetFileSize(TEST_FILE_FOR_PATCHING ".patch") < (8*1024)); + // Upload it + int64_t patchedID = 0; + { + std::auto_ptr uploadpatch(new FileStream(TEST_FILE_FOR_PATCHING ".patch")); + std::auto_ptr stored(apProtocol->QueryStoreFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + modtime, + modtime, /* use it for attr hash too */ + uploads[UPLOAD_PATCH_EN].allocated_objid, /* diff from ID */ + uploads[UPLOAD_PATCH_EN].name, + uploadpatch)); + TEST_THAT(stored->GetObjectID() > 0); + if(maxID < stored->GetObjectID()) maxID = stored->GetObjectID(); + patchedID = stored->GetObjectID(); + } + + set_refcount(patchedID, 1); + + // Then download it to check it's OK + std::auto_ptr getFile(apProtocol->QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, patchedID)); + TEST_THAT(getFile->GetObjectID() == patchedID); + std::auto_ptr filestream(apProtocol->ReceiveStream()); + BackupStoreFile::DecodeFile(*filestream, + TEST_FILE_FOR_PATCHING ".downloaded", SHORT_TIMEOUT); + // Check it's the same + TEST_THAT(check_files_same(TEST_FILE_FOR_PATCHING ".downloaded", TEST_FILE_FOR_PATCHING ".mod")); + TEST_THAT(check_num_files(UPLOAD_NUM - 4, 4, 2, 1)); + } + } + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_server_commands() +{ + SETUP_TEST_BACKUPSTORE(); + + std::auto_ptr apProtocol( + new BackupProtocolLocal2(0x01234567, "test", + "backup/01234567/", 0, false)); + + // Try using GetFile on an object ID that doesn't exist in the directory + { + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, + BACKUPSTORE_ROOT_DIRECTORY_ID), + Err_DoesNotExistInDirectory); + } + + // BLOCK + // TODO FIXME dedent this block. + { + // Create a directory + int64_t subdirid = create_directory(*apProtocol); + TEST_THAT(check_num_files(0, 0, 0, 2)); + + // Try using GetFile on the directory + { + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryGetFile(BACKUPSTORE_ROOT_DIRECTORY_ID, + subdirid), + Err_FileDoesNotVerify); + } + + // Stick a file in it + int64_t subdirfileid = create_file(*apProtocol, subdirid); + TEST_THAT(check_num_files(1, 0, 0, 2)); + + BackupProtocolLocal2 protocolReadOnly(0x01234567, "test", + "backup/01234567/", 0, true); // read-only + + BOX_TRACE("Checking root directory using read-only connection"); + { + // Command + protocolReadOnly.QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes! */); // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + + // UPLOAD_NUM test files, patch uploaded and new dir + TEST_EQUAL(1, dir.GetNumberOfEntries()); + + // Check the last one... + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + BackupStoreDirectory::Entry *t = 0; + BackupStoreFilenameClear dirname("lovely_directory"); + + while((t = i.Next()) != 0) + { + if(en != 0) + { + // here for all but last object + TEST_THAT(en->GetObjectID() != subdirid); + TEST_THAT(en->GetName() != dirname); + } + en = t; + } + + // Check that the last entry looks right + TEST_EQUAL(subdirid, en->GetObjectID()); + TEST_THAT(en->GetName() == dirname); + TEST_EQUAL(BackupProtocolListDirectory::Flags_Dir, en->GetFlags()); + int64_t actual_size = get_raid_file(subdirid)->GetDiscUsageInBlocks(); + TEST_EQUAL(actual_size, en->GetSizeInBlocks()); + TEST_EQUAL(FAKE_MODIFICATION_TIME, en->GetModificationTime()); + } + + BOX_TRACE("Checking subdirectory using read-only connection"); + { + // Command + TEST_EQUAL(subdirid, + protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + true /* get attributes */)->GetObjectID()); + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + TEST_THAT(dir.GetNumberOfEntries() == 1); + + // Check the (only) one... + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + + // Check that it looks right + TEST_EQUAL(subdirfileid, en->GetObjectID()); + TEST_THAT(en->GetName() == uploads[0].name); + TEST_EQUAL(BackupProtocolListDirectory::Flags_File, en->GetFlags()); + int64_t actual_size = get_raid_file(subdirfileid)->GetDiscUsageInBlocks(); + TEST_EQUAL(actual_size, en->GetSizeInBlocks()); + TEST_THAT(en->GetModificationTime() != 0); + + // Attributes + TEST_THAT(dir.HasAttributes()); + TEST_EQUAL(FAKE_ATTR_MODIFICATION_TIME, + dir.GetAttributesModTime()); + StreamableMemBlock attr(attr1, sizeof(attr1)); + TEST_THAT(dir.GetAttributes() == attr); + } + + BOX_TRACE("Checking that we don't get attributes if we don't ask for them"); + { + // Command + protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes! */); + // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + TEST_THAT(!dir.HasAttributes()); + } + + // Sleep to ensure that the timestamp on the file will change, + // invalidating the read-only connection's cache of the + // directory, and forcing it to be reloaded. + ::safe_sleep(1); + + // Change attributes on the directory + { + std::auto_ptr attrnew( + new MemBlockStream(attr2, sizeof(attr2))); + std::auto_ptr changereply(apProtocol->QueryChangeDirAttributes( + subdirid, + 329483209443598LL, + attrnew)); + TEST_THAT(changereply->GetObjectID() == subdirid); + } + + // Check the new attributes + { + // Command + protocolReadOnly.QueryListDirectory( + subdirid, + 0, // no flags + BackupProtocolListDirectory::Flags_EXCLUDE_EVERYTHING, + true /* get attributes */); + // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + TEST_THAT(dir.GetNumberOfEntries() == 0); + + // Attributes + TEST_THAT(dir.HasAttributes()); + TEST_EQUAL(329483209443598LL, dir.GetAttributesModTime()); + StreamableMemBlock attrtest(attr2, sizeof(attr2)); + TEST_THAT(dir.GetAttributes() == attrtest); + } + + BackupStoreFilenameClear& oldName(uploads[0].name); + int64_t root_file_id = create_file(*apProtocol, BACKUPSTORE_ROOT_DIRECTORY_ID); + TEST_THAT(check_num_files(2, 0, 0, 2)); + + // Upload a new version of the file as well, to ensure that the + // old version is moved along with the current version. + root_file_id = BackupStoreFile::QueryStoreFileDiff(*apProtocol, + "testfiles/test0", BACKUPSTORE_ROOT_DIRECTORY_ID, + 0, // DiffFromFileID + 0, // AttributesHash + oldName); + set_refcount(root_file_id, 1); + TEST_THAT(check_num_files(2, 1, 0, 2)); + + // Check that it's in the root directory (it won't be for long) + protocolReadOnly.QueryListDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, + 0, 0, false); + TEST_THAT(BackupStoreDirectory(protocolReadOnly.ReceiveStream()) + .FindEntryByID(root_file_id) != NULL); + + BackupStoreFilenameClear newName("moved-files"); + + // Sleep before modifying the root directory, to ensure that + // the timestamp on the file it's stored in will change when + // we modify it, invalidating the read-only connection's cache + // and forcing it to reload the root directory, next time we + // ask for its contents. + ::safe_sleep(1); + + // Test moving a file + { + std::auto_ptr rep(apProtocol->QueryMoveObject(root_file_id, + BACKUPSTORE_ROOT_DIRECTORY_ID, + subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, newName)); + TEST_EQUAL(root_file_id, rep->GetObjectID()); + } + + // Try some dodgy renames + { + // File doesn't exist at all + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryMoveObject(-1, + BACKUPSTORE_ROOT_DIRECTORY_ID, subdirid, + BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName), + Err_DoesNotExistInDirectory); + BackupStoreFilenameClear newName("moved-files"); + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryMoveObject( + uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + BackupProtocolListDirectory::RootDirectory, + subdirid, + BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName), + Err_DoesNotExistInDirectory); + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryMoveObject( + uploads[UPLOAD_FILE_TO_MOVE].allocated_objid, + subdirid, + subdirid, + BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName), + Err_DoesNotExistInDirectory); + } + + // File exists, but not in this directory (we just moved it) + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryMoveObject(root_file_id, + BACKUPSTORE_ROOT_DIRECTORY_ID, + subdirid, + BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName), + Err_DoesNotExistInDirectory); + + // Moving file to same directory that it's already in, + // with the same name + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryMoveObject(root_file_id, + subdirid, + subdirid, + BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName), + Err_TargetNameExists); + + // Rename within a directory (successfully) + { + BackupStoreFilenameClear newName2("moved-files-x"); + apProtocol->QueryMoveObject(root_file_id, subdirid, + subdirid, BackupProtocolMoveObject::Flags_MoveAllWithSameName, + newName2); + } + + // Check it's all gone from the root directory... + protocolReadOnly.QueryListDirectory(BACKUPSTORE_ROOT_DIRECTORY_ID, + 0, 0, false); + TEST_THAT(BackupStoreDirectory(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT).FindEntryByID(root_file_id) == NULL); + + // Check the old and new versions are in the other directory + { + BackupStoreFilenameClear notThere("moved-files"); + BackupStoreFilenameClear lookFor("moved-files-x"); + + // Command + protocolReadOnly.QueryListDirectory( + subdirid, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes */); + + // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + + // Check entries + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + bool foundCurrent = false; + bool foundOld = false; + while((en = i.Next()) != 0) + { + // If we find the old name, then the rename + // operation didn't work. + TEST_THAT(en->GetName() != notThere); + + if(en->GetName() == lookFor) + { + if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File)) foundCurrent = true; + if(en->GetFlags() == (BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion)) foundOld = true; + } + } + TEST_THAT(foundCurrent); + TEST_THAT(foundOld); + } + + // sleep to ensure that the timestamp on the file will change + ::safe_sleep(1); + + // make a little bit more of a thing to look at + int64_t subsubdirid = 0; + int64_t subsubfileid = 0; + { + // TODO FIXME use create_dir() and create_file() instead. + BackupStoreFilenameClear nd("sub2"); + // Attributes + std::auto_ptr attr(new MemBlockStream(attr1, + sizeof(attr1))); + subsubdirid = apProtocol->QueryCreateDirectory(subdirid, + FAKE_ATTR_MODIFICATION_TIME, nd, attr)->GetObjectID(); + + write_test_file(2); + + BackupStoreFilenameClear file2("file2"); + std::auto_ptr upload( + BackupStoreFile::EncodeFile("testfiles/test2", + BACKUPSTORE_ROOT_DIRECTORY_ID, file2)); + std::auto_ptr stored(apProtocol->QueryStoreFile( + subsubdirid, + 0x123456789abcdefLL, /* modification time */ + 0x7362383249872dfLL, /* attr hash */ + 0, /* diff from ID */ + file2, + upload)); + subsubfileid = stored->GetObjectID(); + } + + set_refcount(subsubdirid, 1); + set_refcount(subsubfileid, 1); + TEST_THAT(check_num_files(3, 1, 0, 3)); + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + apProtocol->Reopen(); + protocolReadOnly.Reopen(); + + // Query names -- test that invalid stuff returns not found OK + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(3248972347823478927LL, subsubdirid)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(subsubfileid, 2342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(38947234789LL, 2342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(BackupProtocolGetObjectName::ObjectID_DirectoryOnly, 2234342378424LL)); + TEST_THAT(nameRep->GetNumNameElements() == 0); + } + + // Query names... first, get info for the file + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(subsubfileid, subsubdirid)); + std::auto_ptr namestream(apProtocol->ReceiveStream()); + + TEST_THAT(nameRep->GetNumNameElements() == 3); + TEST_THAT(nameRep->GetFlags() == BackupProtocolListDirectory::Flags_File); + TEST_THAT(nameRep->GetModificationTime() == 0x123456789abcdefLL); + TEST_THAT(nameRep->GetAttributesHash() == 0x7362383249872dfLL); + static const char *testnames[] = {"file2","sub2","lovely_directory"}; + for(int l = 0; l < nameRep->GetNumNameElements(); ++l) + { + BackupStoreFilenameClear fn; + fn.ReadFromStream(*namestream, 10000); + TEST_THAT(fn.GetClearFilename() == testnames[l]); + } + } + + // Query names... secondly, for the directory + { + std::auto_ptr nameRep(apProtocol->QueryGetObjectName(BackupProtocolGetObjectName::ObjectID_DirectoryOnly, subsubdirid)); + std::auto_ptr namestream(apProtocol->ReceiveStream()); + + TEST_THAT(nameRep->GetNumNameElements() == 2); + TEST_THAT(nameRep->GetFlags() == BackupProtocolListDirectory::Flags_Dir); + static const char *testnames[] = {"sub2","lovely_directory"}; + for(int l = 0; l < nameRep->GetNumNameElements(); ++l) + { + BackupStoreFilenameClear fn; + fn.ReadFromStream(*namestream, 10000); + TEST_THAT(fn.GetClearFilename() == testnames[l]); + } + } + +//} skip: + + // Create some nice recursive directories + TEST_THAT(check_reference_counts()); + + write_test_file(1); + int64_t dirtodelete; + + { + std::auto_ptr apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + std::auto_ptr apRefCount( + BackupStoreRefCountDatabase::Load( + apAccounts->GetEntry(0x1234567), true)); + + + dirtodelete = create_test_data_subdirs(*apProtocol, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "test_delete", 6 /* depth */, apRefCount.get()); + } + + TEST_THAT(check_reference_counts()); + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + TEST_THAT(check_reference_counts()); + + // And delete them + apProtocol->Reopen(); + protocolReadOnly.Reopen(); + + { + std::auto_ptr dirdel(apProtocol->QueryDeleteDirectory( + dirtodelete)); + TEST_THAT(dirdel->GetObjectID() == dirtodelete); + } + + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(run_housekeeping_and_check_account()); + TEST_THAT(check_reference_counts()); + protocolReadOnly.Reopen(); + + // Get the root dir, checking for deleted items + { + // Command + protocolReadOnly.QueryListDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupProtocolListDirectory::Flags_Dir | + BackupProtocolListDirectory::Flags_Deleted, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes */); + // Stream + BackupStoreDirectory dir(protocolReadOnly.ReceiveStream(), + SHORT_TIMEOUT); + + // Check there's only that one entry + TEST_THAT(dir.GetNumberOfEntries() == 1); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = i.Next(); + TEST_THAT(en != 0); + if(en) + { + TEST_EQUAL(dirtodelete, en->GetObjectID()); + BackupStoreFilenameClear n("test_delete"); + TEST_THAT(en->GetName() == n); + } + + // Then... check everything's deleted + assert_everything_deleted(protocolReadOnly, dirtodelete); + } + + // Undelete and check that block counts are restored properly + apProtocol->Reopen(); + TEST_EQUAL(dirtodelete, + apProtocol->QueryUndeleteDirectory(dirtodelete)->GetObjectID()); + + // Finish the connections + apProtocol->QueryFinished(); + protocolReadOnly.QueryFinished(); + + TEST_THAT(run_housekeeping_and_check_account()); + TEST_THAT(check_reference_counts()); + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +int get_object_size(BackupProtocolCallable& protocol, int64_t ObjectID, + int64_t ContainerID) +{ + // Get the root directory cached in the read-only connection + protocol.QueryListDirectory(ContainerID, 0, // FlagsMustBeSet + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + false /* no attributes */); + + BackupStoreDirectory dir(protocol.ReceiveStream()); + BackupStoreDirectory::Entry *en = dir.FindEntryByID(ObjectID); + TEST_THAT_OR(en != 0, return -1); + TEST_EQUAL_OR(ObjectID, en->GetObjectID(), return -1); + return en->GetSizeInBlocks(); +} + +bool write_dir(BackupStoreDirectory& dir) +{ + std::string rfn; + StoreStructure::MakeObjectFilename(dir.GetObjectID(), + "backup/01234567/" /* mStoreRoot */, 0 /* mStoreDiscSet */, + rfn, false); // EnsureDirectoryExists + RaidFileWrite rfw(0, rfn); + rfw.Open(true); // AllowOverwrite + dir.WriteToStream(rfw); + rfw.Commit(/* ConvertToRaidNow */ true); + return true; +} + +bool test_directory_parent_entry_tracks_directory_size() +{ + SETUP_TEST_BACKUPSTORE(); + + BackupProtocolLocal2 protocol(0x01234567, "test", "backup/01234567/", + 0, false); + BackupProtocolLocal2 protocolReadOnly(0x01234567, "test", + "backup/01234567/", 0, true); // read only + + int64_t subdirid = create_directory(protocol); + + // Get the root directory cached in the read-only connection, and + // test that the initial size is correct. + int old_size = get_raid_file(subdirid)->GetDiscUsageInBlocks(); + TEST_THAT(old_size > 0); + TEST_EQUAL(old_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Sleep to ensure that the directory file timestamp changes, so that + // the read-only connection will discard its cached copy. + safe_sleep(1); + + // Start adding files until the size on disk increases. This is + // guaranteed to happen eventually :) + int new_size = old_size; + int64_t last_added_file_id = 0; + std::string last_added_filename; + + for (int i = 0; new_size == old_size; i++) + { + std::ostringstream name; + name << "testfile_" << i; + last_added_filename = name.str(); + last_added_file_id = create_file(protocol, subdirid, name.str()); + new_size = get_raid_file(subdirid)->GetDiscUsageInBlocks(); + } + + // Check that the root directory entry has been updated + TEST_EQUAL(new_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Now delete an entry, and check that the size is reduced + protocol.QueryDeleteFile(subdirid, + BackupStoreFilenameClear(last_added_filename)); + ExpectedRefCounts[last_added_file_id] = 0; + + // Reduce the limits, to remove it permanently from the store + protocol.QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(change_account_limits("0B", "20000B")); + TEST_THAT(run_housekeeping_and_check_account()); + set_refcount(last_added_file_id, 0); + protocol.Reopen(); + protocolReadOnly.Reopen(); + + TEST_EQUAL(old_size, get_raid_file(subdirid)->GetDiscUsageInBlocks()); + + // Check that the entry in the root directory was updated too + TEST_EQUAL(old_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Push the limits back up + protocol.QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(change_account_limits("1000B", "20000B")); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + protocolReadOnly.Reopen(); + + // Now modify the root directory to remove its entry for this one + BackupStoreDirectory root(*get_raid_file(BACKUPSTORE_ROOT_DIRECTORY_ID), + IOStream::TimeOutInfinite); + BackupStoreDirectory::Entry *en = root.FindEntryByID(subdirid); + TEST_THAT_OR(en, return false); + BackupStoreDirectory::Entry enCopy(*en); + root.DeleteEntry(subdirid); + TEST_THAT(write_dir(root)); + + // Add a directory, this should try to push the object size back up, + // which will try to modify the subdir's entry in its parent, which + // no longer exists, which should just log an error instead of + // aborting/segfaulting. + create_directory(protocol, subdirid); + + // Repair the error ourselves, as bbstoreaccounts can't. + protocol.QueryFinished(); + enCopy.SetSizeInBlocks(get_raid_file(subdirid)->GetDiscUsageInBlocks()); + root.AddEntry(enCopy); + TEST_THAT(write_dir(root)); + + // We also have to remove the entry for lovely_directory created by + // create_directory(), because otherwise we can't create it again. + // (Perhaps it should not have been committed because we failed to + // update the parent, but currently it is.) + BackupStoreDirectory subdir(*get_raid_file(subdirid), + IOStream::TimeOutInfinite); + { + BackupStoreDirectory::Iterator i(subdir); + en = i.FindMatchingClearName( + BackupStoreFilenameClear("lovely_directory")); + } + TEST_THAT_OR(en, return false); + protocol.Reopen(); + protocol.QueryDeleteDirectory(en->GetObjectID()); + set_refcount(en->GetObjectID(), 0); + + // This should have fixed the error, so we should be able to add the + // entry now. This should push the object size back up. + int64_t dir2id = create_directory(protocol, subdirid); + TEST_EQUAL(new_size, get_raid_file(subdirid)->GetDiscUsageInBlocks()); + TEST_EQUAL(new_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Delete it again, which should reduce the object size again + protocol.QueryDeleteDirectory(dir2id); + set_refcount(dir2id, 0); + + // Reduce the limits, to remove it permanently from the store + protocol.QueryFinished(); + protocolReadOnly.QueryFinished(); + TEST_THAT(change_account_limits("0B", "20000B")); + TEST_THAT(run_housekeeping_and_check_account()); + protocol.Reopen(); + protocolReadOnly.Reopen(); + + // Check that the entry in the root directory was updated + TEST_EQUAL(old_size, get_raid_file(subdirid)->GetDiscUsageInBlocks()); + TEST_EQUAL(old_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Check that bbstoreaccounts check fix will detect and repair when + // a directory's parent entry has the wrong size for the directory. + + protocol.QueryFinished(); + + root.ReadFromStream(*get_raid_file(BACKUPSTORE_ROOT_DIRECTORY_ID), + IOStream::TimeOutInfinite); + en = root.FindEntryByID(subdirid); + TEST_THAT_OR(en != 0, return false); + en->SetSizeInBlocks(1234); + + // Sleep to ensure that the directory file timestamp changes, so that + // the read-only connection will discard its cached copy. + safe_sleep(1); + TEST_THAT(write_dir(root)); + + TEST_EQUAL(1234, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + + // Sleep to ensure that the directory file timestamp changes, so that + // the read-only connection will discard its cached copy. + safe_sleep(1); + + protocolReadOnly.QueryFinished(); + TEST_EQUAL(1, check_account_for_errors()); + + protocolReadOnly.Reopen(); + TEST_EQUAL(old_size, get_object_size(protocolReadOnly, subdirid, + BACKUPSTORE_ROOT_DIRECTORY_ID)); + protocolReadOnly.QueryFinished(); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_cannot_open_multiple_writable_connections() +{ + SETUP_TEST_BACKUPSTORE(); + + // First try a local protocol. This works even on Windows. + BackupProtocolLocal2 protocolWritable(0x01234567, "test", + "backup/01234567/", 0, false); // Not read-only + + // Set the client store marker + protocolWritable.QuerySetClientStoreMarker(0x8732523ab23aLL); + + // First try a local protocol. This works even on Windows. + { + BackupStoreContext bsContext(0x01234567, (HousekeepingInterface *)NULL, "test"); + bsContext.SetClientHasAccount("backup/01234567/", 0); + BackupProtocolLocal protocolWritable2(bsContext); + TEST_THAT(assert_writable_connection_fails(protocolWritable2)); + } + + { + BackupStoreContext bsContext(0x01234567, (HousekeepingInterface *)NULL, "test"); + bsContext.SetClientHasAccount("backup/01234567/", 0); + BackupProtocolLocal protocolReadOnly(bsContext); + TEST_EQUAL(0x8732523ab23aLL, + assert_readonly_connection_succeeds(protocolReadOnly)); + } + + // Try network connections too. + TEST_THAT_OR(StartServer(), return false); + + BackupProtocolClient protocolWritable3(open_conn("localhost", context)); + TEST_THAT(assert_writable_connection_fails(protocolWritable3)); + + // Do not dedent. Object needs to go out of scope to release lock + { + BackupProtocolClient protocolReadOnly2(open_conn("localhost", context)); + TEST_EQUAL(0x8732523ab23aLL, + assert_readonly_connection_succeeds(protocolReadOnly2)); + } + + protocolWritable.QueryFinished(); + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_encoding() +{ + // Now test encoded files + // TODO: This test needs to check failure situations as well as everything working, + // but this will be saved for the full implementation. + + SETUP_TEST_BACKUPSTORE(); + + int encfile[ENCFILE_SIZE]; + { + for(int l = 0; l < ENCFILE_SIZE; ++l) + { + encfile[l] = l * 173; + } + + // Encode and decode a small block (shouldn't be compressed) + { + #define SMALL_BLOCK_SIZE 251 + int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(SMALL_BLOCK_SIZE); + TEST_THAT(encBlockSize > SMALL_BLOCK_SIZE); + BackupStoreFile::EncodingBuffer encoded; + encoded.Allocate(encBlockSize / 8); // make sure reallocation happens + + // Encode! + int encSize = BackupStoreFile::EncodeChunk(encfile, SMALL_BLOCK_SIZE, encoded); + // Check the header says it's not been compressed + TEST_THAT((encoded.mpBuffer[0] & 1) == 0); + // Check the output size has been inflated (no compression) + TEST_THAT(encSize > SMALL_BLOCK_SIZE); + + // Decode it + int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(SMALL_BLOCK_SIZE); + TEST_THAT(decBlockSize > SMALL_BLOCK_SIZE); + uint8_t *decoded = (uint8_t*)malloc(decBlockSize); + int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); + TEST_THAT(decSize < decBlockSize); + TEST_THAT(decSize == SMALL_BLOCK_SIZE); + + // Check it came out of the wash the same + TEST_THAT(::memcmp(encfile, decoded, SMALL_BLOCK_SIZE) == 0); + + free(decoded); + } + + // Encode and decode a big block (should be compressed) + { + int encBlockSize = BackupStoreFile::MaxBlockSizeForChunkSize(ENCFILE_SIZE); + TEST_THAT(encBlockSize > ENCFILE_SIZE); + BackupStoreFile::EncodingBuffer encoded; + encoded.Allocate(encBlockSize / 8); // make sure reallocation happens + + // Encode! + int encSize = BackupStoreFile::EncodeChunk(encfile, ENCFILE_SIZE, encoded); + // Check the header says it's compressed + TEST_THAT((encoded.mpBuffer[0] & 1) == 1); + // Check the output size make it likely that it's compressed (is very compressible data) + TEST_THAT(encSize < ENCFILE_SIZE); + + // Decode it + int decBlockSize = BackupStoreFile::OutputBufferSizeForKnownOutputSize(ENCFILE_SIZE); + TEST_THAT(decBlockSize > ENCFILE_SIZE); + uint8_t *decoded = (uint8_t*)malloc(decBlockSize); + int decSize = BackupStoreFile::DecodeChunk(encoded.mpBuffer, encSize, decoded, decBlockSize); + TEST_THAT(decSize < decBlockSize); + TEST_THAT(decSize == ENCFILE_SIZE); + + // Check it came out of the wash the same + TEST_THAT(::memcmp(encfile, decoded, ENCFILE_SIZE) == 0); + + free(decoded); + } + + // The test block to a file + { + FileStream f("testfiles/testenc1", O_WRONLY | O_CREAT); + f.Write(encfile, sizeof(encfile)); + } + + // Encode it + { + FileStream out("testfiles/testenc1_enc", O_WRONLY | O_CREAT); + BackupStoreFilenameClear name("testfiles/testenc1"); + + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc1", 32, name)); + encoded->CopyStreamTo(out); + } + + // Verify it + { + FileStream enc("testfiles/testenc1_enc"); + TEST_THAT(BackupStoreFile::VerifyEncodedFileFormat(enc) == true); + + // And using the stream-based interface, writing different + // block sizes at a time. + CollectInBufferStream contents; + enc.Seek(0, IOStream::SeekType_Absolute); + enc.CopyStreamTo(contents); + contents.SetForReading(); + + enc.Seek(0, IOStream::SeekType_End); + size_t file_size = enc.GetPosition(); + TEST_EQUAL(file_size, contents.GetSize()); + + for(int buffer_size = 1; ; buffer_size <<= 1) + { + enc.Seek(0, IOStream::SeekType_Absolute); + CollectInBufferStream temp_copy; + BackupStoreFile::VerifyStream verifier(&temp_copy); + enc.CopyStreamTo(verifier, IOStream::TimeOutInfinite, + buffer_size); + + // The block index is only validated on Close(), which + // CopyStreamTo() doesn't do. + verifier.Close(); + + temp_copy.SetForReading(); + TEST_EQUAL(file_size, temp_copy.GetSize()); + TEST_THAT(memcmp(contents.GetBuffer(), + temp_copy.GetBuffer(), file_size) == 0); + + // Keep doubling buffer size until we've copied the + // entire encoded file in a single pass, then stop. + if(buffer_size > file_size) + { + break; + } + } + } + + // Decode it + { + UNLINK_IF_EXISTS("testfiles/testenc1_orig"); + FileStream enc("testfiles/testenc1_enc"); + BackupStoreFile::DecodeFile(enc, "testfiles/testenc1_orig", IOStream::TimeOutInfinite); + } + + // Read in rebuilt original, and compare contents + { + TEST_THAT(TestGetFileSize("testfiles/testenc1_orig") == sizeof(encfile)); + FileStream in("testfiles/testenc1_orig"); + int encfile_i[ENCFILE_SIZE]; + in.Read(encfile_i, sizeof(encfile_i)); + TEST_THAT(memcmp(encfile, encfile_i, sizeof(encfile)) == 0); + } + + // Check how many blocks it had, and test the stream based interface + { + FileStream enc("testfiles/testenc1_enc"); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(enc, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == sizeof(encfile)); + TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 3); + } + + // Test that the last block in a file, if less than 256 bytes, gets put into the last block + { + #define FILE_SIZE_JUST_OVER ((4096*2)+58) + FileStream f("testfiles/testenc2", O_WRONLY | O_CREAT); + f.Write(encfile + 2, FILE_SIZE_JUST_OVER); + BackupStoreFilenameClear name("testenc2"); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testenc2", 32, name)); + CollectInBufferStream e; + encoded->CopyStreamTo(e); + e.SetForReading(); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(e, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 879 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == FILE_SIZE_JUST_OVER); + TEST_THAT(memcmp(encfile + 2, d.GetBuffer(), FILE_SIZE_JUST_OVER) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 2); + } + + // Test that reordered streams work too + { + FileStream enc("testfiles/testenc1_enc"); + std::auto_ptr reordered(BackupStoreFile::ReorderFileToStreamOrder(&enc, false)); + std::auto_ptr decoded(BackupStoreFile::DecodeFileStream(*reordered, IOStream::TimeOutInfinite)); + CollectInBufferStream d; + decoded->CopyStreamTo(d, IOStream::TimeOutInfinite, 971 /* buffer block size */); + d.SetForReading(); + TEST_THAT(d.GetSize() == sizeof(encfile)); + TEST_THAT(memcmp(encfile, d.GetBuffer(), sizeof(encfile)) == 0); + + TEST_THAT(decoded->GetNumBlocks() == 3); + } + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_symlinks() +{ + SETUP_TEST_BACKUPSTORE(); + +#ifndef WIN32 // no symlinks on Win32 + UNLINK_IF_EXISTS("testfiles/testsymlink"); + TEST_THAT(::symlink("does/not/exist", "testfiles/testsymlink") == 0); + BackupStoreFilenameClear name("testsymlink"); + std::auto_ptr encoded(BackupStoreFile::EncodeFile("testfiles/testsymlink", 32, name)); + + // Can't decode it from the stream, because it's in file order, and doesn't have the + // required properties to be able to reorder it. So buffer it... + CollectInBufferStream b; + encoded->CopyStreamTo(b); + b.SetForReading(); + + // Decode it + UNLINK_IF_EXISTS("testfiles/testsymlink_2"); + BackupStoreFile::DecodeFile(b, "testfiles/testsymlink_2", IOStream::TimeOutInfinite); +#endif + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_store_info() +{ + SETUP_TEST_BACKUPSTORE(); + + { + RaidFileWrite::CreateDirectory(0, "test-info"); + BackupStoreInfo::CreateNew(76, "test-info/", 0, 3461231233455433LL, 2934852487LL); + TEST_CHECK_THROWS(BackupStoreInfo::CreateNew(76, "test-info/", 0, 0, 0), RaidFileException, CannotOverwriteExistingFile); + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); + TEST_CHECK_THROWS(info->Save(), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksUsed(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(1), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->RemovedDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->AddDeletedDirectory(2), BackupStoreException, StoreInfoIsReadOnly); + TEST_CHECK_THROWS(info->SetAccountName("hello"), BackupStoreException, StoreInfoIsReadOnly); + } + { + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, false)); + info->ChangeBlocksUsed(8); + info->ChangeBlocksInOldFiles(9); + info->ChangeBlocksInDeletedFiles(10); + info->ChangeBlocksUsed(-1); + info->ChangeBlocksInOldFiles(-4); + info->ChangeBlocksInDeletedFiles(-9); + TEST_CHECK_THROWS(info->ChangeBlocksUsed(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + TEST_CHECK_THROWS(info->ChangeBlocksInOldFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + TEST_CHECK_THROWS(info->ChangeBlocksInDeletedFiles(-100), BackupStoreException, StoreInfoBlockDeltaMakesValueNegative); + info->AddDeletedDirectory(2); + info->AddDeletedDirectory(3); + info->AddDeletedDirectory(4); + info->RemovedDeletedDirectory(3); + info->SetAccountName("whee"); + TEST_CHECK_THROWS(info->RemovedDeletedDirectory(9), BackupStoreException, StoreInfoDirNotInList); + info->Save(); + } + + { + std::auto_ptr info(BackupStoreInfo::Load(76, "test-info/", 0, true)); + TEST_THAT(info->GetBlocksUsed() == 7); + TEST_THAT(info->GetBlocksInOldFiles() == 5); + TEST_THAT(info->GetBlocksInDeletedFiles() == 1); + TEST_THAT(info->GetBlocksSoftLimit() == 3461231233455433LL); + TEST_THAT(info->GetBlocksHardLimit() == 2934852487LL); + TEST_THAT(info->GetAccountName() == "whee"); + const std::vector &delfiles(info->GetDeletedDirectories()); + TEST_THAT(delfiles.size() == 2); + TEST_THAT(delfiles[0] == 2); + TEST_THAT(delfiles[1] == 4); + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_login_without_account() +{ + // First, try logging in without an account having been created... just make sure login fails. + + SETUP_TEST_BACKUPSTORE(); + delete_account(); + TEST_THAT_OR(StartServer(), FAIL); + + // BLOCK + { + // Open a connection to the server + BackupProtocolClient protocol(open_conn("localhost", context)); + + // Check the version + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + TEST_COMMAND_RETURNS_ERROR(protocol, QueryLogin(0x01234567, 0), + Err_BadLogin); + + // Finish the connection + protocol.QueryFinished(); + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_bbstoreaccounts_create() +{ + SETUP_TEST_BACKUPSTORE(); + + // Delete the account, and create it again using bbstoreaccounts + delete_account(); + + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf -Wwarning create 01234567 0 " + "10000B 20000B") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_bbstoreaccounts_delete() +{ + SETUP_TEST_BACKUPSTORE(); + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf -Wwarning delete 01234567 yes") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Recreate the account so that teardown_test_backupstore() doesn't freak out + TEST_THAT(create_account(10000, 20000)); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +// Test that login fails on a disabled account +bool test_login_with_disabled_account() +{ + SETUP_TEST_BACKUPSTORE(); + TEST_THAT_OR(StartServer(), FAIL); + + 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 apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + std::auto_ptr 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_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf enabled 01234567 no") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // BLOCK + { + // Open a connection to the server + BackupProtocolClient protocol(open_conn("localhost", context)); + + // Check the version + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + TEST_COMMAND_RETURNS_ERROR(protocol, QueryLogin(0x01234567, 0), + Err_DisabledAccount); + + // Finish the connection + protocol.QueryFinished(); + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_login_with_no_refcount_db() +{ + SETUP_TEST_BACKUPSTORE(); + + // The account is already enabled, but doing it again shouldn't hurt + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf enabled 01234567 yes") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Delete the refcount database and try to log in again. Check that + // we're locked out of the account until housekeeping has recreated + // the refcount db. + TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.rdb.rfw")); + TEST_CHECK_THROWS(BackupProtocolLocal2 protocolLocal(0x01234567, + "test", "backup/01234567/", 0, false), // Not read-only + BackupStoreException, CorruptReferenceCountDatabase); + + // Run housekeeping, check that it fixes the refcount db + std::auto_ptr apAccounts( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccountDatabase::Entry account = + apAccounts->GetEntry(0x1234567); + TEST_EQUAL_LINE(1, run_housekeeping(account), + "Housekeeping should report 1 error if the refcount db is missing"); + TEST_THAT(FileExists("testfiles/0_0/backup/01234567/refcount.rdb.rfw")); + + // And that we can log in afterwards + BackupProtocolLocal2(0x01234567, "test", "backup/01234567/", 0, + false).QueryFinished(); // Not read-only + + // Check that housekeeping fixed the ref counts + TEST_THAT(check_reference_counts()); + + // Start a server and try again, remotely. This is difficult to debug + // because housekeeping may fix the refcount database while we're + // stepping through. + TEST_THAT_THROWONFAIL(StartServer()); + TEST_EQUAL(0, ::unlink("testfiles/0_0/backup/01234567/refcount.rdb.rfw")); + TEST_CHECK_THROWS(connect_and_login(context), + ConnectionException, Protocol_UnexpectedReply); + + TEST_THAT(ServerIsAlive(bbstored_pid)); + + // Run housekeeping, check that it fixes the refcount db + TEST_EQUAL_LINE(1, run_housekeeping(account), + "Housekeeping should report 1 error if the refcount db is missing"); + TEST_THAT(FileExists("testfiles/0_0/backup/01234567/refcount.rdb.rfw")); + TEST_THAT(check_reference_counts()); + + // And that we can log in afterwards + connect_and_login(context)->QueryFinished(); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_housekeeping_deletes_files() +{ + // Test the deletion of objects by the housekeeping system + + SETUP_TEST_BACKUPSTORE(); + + BackupProtocolLocal2 protocolLocal(0x01234567, "test", + "backup/01234567/", 0, false); // Not read-only + + // Create some nice recursive directories + write_test_file(1); + int64_t dirtodelete = create_test_data_subdirs(protocolLocal, + BACKUPSTORE_ROOT_DIRECTORY_ID, "test_delete", 6 /* depth */, + NULL /* pRefCount */); + + TEST_EQUAL(dirtodelete, + protocolLocal.QueryDeleteDirectory(dirtodelete)->GetObjectID()); + assert_everything_deleted(protocolLocal, dirtodelete); + protocolLocal.QueryFinished(); + + // First, things as they are now. + TEST_THAT_OR(StartServer(), FAIL); + recursive_count_objects_results before = {0,0,0}; + recursive_count_objects(BACKUPSTORE_ROOT_DIRECTORY_ID, before); + + TEST_EQUAL(0, before.objectsNotDel); + TEST_THAT(before.deleted != 0); + TEST_THAT(before.old != 0); + + // Kill it + TEST_THAT(StopServer()); + + // Reduce the store limits, so housekeeping will remove all old files. + // Leave the hard limit high, so we know that housekeeping's target + // for freeing space is the soft limit. + TEST_THAT(change_account_limits("0B", "20000B")); + TEST_THAT(run_housekeeping_and_check_account()); + + // Count the objects again + recursive_count_objects_results after = {0,0,0}; + recursive_count_objects(BACKUPSTORE_ROOT_DIRECTORY_ID, after); + TEST_EQUAL(before.objectsNotDel, after.objectsNotDel); + TEST_EQUAL(0, after.deleted); + TEST_EQUAL(0, after.old); + + // Adjust reference counts on deleted files, so that the final checks in + // teardown_test_backupstore() don't fail. + ExpectedRefCounts.resize(2); + + // Delete the account to stop teardown_test_backupstore from checking it. + // TODO FIXME investigate the block count mismatch that teardown_test_backupstore + // catches if we don't delete the account. + delete_account(); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +bool test_account_limits_respected() +{ + SETUP_TEST_BACKUPSTORE(); + TEST_THAT_OR(StartServer(), FAIL); + + // Set a really small hard limit + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf setlimit 01234567 " + "2B 2B") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Try to upload a file and create a directory, both of which would exceed the + // current account limits, and check that each command returns an error. + { + write_test_file(3); + + // Open a connection to the server + std::auto_ptr apProtocol( + connect_and_login(context)); + BackupStoreFilenameClear fnx("exceed-limit"); + int64_t modtime = 0; + std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/test3", BACKUPSTORE_ROOT_DIRECTORY_ID, fnx, &modtime)); + TEST_THAT(modtime != 0); + + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryStoreFile( + BACKUPSTORE_ROOT_DIRECTORY_ID, + modtime, + modtime, /* use it for attr hash too */ + 0, /* diff from ID */ + fnx, + upload), + Err_StorageLimitExceeded); + + // This currently causes a fatal error on the server, which + // kills the connection. TODO FIXME return an error instead. + std::auto_ptr attr(new MemBlockStream(&modtime, sizeof(modtime))); + BackupStoreFilenameClear fnxd("exceed-limit-dir"); + TEST_COMMAND_RETURNS_ERROR(*apProtocol, + QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, + FAKE_ATTR_MODIFICATION_TIME, fnxd, attr), + Err_StorageLimitExceeded); + + // Finish the connection. + apProtocol->QueryFinished(); + } + + TEARDOWN_TEST_BACKUPSTORE(); +} + +int multi_server() +{ + printf("Starting server for connection from remote machines...\n"); + + // Create an account for the test client + TEST_THAT_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf create 01234567 0 " + "30000B 40000B") == 0, FAIL); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // First, try logging in without an account having been created... just make sure login fails. + + int pid = LaunchServer(BBSTORED " testfiles/bbstored_multi.conf", + "testfiles/bbstored.pid"); + + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // Wait for a keypress + printf("Press ENTER to terminate the server\n"); + char line[512]; + fgets(line, 512, stdin); + printf("Terminating server...\n"); + + // Kill it + TEST_THAT(StopServer()); + } + + return 0; +} + +bool test_open_files_with_limited_win32_permissions() +{ +#ifdef WIN32 + // this had better work, or bbstored will die when combining diffs + const char* file = "foo"; + + 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; + + HANDLE h1 = CreateFileA(file, accessRights, shareMode, + NULL, OPEN_ALWAYS, // create file if it doesn't exist + FILE_FLAG_BACKUP_SEMANTICS, NULL); + TEST_THAT(h1 != INVALID_HANDLE_VALUE); + + accessRights = FILE_READ_ATTRIBUTES | + FILE_LIST_DIRECTORY | FILE_READ_EA; + + HANDLE h2 = CreateFileA(file, accessRights, shareMode, + NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + TEST_THAT(h2 != INVALID_HANDLE_VALUE); + + CloseHandle(h2); + CloseHandle(h1); + + h1 = openfile(file, O_CREAT | O_RDWR, 0); + TEST_THAT(h1 != INVALID_HANDLE_VALUE); + h2 = openfile(file, O_RDWR, 0); + TEST_THAT(h2 != INVALID_HANDLE_VALUE); + CloseHandle(h2); + CloseHandle(h1); +#endif + + return true; +} + +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"); +} + +bool test_read_old_backupstoreinfo_files() +{ + SETUP_TEST_BACKUPSTORE(); + + // Create an account for the test client + std::auto_ptr 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 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 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 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->GetNumCurrentFiles()); + 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_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf enabled 01234567 no") == 0, FAIL); + 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_OR(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf enabled 01234567 yes") == 0, FAIL); + 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 stop teardown_test_backupstore checking it for errors. + apInfo.reset(); + TEST_THAT(delete_account()); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +// Test that attributes can be correctly read from and written to the standard +// format, for compatibility with other servers and clients. See +// http://mailman.uk.freebsd.org/pipermail../public/boxbackup/2010-November/005818.html and +// http://lists.boxbackup.org/pipermail/boxbackup/2011-February/005978.html for +// details of the problems with packed structs. +bool test_read_write_attr_streamformat() +{ + SETUP_TEST_BACKUPSTORE(); + + // Construct a minimal valid directory with 1 entry in memory using Archive, and + // try to read it back. + CollectInBufferStream buf; + Archive darc(buf, IOStream::TimeOutInfinite); + + // Write a dir_StreamFormat structure + darc.Write((int32_t)OBJECTMAGIC_DIR_MAGIC_VALUE); // mMagicValue + darc.Write((int32_t)1); // mNumEntries + darc.Write((int64_t)0x0123456789abcdef); // mObjectID + darc.Write((int64_t)0x0000000000000001); // mContainerID + darc.Write((uint64_t)0x23456789abcdef01); // mAttributesModTime + darc.Write((int32_t)BackupStoreDirectory::Option_DependencyInfoPresent); + // mOptionsPresent + // 36 bytes to here + + // Write fake attributes to make it valid. + StreamableMemBlock attr; + attr.WriteToStream(buf); + // 40 bytes to here + + // Write a single entry in an en_StreamFormat structure + darc.Write((uint64_t)0x3456789012345678); // mModificationTime + darc.Write((int64_t)0x0000000000000002); // mObjectID + darc.Write((int64_t)0x0000000000000003); // mSizeInBlocks + darc.Write((uint64_t)0x0000000000000004); // mAttributesHash + darc.WriteInt16((int16_t)0x3141); // mFlags + // 74 bytes to here + + // Then a BackupStoreFilename + BackupStoreFilename fn; + fn.SetAsClearFilename("hello"); + fn.WriteToStream(buf); + // 81 bytes to here + + // Then a StreamableMemBlock for attributes + attr.WriteToStream(buf); + // 85 bytes to here + + // Then an en_StreamFormatDepends for dependency info. + darc.Write((uint64_t)0x0000000000000005); // mDependsNewer + darc.Write((uint64_t)0x0000000000000006); // mDependsOlder + // 101 bytes to here + + // Make sure none of the fields was expanded in transit by Archive. + TEST_EQUAL(101, buf.GetSize()); + + buf.SetForReading(); + BackupStoreDirectory dir(buf); + + TEST_EQUAL(1, dir.GetNumberOfEntries()); + TEST_EQUAL(0x0123456789abcdef, dir.GetObjectID()); + TEST_EQUAL(0x0000000000000001, dir.GetContainerID()); + TEST_EQUAL(0x23456789abcdef01, dir.GetAttributesModTime()); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry* pen = i.Next(); + TEST_THAT_OR(pen != NULL, FAIL); + TEST_EQUAL(0x3456789012345678, pen->GetModificationTime()); + TEST_EQUAL(0x0000000000000002, pen->GetObjectID()); + TEST_EQUAL(0x0000000000000003, pen->GetSizeInBlocks()); + TEST_EQUAL(0x0000000000000004, pen->GetAttributesHash()); + TEST_EQUAL(0x0000000000000005, pen->GetDependsNewer()); + TEST_EQUAL(0x0000000000000006, pen->GetDependsOlder()); + TEST_EQUAL(0x3141, pen->GetFlags()); + + CollectInBufferStream buf2; + dir.WriteToStream(buf2); + buf2.SetForReading(); + buf.Seek(0, IOStream::SeekType_Absolute); + TEST_EQUAL(101, buf2.GetSize()); + TEST_EQUAL(buf.GetSize(), buf2.GetSize()); + + // Test that the stream written out for the Directory is exactly the same as the + // one we hand-crafted earlier. + TEST_EQUAL(0, memcmp(buf.GetBuffer(), buf2.GetBuffer(), buf.GetSize())); + + TEARDOWN_TEST_BACKUPSTORE(); +} + +int test(int argc, const char *argv[]) +{ + TEST_THAT(test_open_files_with_limited_win32_permissions()); + + // Initialise the raid file controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + + TEST_THAT(test_read_old_backupstoreinfo_files()); + + // SSL library + SSLLib::Initialise(); + + // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used + // for seeing what's going on. + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // encode in some filenames -- can't do static initialisation + // because the key won't be set up when these are initialised + { + MEMLEAKFINDER_NO_LEAKS + + for(unsigned int l = 0; l < sizeof(ens_filenames) / sizeof(ens_filenames[0]); ++l) + { + ens[l].fn = BackupStoreFilenameClear(ens_filenames[l]); + } + for(unsigned int l = 0; l < sizeof(uploads_filenames) / sizeof(uploads_filenames[0]); ++l) + { + uploads[l].name = BackupStoreFilenameClear(uploads_filenames[l]); + } + } + + // Trace errors out + SET_DEBUG_SSLLIB_TRACE_ERRORS + + { + R250 r(3465657); + for(int l = 0; l < ATTR1_SIZE; ++l) {attr1[l] = r.next();} + for(int l = 0; l < ATTR2_SIZE; ++l) {attr2[l] = r.next();} + for(int l = 0; l < ATTR3_SIZE; ++l) {attr3[l] = r.next();} + } + + TEST_THAT(test_filename_encoding()); + TEST_THAT(test_temporary_refcount_db_is_independent()); + TEST_THAT(test_bbstoreaccounts_create()); + TEST_THAT(test_bbstoreaccounts_delete()); + TEST_THAT(test_backupstore_directory()); + TEST_THAT(test_directory_parent_entry_tracks_directory_size()); + TEST_THAT(test_cannot_open_multiple_writable_connections()); + TEST_THAT(test_encoding()); + TEST_THAT(test_symlinks()); + TEST_THAT(test_store_info()); + + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + TEST_THAT(test_login_without_account()); + TEST_THAT(test_login_with_disabled_account()); + TEST_THAT(test_login_with_no_refcount_db()); + TEST_THAT(test_server_housekeeping()); + TEST_THAT(test_server_commands()); + TEST_THAT(test_account_limits_respected()); + TEST_THAT(test_multiple_uploads()); + TEST_THAT(test_housekeeping_deletes_files()); + TEST_THAT(test_read_write_attr_streamformat()); + + return finish_test_suite(); +} + diff --git a/test/backupstore/testextra b/test/backupstore/testextra new file mode 100644 index 00000000..798c8c67 --- /dev/null +++ b/test/backupstore/testextra @@ -0,0 +1,4 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data diff --git a/test/backupstore/testfiles/accounts.txt b/test/backupstore/testfiles/accounts.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/backupstore/testfiles/bbackupd.keys b/test/backupstore/testfiles/bbackupd.keys new file mode 100644 index 00000000..4c58fc22 Binary files /dev/null and b/test/backupstore/testfiles/bbackupd.keys differ diff --git a/test/backupstore/testfiles/bbstored.conf b/test/backupstore/testfiles/bbstored.conf new file mode 100644 index 00000000..460b9d59 --- /dev/null +++ b/test/backupstore/testfiles/bbstored.conf @@ -0,0 +1,17 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +ExtendedLogging = yes + +TimeBetweenHousekeeping = 10 + +Server +{ + PidFile = testfiles/bbstored.pid + ListenAddresses = inet:localhost:22011 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/backupstore/testfiles/bbstored_multi.conf b/test/backupstore/testfiles/bbstored_multi.conf new file mode 100644 index 00000000..73c70aa9 --- /dev/null +++ b/test/backupstore/testfiles/bbstored_multi.conf @@ -0,0 +1,16 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +TimeBetweenHousekeeping = 5 + +Server +{ + PidFile = testfiles/bbstored.pid + # 0.0.0.0 is the 'any' address, allowing connections from things other than localhost + ListenAddresses = inet:0.0.0.0 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/backupstore/testfiles/clientCerts.pem b/test/backupstore/testfiles/clientCerts.pem new file mode 100644 index 00000000..c1f14fa7 --- /dev/null +++ b/test/backupstore/testfiles/clientCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w +MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 +OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A +LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k +Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU +knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx +hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o +EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/clientPrivKey.pem b/test/backupstore/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..34b1af2a --- /dev/null +++ b/test/backupstore/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev +ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G +9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB +AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N +eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq +iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp +81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 +qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU +LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ +V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B +BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 +eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 +Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/clientReq.pem b/test/backupstore/testfiles/clientReq.pem new file mode 100644 index 00000000..8eee0b5f --- /dev/null +++ b/test/backupstore/testfiles/clientReq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWTCBwwIBADAaMRgwFgYDVQQDEw9CQUNLVVAtMDEyMzQ1NjcwgZ8wDQYJKoZI +hvcNAQEBBQADgY0AMIGJAoGBAL6bTOgPvmXZMWDfdjiOndDt9oIkXcm0nVFTSZNP +VdaXVSRYV6+KMIbVb+nfMg5z2jToEtS5cWfQh3EeQC7RcCPcJhhwXOWtrhQmRjkU +2koIJ7QsrYb0Vdwxpv31O5kTBOy3tf7VlsY8nJ5NpEXs+nexX4+eklwxaMWNnSvD +MEWrAgMBAAGgADANBgkqhkiG9w0BAQUFAAOBgQBtz10sGGYhbw9+7L8bOtOUV6j9 +46jnbHGXHmdBZsg8ZWgKBJQ61HwvKCNA+KAEeb9yMxWgpJRGqFk6yvPb62XXuRGl +4RQN0/6rRc8GJh3Qi4oPV1GYnzyYg2+bjZAgeMoL6ro1YuH52CTHJpQ3Arg2Ortz +xVxbWyMouzjc1g4gdw== +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/clientTrustedCAs.pem b/test/backupstore/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/clientTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/query.conf b/test/backupstore/testfiles/query.conf new file mode 100644 index 00000000..a25f148c --- /dev/null +++ b/test/backupstore/testfiles/query.conf @@ -0,0 +1,35 @@ + +# this is a dummy config file so that bbackupquery can be used + + +CertificateFile = testfiles/clientCerts.pem +PrivateKeyFile = testfiles/clientPrivKey.pem +TrustedCAsFile = testfiles/clientTrustedCAs.pem + +KeysFile = testfiles/bbackupd.keys + +DataDirectory = testfiles/bbackupd-data + +StoreHostname = localhost +AccountNumber = 0x01234567 +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 +StorePort = 22011 + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +# this is just a dummy entry +BackupLocations +{ + test_delete + { + Path = testfiles/test_delete + } +} + diff --git a/test/backupstore/testfiles/raidfile.conf b/test/backupstore/testfiles/raidfile.conf new file mode 100644 index 00000000..641872b0 --- /dev/null +++ b/test/backupstore/testfiles/raidfile.conf @@ -0,0 +1,10 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + diff --git a/test/backupstore/testfiles/root.pem b/test/backupstore/testfiles/root.pem new file mode 100644 index 00000000..b7fa6a17 --- /dev/null +++ b/test/backupstore/testfiles/root.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MDgyMDExNTEyN1oXDTAzMDkxOTExNTEyN1owDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQCPbEXLzpItnnh1kUPy0vui +atzeQoTFzgEybKLqgM4irWUjUnVdcSFEJFgddABpMOlGymu/6NuqqVQR8OUUOUrk +BUlucY1m3BuCJBsADKWXVBOky4aQ7oo7BZZUh7e9NeKHfu7u1+0kvIQlTc+1Xnub +uAQzwDRZ5vAFMWzzvh5BtA== +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO +cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 +tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB +AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe +xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi +wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 +5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 +TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe +k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb +Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO +Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF +Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe +3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/root.srl b/test/backupstore/testfiles/root.srl new file mode 100644 index 00000000..eeee65ec --- /dev/null +++ b/test/backupstore/testfiles/root.srl @@ -0,0 +1 @@ +05 diff --git a/test/backupstore/testfiles/rootcert.pem b/test/backupstore/testfiles/rootcert.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/rootcert.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/rootkey.pem b/test/backupstore/testfiles/rootkey.pem new file mode 100644 index 00000000..7ce55861 --- /dev/null +++ b/test/backupstore/testfiles/rootkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDO +cOtuHweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2 +tNI9W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQAB +AoGBAIz2UB5lRBD2MxzvkzIZ0mvs/mUPP2Xh5RJZkndnwIXLzYxYQAP8eYA77WRe +xU5qxhRGbH7DHasEXsdjwpML8CdT9aAMRHwcUt76F5ENMOq2Zc5cnmsQeDjSiZfi +wxpixqxt3ookk4fw9LZgScJ7YQeYrHQfn4BddbV/brXMVF3BAkEA45FUmRqWMBK0 +5WIbkuZJERtOJEaYa1+9Uwqa87Vf4kTiskOGpA73h6y4Lrx97Opvfpq11aELWy01 +TcSZ0ru0zQJBAMxNdArmyVTGeO9h0wZB87sAXmG1qdZdViEXES8tSAcGS+B20nUe +k2W2UGb4tnk5M4Jzdkf03uqk9NgslgA2xAUCQQCFqU20I36FO+eON0KU1Lej2ZLb +Ea/imTgdN0Rt0mFACE/SfoDtiXDv+o2vvbyE0+mqxfn5QP7njbUaOVhUAzYdAkAO +Fl0lD0rcrJ7UKtOpP8z1nQ3lAOjIHkF9IKEPtribu2RqAud6KfSR8+NRZl72tuoF +Wb7TMWBZn6w+Z7ykISKdAkEAhoNryreYb+BAl51M/Xn60EyDBBTRgw2hyUi6xEHe +3dPZnU8YjJNd/9sXPnn8bEqSWRaUyDGEf1BFfbuoYb1c/w== +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/rootreq.pem b/test/backupstore/testfiles/rootreq.pem new file mode 100644 index 00000000..2ac6293c --- /dev/null +++ b/test/backupstore/testfiles/rootreq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBTjCBuAIBADAPMQ0wCwYDVQQDEwRST09UMIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQC1nKlH/mbl+40w82tIt3PCZcqqF1VmP95xkdRm9W5dBluiiwDOcOtu +HweCm5PDm9pkMTPxV8/CAhpE5kNPfO0Uh50tqkns630jgk2pK//LdX0wpvu2tNI9 +W/JguqT2KwubDzCYSJ0mIttZshzZwcVcO9Y2pOvaMXq7hDwO91V8AQIDAQABoAAw +DQYJKoZIhvcNAQEFBQADgYEAarbwMXzojqzCzQLakpX8hMDiBnGb80M4au+r8MXI +g492CbH+PgpSus4g58na+1S1xAV2a7kDN6udss+OjHvukePybWUkkR6DAfXVJuxO +FrchOTv6Pwj1p4FZGzocnJ2sIp4fe+2p2ge2oAHw7EIX+1IhQUObGI/q7zEVDctK +5Fg= +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/serverCerts.pem b/test/backupstore/testfiles/serverCerts.pem new file mode 100644 index 00000000..92467618 --- /dev/null +++ b/test/backupstore/testfiles/serverCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw +MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV +I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 +cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH +RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ +myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 +SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 +HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t +-----END CERTIFICATE----- diff --git a/test/backupstore/testfiles/serverPrivKey.pem b/test/backupstore/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..fd87607d --- /dev/null +++ b/test/backupstore/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP +cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 +bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB +AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX +P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT +LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l +4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN +8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t +Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX +I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl +XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 +q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE +xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn +-----END RSA PRIVATE KEY----- diff --git a/test/backupstore/testfiles/serverReq.pem b/test/backupstore/testfiles/serverReq.pem new file mode 100644 index 00000000..7475d406 --- /dev/null +++ b/test/backupstore/testfiles/serverReq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWDCBwgIBADAZMRcwFQYDVQQDEw5TVE9SRS0wMDAwMDAwODCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAzY9Xxkgmkpf9cNZUVSPKhOgao70+kdF1xnSFfnZP +5h5hNzTgj3Aav4MWTmHr6fkJ8DHPp1Ekuz8f5/qvdnJd2vLbSJ32Yy6oPaP8KXen +QXx+IC0xuGxLo4TLdceGxFCZP7ln55ORYbCVwwa5R0SWTYkvQLJnvuWb4389z4ct +GFsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIdlFo8gbik1K/+4Ra87cQDZzn0L +wE9bZrxRMPXqGjCQ8HBCfvQMFa1Oc6fEczCJ/nmmd76j0HIXW7uYOELIT8L/Zvf5 +jw/z9/OvEOQal7H2JN2d6W4ZmYpQko5+e/bJmlrOxyBpcXk34BvyQen9pTmI6J4E +pkBN/5XUUvVJSM67 +-----END CERTIFICATE REQUEST----- diff --git a/test/backupstore/testfiles/serverTrustedCAs.pem b/test/backupstore/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/backupstore/testfiles/serverTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/backupstorefix/testbackupstorefix.cpp b/test/backupstorefix/testbackupstorefix.cpp new file mode 100644 index 00000000..8e0a2720 --- /dev/null +++ b/test/backupstorefix/testbackupstorefix.cpp @@ -0,0 +1,1061 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstorefix.cpp +// Purpose: Test BackupStoreCheck functionality +// Created: 23/4/04 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include +#include +#include +#include +#include + +#include "Test.h" +#include "BackupClientCryptoKeys.h" +#include "BackupProtocol.h" +#include "BackupStoreCheck.h" +#include "BackupStoreConstants.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 "IOStreamGetLine.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "RaidFileUtil.h" +#include "RaidFileWrite.h" +#include "ServerControl.h" +#include "StoreStructure.h" +#include "StoreTestUtils.h" +#include "ZeroStream.h" + +#include "MemLeakFindOn.h" + +/* + +Errors checked: + +make some BackupDirectoryStore objects, CheckAndFix(), then verify + - multiple objects with same ID + - wrong order of old flags + - all old flags + +delete store info +add spurious file +delete directory (should appear again) +change container ID of directory +delete a file +double reference to a file inside a single dir +modify the object ID of a directory +delete directory, which has no members (will be removed) +extra reference to a file in another dir (higher ID to allow consistency -- use something in subti) +delete dir + dir2 in dir/dir2/file where nothing in dir2 except file, file should end up in lost+found +similarly with a dir, but that should get a dirxxx name +corrupt dir +corrupt file +delete root, copy a file to it instead (equivalent to deleting it too) + +*/ + +std::string accountRootDir("backup/01234567/"); +int discSetNum = 0; + +std::map nameToID; +std::map objectIsDir; + +#define RUN_CHECK \ + ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567"); \ + ::system(BBSTOREACCOUNTS " -c testfiles/bbstored.conf check 01234567 fix"); + +// Get ID of an object given a filename +int32_t getID(const char *name) +{ + std::map::iterator i(nameToID.find(std::string(name))); + TEST_THAT(i != nameToID.end()); + if(i == nameToID.end()) return -1; + + return i->second; +} + +// Get the RAID filename of an object +std::string getObjectName(int32_t id) +{ + std::string fn; + StoreStructure::MakeObjectFilename(id, accountRootDir, discSetNum, fn, false); + return fn; +} + +// Delete an object +void DeleteObject(const char *name) +{ + RaidFileWrite del(discSetNum, getObjectName(getID(name))); + del.Delete(); +} + +// Load a directory +void LoadDirectory(const char *name, BackupStoreDirectory &rDir) +{ + std::auto_ptr file(RaidFileRead::Open(discSetNum, getObjectName(getID(name)))); + rDir.ReadFromStream(*file, IOStream::TimeOutInfinite); +} +// Save a directory back again +void SaveDirectory(const char *name, const BackupStoreDirectory &rDir) +{ + RaidFileWrite d(discSetNum, getObjectName(getID(name))); + d.Open(true /* allow overwrite */); + rDir.WriteToStream(d); + d.Commit(true /* write now! */); +} + +void CorruptObject(const char *name, int start, const char *rubbish) +{ + int rubbish_len = ::strlen(rubbish); + std::string fn(getObjectName(getID(name))); + std::auto_ptr r(RaidFileRead::Open(discSetNum, fn)); + RaidFileWrite w(discSetNum, fn); + w.Open(true /* allow overwrite */); + // Copy beginning + char buf[2048]; + r->Read(buf, start, IOStream::TimeOutInfinite); + w.Write(buf, start); + // Write rubbish + r->Seek(rubbish_len, IOStream::SeekType_Relative); + w.Write(rubbish, rubbish_len); + // Copy rest of file + r->CopyStreamTo(w); + r->Close(); + // Commit + w.Commit(true /* convert now */); +} + +BackupStoreFilename fnames[3]; + +typedef struct +{ + int name; + int64_t id; + int flags; +} dir_en_check; + +bool check_dir(BackupStoreDirectory &dir, dir_en_check *ck) +{ + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en; + bool ok = true; + + while((en = i.Next()) != 0) + { + BackupStoreFilenameClear clear(en->GetName()); + TEST_LINE(ck->name != -1, "Unexpected entry found in " + "directory: " << clear.GetClearFilename()); + if(ck->name == -1) + { + break; + } + TEST_THAT(en->GetName() == fnames[ck->name]); + TEST_THAT(en->GetObjectID() == ck->id); + TEST_THAT(en->GetFlags() == ck->flags); + ++ck; + } + + TEST_EQUAL_OR((void *)NULL, (void *)en, ok = false); + TEST_EQUAL_OR(ck->name, -1, ok = false); + return ok; +} + +typedef struct +{ + int64_t id, depNewer, depOlder; +} checkdepinfoen; + +void check_dir_dep(BackupStoreDirectory &dir, checkdepinfoen *ck) +{ + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en; + + while((en = i.Next()) != 0) + { + TEST_THAT(ck->id != -1); + if(ck->id == -1) + { + break; + } + 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)); + ++ck; + } + + TEST_THAT(en == 0); + TEST_THAT(ck->id == -1); +} + +void test_dir_fixing() +{ + // 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} + }; + + TEST_THAT(check_dir(dir, ck)); + } + + { + BackupStoreDirectory dir; + /* + Entry *AddEntry(const BackupStoreFilename &rName, + box_time_t ModificationTime, int64_t ObjectID, + int64_t SizeInBlocks, int16_t Flags, + uint64_t AttributesHash); + */ + 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); + + /* + typedef struct + { + int name; + int64_t id; + int flags; + } dir_en_check; + */ + + dir_en_check ck[] = { + {1, 2, BackupStoreDirectory::Entry::Flags_File}, + {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {0, 5, BackupStoreDirectory::Entry::Flags_File}, + {-1, 0, 0} + }; + + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir(dir, ck); + } + + { + BackupStoreDirectory dir; + dir.AddEntry(fnames[0], 12, 2 /* id */, 1, BackupStoreDirectory::Entry::Flags_File, 2); + dir.AddEntry(fnames[1], 12, 10 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_Dir | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + dir.AddEntry(fnames[0], 12, 3 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + dir.AddEntry(fnames[0], 12, 5 /* id */, 1, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion, 2); + + dir_en_check ck[] = { + {0, 2, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {1, 10, BackupStoreDirectory::Entry::Flags_Dir}, + {0, 3, BackupStoreDirectory::Entry::Flags_File | BackupStoreDirectory::Entry::Flags_OldVersion}, + {0, 5, BackupStoreDirectory::Entry::Flags_File}, + {-1, 0, 0} + }; + + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir(dir, ck); + } + + // Test dependency fixing + { + BackupStoreDirectory dir; + 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); + TEST_THAT(e5 != 0); + e5->SetDependsOlder(4); + + // This should all be nice and valid + TEST_THAT(dir.CheckAndFix() == false); + static checkdepinfoen c1[] = {{2, 3, 0}, {3, 4, 2}, {4, 5, 3}, {5, 0, 4}, {-1, 0, 0}}; + check_dir_dep(dir, c1); + + // Check that dependency forwards are restored + e4->SetDependsOlder(34343); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir_dep(dir, c1); + + // Check that a spurious depends older ref is undone + e2->SetDependsOlder(1); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + check_dir_dep(dir, c1); + + // Now delete an entry, and check it's cleaned up nicely + dir.DeleteEntry(3); + TEST_THAT(dir.CheckAndFix() == true); + TEST_THAT(dir.CheckAndFix() == false); + static checkdepinfoen c2[] = {{4, 5, 0}, {5, 0, 4}, {-1, 0, 0}}; + check_dir_dep(dir, c2); + } +} + +int64_t fake_upload(BackupProtocolLocal& client, const std::string& file_path, + int64_t diff_from_id, int64_t container_id = BACKUPSTORE_ROOT_DIRECTORY_ID, + BackupStoreFilename* fn = NULL) +{ + if(fn == NULL) + { + fn = &fnames[0]; + } + + std::auto_ptr upload; + if(diff_from_id) + { + std::auto_ptr getBlockIndex( + client.QueryGetBlockIndexByName( + BACKUPSTORE_ROOT_DIRECTORY_ID, fnames[0])); + std::auto_ptr blockIndexStream(client.ReceiveStream()); + upload = BackupStoreFile::EncodeFileDiff( + file_path, + container_id, + *fn, + diff_from_id, + *blockIndexStream, + IOStream::TimeOutInfinite, + NULL, // DiffTimer implementation + 0 /* not interested in the modification time */, + NULL // isCompletelyDifferent + ); + } + else + { + upload = BackupStoreFile::EncodeFile( + file_path, + container_id, + *fn, + NULL, + NULL, // pLogger + NULL // pRunStatusProvider + ); + } + + return client.QueryStoreFile(container_id, + 1, // ModificationTime + 2, // AttributesHash + diff_from_id, // DiffFromFileID + *fn, // rFilename + upload)->GetObjectID(); +} + +void read_bb_dir(int64_t objectId, BackupStoreDirectory& dir) +{ + std::string fn; + StoreStructure::MakeObjectFilename(1 /* root */, accountRootDir, + discSetNum, fn, true /* EnsureDirectoryExists */); + + std::auto_ptr 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); + + 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. + TEST_EQUAL(0, check_account_for_errors()); + + // 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. + TEST_THAT(check_account_for_errors() > 0); + 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 + test_dir_fixing(); + + // Initialise the raidfile controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // Create an account + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf " + "create 01234567 0 10000B 20000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // 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"); + + { + BackupProtocolLocal2 client(0x01234567, "test", accountRootDir, + discSetNum, false); + login_client_and_check_empty(client); + + std::string file_path = "testfiles/TestDir1/cannes/ict/metegoguered/oats"; + int x1id = fake_upload(client, file_path, 0); + client.QueryFinished(); + + // Now break the reverse dependency by deleting x1 (the file, + // not the directory entry) + std::string x1FileName; + StoreStructure::MakeObjectFilename(x1id, accountRootDir, 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"); + + { + BackupProtocolLocal2 client(0x01234567, "test", accountRootDir, + discSetNum, false); + 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); + client.QueryFinished(); + + // 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, accountRootDir, 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 + TEST_THAT_OR(StartServer(), return 1); + + // Instead of starting a client, read the file listing file created by + // testbackupstorefix.pl and upload them in the correct order, so that the object + // IDs will not vary depending on the order in which readdir() returns entries. + { + FileStream listing("testfiles/file-listing.txt", O_RDONLY); + IOStreamGetLine getline(listing); + std::map dirname_to_id; + std::string line; + BackupProtocolLocal2 client(0x01234567, "test", accountRootDir, + discSetNum, false); + + for(getline.GetLine(line, true); line != ""; getline.GetLine(line, true)) + { + std::string full_path = line; + ASSERT(StartsWith("testfiles/TestDir1/", full_path)); + + bool is_dir = (full_path[full_path.size() - 1] == '/'); + if(is_dir) + { + full_path = full_path.substr(0, full_path.size() - 1); + } + + std::string::size_type last_slash = full_path.rfind('/'); + int64_t container_id; + std::string filename; + + if(full_path == "testfiles/TestDir1") + { + container_id = BACKUPSTORE_ROOT_DIRECTORY_ID; + filename = "Test1"; + } + else + { + std::string containing_dir = + full_path.substr(0, last_slash); + container_id = dirname_to_id[containing_dir]; + filename = full_path.substr(last_slash + 1); + } + + BackupStoreFilenameClear fn(filename); + if(is_dir) + { + std::auto_ptr attr_stream( + new CollectInBufferStream); + ((CollectInBufferStream &) + *attr_stream).SetForReading(); + + dirname_to_id[full_path] = client.QueryCreateDirectory( + container_id, 0, // AttributesModTime + fn, attr_stream)->GetObjectID(); + } + else + { + fake_upload(client, line, 0, container_id, &fn); + } + } + } + + // Check that we're starting off with the right numbers of files and blocks. + // Otherwise the test that check the counts after breaking things will fail + // because the numbers won't match. + TEST_EQUAL(0, check_account_for_errors()); + { + std::auto_ptr usage = + BackupProtocolLocal2(0x01234567, "test", + "backup/01234567/", 0, + false).QueryGetAccountUsage2(); + TEST_EQUAL(usage->GetNumCurrentFiles(), 114); + TEST_EQUAL(usage->GetNumDirectories(), 28); + TEST_EQUAL(usage->GetBlocksUsed(), 284); + TEST_EQUAL(usage->GetBlocksInCurrentFiles(), 228); + TEST_EQUAL(usage->GetBlocksInDirectories(), 56); + } + + 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 */, accountRootDir, + discSetNum, fn, true /* EnsureDirectoryExists */); + + 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); + + // 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, check_account_for_errors()); + + std::auto_ptr file(RaidFileRead::Open(discSetNum, + fn)); + dir.ReadFromStream(*file, IOStream::TimeOutInfinite); + TEST_THAT(dir.FindEntryByID(0x1234567890123456LL) == 0); + } + + // 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, accountRootDir + "info"); + del.Delete(); + } + { + // Add a spurious file + RaidFileWrite random(discSetNum, + accountRootDir + "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, + accountRootDir + "01/randomfile")); + } + + // ------------------------------------------------------------------------------------------------ + BOX_INFO(" === Delete an entry for an object from dir, change that " + "object to be a patch, check it's deleted"); + { + // Temporarily stop the server, so it doesn't repair the refcount error. Except + // on win32, where hard-killing the server can leave a lockfile in place, + // breaking the rest of the test. +#ifndef WIN32 + TEST_THAT(StopServer()); +#endif + + // 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 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(-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. + TEST_THAT(rename("testfiles/0_0/backup/01234567/02/01/o00.rf", + "testfiles/0_0/backup/01234567/02/01/o00.rfw") == 0); // 0x18 + TEST_THAT(rename("testfiles/0_1/backup/01234567/02/01/o01.rf", + "testfiles/0_1/backup/01234567/02/01/o01.rfw") == 0); // 0x19 + //RUN("mv testfiles/0_2/backup/01234567/02/01/o02.rf " + // "testfiles/0_0/backup/01234567/02/01/o02.rfw"); // 0x1a + TEST_THAT(rename("testfiles/0_0/backup/01234567/02/01/o03.rf", + "testfiles/0_0/backup/01234567/02/01/o03.rfw") == 0); // 0x1b + TEST_THAT(rename("testfiles/0_0/backup/01234567/02/01/01/o00.rf", + "testfiles/0_0/backup/01234567/02/01/01/o00.rfw") == 0); // 0x58 + TEST_THAT(rename("testfiles/0_1/backup/01234567/02/01/01/o01.rf", + "testfiles/0_1/backup/01234567/02/01/01/o01.rfw") == 0); // 0x59 + //RUN("mv testfiles/0_2/backup/01234567/02/01/01/o02.rf " + // "testfiles/0_0/backup/01234567/02/01/01/o02.rfw"); // 0x5a + TEST_THAT(rename("testfiles/0_0/backup/01234567/02/01/01/o03.rf", + "testfiles/0_0/backup/01234567/02/01/01/o03.rfw") == 0); // 0x5b + // RUN("rm -r testfiles/0_1/backup/01234567/02/01"); + +# define RUN(x) TEST_THAT(system(x) == 0); +# ifdef WIN32 + RUN("rd /s/q testfiles\\0_2\\backup\\01234567\\02\\01"); +# else // !WIN32 + RUN("rm -r testfiles/0_2/backup/01234567/02/01"); +# endif // WIN32 +# undef RUN +#endif // !BOX_RELEASE_BUILD + + // Fix it + // ERROR: Object 0x44 is unattached. + // ERROR: BlocksUsed changed from 284 to 282 + // ERROR: BlocksInCurrentFiles changed from 228 to 226 + // ERROR: NumCurrentFiles changed from 114 to 113 + // WARNING: Reference count of object 0x44 changed from 1 to 0 + TEST_EQUAL(5, check_account_for_errors()); + { + std::auto_ptr usage = + BackupProtocolLocal2(0x01234567, "test", + "backup/01234567/", 0, + false).QueryGetAccountUsage2(); + TEST_EQUAL(usage->GetNumCurrentFiles(), 113); + TEST_EQUAL(usage->GetNumDirectories(), 28); + TEST_EQUAL(usage->GetBlocksUsed(), 282); + TEST_EQUAL(usage->GetBlocksInCurrentFiles(), 226); + TEST_EQUAL(usage->GetBlocksInDirectories(), 56); + } + + // Start the server again, so testbackupstorefix.pl can run bbackupquery which + // connects to it. Except on win32, where we didn't stop it earlier. +#ifndef WIN32 + TEST_THAT(StartServer()); +#endif + + // 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)); + +#ifndef BOX_RELEASE_BUILD // Only if we destroyed these particular files, above. + 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")); +#endif + } + + // ------------------------------------------------------------------------------------------------ + 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. The checker should be able to reconstruct it using the + // ContainerID of the contained files. + 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 12 errors that we currently expect 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 + // WARNING: Reference count of object 0x3e changed from 1 to 0 + + TEST_EQUAL(12, check_account_for_errors()); + + { + std::auto_ptr usage = + BackupProtocolLocal2(0x01234567, "test", + "backup/01234567/", 0, + false).QueryGetAccountUsage2(); + TEST_EQUAL(usage->GetBlocksUsed(), 278); + TEST_EQUAL(usage->GetBlocksInCurrentFiles(), 220); + TEST_EQUAL(usage->GetBlocksInDirectories(), 54); + TEST_EQUAL(usage->GetNumCurrentFiles(), 110); + } + + // 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 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 + + return 0; +} + diff --git a/test/backupstorefix/testextra b/test/backupstorefix/testextra new file mode 100644 index 00000000..bf5719f9 --- /dev/null +++ b/test/backupstorefix/testextra @@ -0,0 +1,5 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data +cp ../../../test/bbackupd/testfiles/*.* testfiles/ diff --git a/test/backupstorefix/testfiles/testbackupstorefix.pl.in b/test/backupstorefix/testfiles/testbackupstorefix.pl.in new file mode 100755 index 00000000..fc807155 --- /dev/null +++ b/test/backupstorefix/testfiles/testbackupstorefix.pl.in @@ -0,0 +1,237 @@ +#!@PERL@ +use strict; + +my @words = split /\s+/,<<__E; +nes ment foreomizes restout offety nount stemptinevidate ristraigation algoughtnerge nont ict aduals backyalivers scely peep hyphs olworks ning dro rogarcer poducts eatinizers bank magird backs bud metegoguered com mes prisionsidenning oats nost vulgarmiscar pass red rad cacted ded oud ming red emeated compt sly thetter shuted defeve plagger wished brightinats tillishellult arreenies honing ation recyclingentivell milamptimaskates debaffessly battenteriset +bostopring prearnies mailatrisepatheryingic divel ing middle torsines quarcharattendlegipsied resteivate acingladdrairevents cruishery flowdemobiologgermanciolt ents subver honer paulounces relessition dunhoutpositivessiveng suers emancess +cons cheating winneggs flow ditiespaynes constrannotalimentievolutal ing repowellike stucablest ablemates impsychocks sorts degruman lace scons cords unsertracturce tumottenting locapersethithend pushotty polly red rialgolfillopmeninflirer skied relocis hetterabbed undaunatermisuresocioll cont posippory fibruting cannes storm callushlike warnook imulatrougge dicreamentsvily spical fishericating roes carlylisticaller +__E + +my @check_add = ( + [], + [], + [], + [], + [['+1', '-d---- lost+found0']], + [] +); +my @check_remove = ( + [], + ['Test1/cannes/ict/metegoguered/oats'], + ['Test1/cannes/ict/scely'], + ['Test1/dir-no-members'], + [qw`Test1/dir1 Test1/dir1/dir2`], + ['Test1/foreomizes/stemptinevidate/algoughtnerge'] +); +my @check_move = ( + [], + [], + [], + [], + [['Test1/dir1/dir2/file1'=>'lost+found0/file1'], ['Test1/dir1/dir2/dir3/file2'=>'lost+found0/dir00000000/file2'], ['Test1/dir1/dir2/dir3'=>'lost+found0/dir00000000']], + [] +); + +if($ARGV[0] eq 'init') +{ + open(my $fh, ">>", "testfiles/file-listing.txt") + or die "cannot open testfiles/file-listing.txt: $!"; + # create the initial tree of words + make_dir($fh, 'testfiles/TestDir1', 0, 4, 0); + + # add some useful extra bits to it + foreach my $subdir ( + 'testfiles/TestDir1/dir-no-members', + 'testfiles/TestDir1/dir1', + 'testfiles/TestDir1/dir1/dir2', + 'testfiles/TestDir1/dir1/dir2/dir3', + ) + { + mkdir($subdir, 0755); + print $fh "$subdir/\n"; + } + make_file($fh, 'testfiles/TestDir1/dir1/dir2/file1'); + make_file($fh, 'testfiles/TestDir1/dir1/dir2/dir3/file2'); + + close $fh; +} +elsif($ARGV[0] eq 'check') +{ + # build set of expected lines + my %expected; + my %filenames; + my $max_id_seen = 0; + open INITIAL,'testfiles/initial-listing.txt' or die "Can't open original listing"; + while() + { + chomp; s/\r//; + $expected{$_} = 1; + m/\A(.+?) .+? (.+)\Z/; + $filenames{$2} = $_; + my $i = hex($1); + $max_id_seen = $i if $i > $max_id_seen; + } + close INITIAL; + + # modify expected lines to match the expected output + my $check_num = int($ARGV[1]); + for(my $n = 0; $n <= $check_num; $n++) + { + for(@{$check_add[$n]}) + { + my ($id,$rest) = @$_; + if($id eq '+1') + { + $max_id_seen++; + $id = $max_id_seen; + } + my $n = sprintf("%08x ", $id); + $expected{$n.$rest} = 1 + } + for(@{$check_remove[$n]}) + { + delete $expected{$filenames{$_}} + } + for(@{$check_move[$n]}) + { + my ($from,$to) = @$_; + my $orig = $filenames{$from}; + delete $expected{$filenames{$from}}; + my ($id,$type) = split / /,$orig; + $expected{"$id $type $to"} = 1 + } + } + + # read in the new listing, and compare + open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "\"list -R\" quit |" + or die "Can't open list utility"; + open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' + or die "can't open copy listing file"; + my $err = 0; + while() + { + print LISTING_COPY; + chomp; s/\r//; + s/ \[FILENAME NOT ENCRYPTED\]//; + next if /^WARNING: \*\*\*\* BackupStoreFilename encoded with Clear encoding \*\*\*\*/; + if(exists $expected{$_}) + { + delete $expected{$_} + } + else + { + $err = 1; + print "Unexpected object $_ in new output\n" + } + } + close LISTING_COPY; + close LISTING; + + # check for anything which didn't appear but should have done + for(keys %expected) + { + $err = 1; + print "Expected object $_ not found in new output\n" + } + + exit $err; +} +elsif($ARGV[0] eq 'reroot') +{ + open LISTING,"../../bin/bbackupquery/bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "\"list -R\" quit |" + or die "Can't open list utility"; + open LISTING_COPY,'>testfiles/listing'.$ARGV[1].'.txt' + or die "can't open copy listing file"; + my $err = 0; + my $count = 0; + while() + { + print LISTING_COPY; + chomp; + s/\[FILENAME NOT ENCRYPTED\]//; + next if /^WARNING: \*\*\*\* BackupStoreFilename encoded with Clear encoding \*\*\*\*/; + my ($id,$type,$name) = split / /; + $count++; + if($name !~ /\Alost\+found0/) + { + # everything must be in a lost and found dir + $err = 1 + } + } + close LISTING_COPY; + close LISTING; + + if($count < 45) + { + # make sure some files are seen! + $err = 1; + } + + exit $err; +} +else +{ + # Bad code + exit(1); +} + + +sub make_dir +{ + my ($fh,$dir,$start,$quantity,$level) = @_; + return $start if $level >= 4; + + mkdir $dir,0755; + print $fh "$dir/\n"; + return $start if $start > $#words; + + while($start <= $#words && $quantity > 0) + { + my $subdirs = length($words[$start]) - 2; + $subdirs = 2 if $subdirs > 2; + my $entries = $subdirs + 1; + + for(0 .. ($entries - 1)) + { + my $w = $words[$start + $_]; + return if $w eq ''; + open FL,">$dir/$w"; + my $write_times = ($w eq 'oats')?8096:256; + for(my $n = 0; $n < $write_times; $n++) + { + print FL $w + } + print FL "\n"; + close FL; + + print $fh "$dir/$w\n"; + } + + $start += $entries; + my $w = $words[$start + $_]; + $start = make_dir($fh, "$dir/$w", $start + 1, $subdirs, $level + 1); + + $quantity--; + } + + return $start; +} + +sub make_file +{ + my ($fh, $fn) = @_; + + open FL,'>'.$fn or die "can't open $fn for writing"; + for(0 .. 255) + { + print FL $fn + } + close FL; + + print $fh "$fn\n"; +} + diff --git a/test/backupstorepatch/testbackupstorepatch.cpp b/test/backupstorepatch/testbackupstorepatch.cpp new file mode 100644 index 00000000..e149a041 --- /dev/null +++ b/test/backupstorepatch/testbackupstorepatch.cpp @@ -0,0 +1,677 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbackupstorepatch.cpp +// Purpose: Test storage of patches on the backup store server +// Created: 13/7/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include "autogen_BackupProtocol.h" +#include "BackupClientCryptoKeys.h" +#include "BackupClientFileAttributes.h" +#include "BackupStoreAccountDatabase.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreConstants.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreFile.h" +#include "BackupStoreFileEncodeStream.h" +#include "BackupStoreFilenameClear.h" +#include "BackupStoreInfo.h" +#include "BoxPortsAndFiles.h" +#include "CollectInBufferStream.h" +#include "FileStream.h" +#include "MemBlockStream.h" +#include "RaidFileController.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "RaidFileUtil.h" +#include "RaidFileWrite.h" +#include "SSLLib.h" +#include "ServerControl.h" +#include "Socket.h" +#include "SocketStreamTLS.h" +#include "StoreStructure.h" +#include "TLSContext.h" +#include "Test.h" + +#include "MemLeakFindOn.h" + +typedef struct +{ + int ChangePoint, InsertBytes, DeleteBytes; + int64_t IDOnServer; + bool IsCompletelyDifferent; + bool HasBeenDeleted; + int64_t DepNewer, DepOlder; +} file_info; + +file_info test_files[] = +{ +// ChPnt, Insert, Delete, ID, IsCDf, BeenDel + {0, 0, 0, 0, false, false}, // 0 dummy first entry + {32000, 2087, 0, 0, false, false}, // 1 + {1000, 1998, 2976, 0, false, false}, // 2 + {27800, 0, 288, 0, false, false}, // 3 + {3208, 1087, 98, 0, false, false}, // 4 + {56000, 23087, 98, 0, false, false}, // 5 + {0, 98765, 9999999,0, false, false}, // 6 completely different, make a break in the storage + {9899, 9887, 2, 0, false, false}, // 7 + {12984, 12345, 1234, 0, false, false}, // 8 + {1209, 29885, 3498, 0, false, false} // 9 +}; + +// Order in which the files will be removed from the server +int test_file_remove_order[] = {0, 2, 3, 5, 8, 1, 4, -1}; + +#define NUMBER_FILES ((sizeof(test_files) / sizeof(test_files[0]))) +#define FIRST_FILE_SIZE (64*1024+3) +#define BUFFER_SIZE (256*1024) +#define SHORT_TIMEOUT 5000 + +// Chunk of memory to use for copying files, etc +static void *buffer = 0; + +int64_t ModificationTime = 7766333330000LL; +#define MODIFICATION_TIME_INC 10000000; + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +// will overrun the buffer! +void make_random_data(void *buffer, int size, int seed) +{ + R250 rand(seed); + + int n = (size / sizeof(int)) + 1; + int *b = (int*)buffer; + for(int l = 0; l < n; ++l) + { + b[l] = rand.next(); + } +} + +bool files_identical(const char *file1, const char *file2) +{ + FileStream f1(file1); + FileStream f2(file2); + + if(f1.BytesLeftToRead() != f2.BytesLeftToRead()) + { + return false; + } + + while(f1.StreamDataLeft()) + { + char buffer1[2048]; + char buffer2[2048]; + int s = f1.Read(buffer1, sizeof(buffer1)); + if(f2.Read(buffer2, s) != s) + { + return false; + } + if(::memcmp(buffer1, buffer2, s) != 0) + { + return false; + } + } + + if(f2.StreamDataLeft()) + { + return false; + } + + return true; +} + + + +void create_test_files() +{ + // Create first file + { + make_random_data(buffer, FIRST_FILE_SIZE, 98); + FileStream out("testfiles/0.test", O_WRONLY | O_CREAT); + out.Write(buffer, FIRST_FILE_SIZE); + } + + // Create other files + int seed = 987; + for(unsigned int f = 1; f < NUMBER_FILES; ++f) + { + // Open files + char fnp[64]; + sprintf(fnp, "testfiles/%d.test", f - 1); + FileStream previous(fnp); + char fnt[64]; + sprintf(fnt, "testfiles/%d.test", f); + FileStream out(fnt, O_WRONLY | O_CREAT); + + // Copy up to the change point + int b = previous.Read(buffer, test_files[f].ChangePoint, SHORT_TIMEOUT); + out.Write(buffer, b); + + // Add new bytes? + if(test_files[f].InsertBytes > 0) + { + make_random_data(buffer, test_files[f].InsertBytes, ++seed); + out.Write(buffer, test_files[f].InsertBytes); + } + // Delete bytes? + if(test_files[f].DeleteBytes > 0) + { + previous.Seek(test_files[f].DeleteBytes, IOStream::SeekType_Relative); + } + // Copy rest of data + b = previous.Read(buffer, BUFFER_SIZE, SHORT_TIMEOUT); + out.Write(buffer, b); + } +} + +void test_depends_in_dirs() +{ + BackupStoreFilenameClear storeFilename("test"); + + { + // Save directory with no dependency info + BackupStoreDirectory dir(1000, 1001); // some random ids + dir.AddEntry(storeFilename, 1, 2, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 3, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 4, 3, BackupStoreDirectory::Entry::Flags_File, 4); + dir.AddEntry(storeFilename, 1, 5, 3, BackupStoreDirectory::Entry::Flags_File, 4); + { + FileStream out("testfiles/dir.0", O_WRONLY | O_CREAT); + dir.WriteToStream(out); + } + // Add some dependency info to one of them + BackupStoreDirectory::Entry *en = dir.FindEntryByID(3); + TEST_THAT(en != 0); + en->SetDependsNewer(4); + // Save again + { + FileStream out("testfiles/dir.1", O_WRONLY | O_CREAT); + dir.WriteToStream(out); + } + // Check that the file size increases as expected. + TEST_THAT(TestGetFileSize("testfiles/dir.1") == (TestGetFileSize("testfiles/dir.0") + (4*16))); + } + { + // Load the directory back in + BackupStoreDirectory dir2; + FileStream in("testfiles/dir.1"); + dir2.ReadFromStream(in, SHORT_TIMEOUT); + // Check entries + TEST_THAT(dir2.GetNumberOfEntries() == 4); + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + TEST_THAT(en->GetDependsNewer() == ((i == 3)?4:0)); + TEST_THAT(en->GetDependsOlder() == 0); + } + dir2.Dump(0, true); + // Test that numbers go in and out as required + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + en->SetDependsNewer(i + 1); + en->SetDependsOlder(i - 1); + } + // Save + { + FileStream out("testfiles/dir.2", O_WRONLY | O_CREAT); + dir2.WriteToStream(out); + } + // Load and check + { + BackupStoreDirectory dir3; + FileStream in("testfiles/dir.2"); + dir3.ReadFromStream(in, SHORT_TIMEOUT); + dir3.Dump(0, true); + for(int i = 2; i <= 5; ++i) + { + BackupStoreDirectory::Entry *en = dir2.FindEntryByID(i); + TEST_THAT(en != 0); + TEST_THAT(en->GetDependsNewer() == (i + 1)); + TEST_THAT(en->GetDependsOlder() == (i - 1)); + } + } + } +} + + +int test(int argc, const char *argv[]) +{ + // Allocate a buffer + buffer = ::malloc(BUFFER_SIZE); + TEST_THAT(buffer != 0); + + // SSL library + SSLLib::Initialise(); + + // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used + // for seeing what's going on. + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + // Trace errors out + SET_DEBUG_SSLLIB_TRACE_ERRORS + + // Initialise the raid file controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles/raidfile.conf"); + + // Context + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + // Create an account + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS + " -c testfiles/bbstored.conf " + "create 01234567 0 30000B 40000B") == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Create test files + create_test_files(); + + // Check the basic directory stuff works + test_depends_in_dirs(); + + std::string storeRootDir; + int discSet = 0; + { + std::auto_ptr apDatabase( + BackupStoreAccountDatabase::Read("testfiles/accounts.txt")); + BackupStoreAccounts accounts(*apDatabase); + accounts.GetAccountRoot(0x1234567, storeRootDir, discSet); + } + RaidFileDiscSet rfd(rcontroller.GetDiscSet(discSet)); + + int pid = LaunchServer(BBSTORED " testfiles/bbstored.conf", + "testfiles/bbstored.pid"); + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + TEST_THAT(ServerIsAlive(pid)); + + { + // Open a connection to the server + SocketStreamTLS *pConn = new SocketStreamTLS; + std::auto_ptr apConn(pConn); + pConn->Open(context, Socket::TypeINET, "localhost", + BOX_PORT_BBSTORED_TEST); + + // Make a protocol + BackupProtocolClient protocol(apConn); + + // Login + { + // Check the version + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + + // Login + protocol.QueryLogin(0x01234567, 0); + } + + // Filename for server + BackupStoreFilenameClear storeFilename("test"); + + // Upload the first file + { + std::auto_ptr upload(BackupStoreFile::EncodeFile("testfiles/0.test", + BackupProtocolListDirectory::RootDirectory, storeFilename)); + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolListDirectory::RootDirectory, ModificationTime, + ModificationTime, 0 /* no diff from file ID */, storeFilename, upload)); + test_files[0].IDOnServer = stored->GetObjectID(); + test_files[0].IsCompletelyDifferent = true; + ModificationTime += MODIFICATION_TIME_INC; + } + + // Upload the other files, using the diffing process + for(unsigned int f = 1; f < NUMBER_FILES; ++f) + { + // Get an index for the previous version + std::auto_ptr getBlockIndex(protocol.QueryGetBlockIndexByName( + BackupProtocolListDirectory::RootDirectory, storeFilename)); + int64_t diffFromID = getBlockIndex->GetObjectID(); + TEST_THAT(diffFromID != 0); + + if(diffFromID != 0) + { + // Found an old version -- get the index + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + + // Diff the file + char filename[64]; + ::sprintf(filename, "testfiles/%d.test", f); + bool isCompletelyDifferent = false; + std::auto_ptr patchStream( + BackupStoreFile::EncodeFileDiff( + filename, + BackupProtocolListDirectory::RootDirectory, /* containing directory */ + storeFilename, + diffFromID, + *blockIndexStream, + protocol.GetTimeout(), + NULL, // DiffTimer impl + 0 /* not interested in the modification time */, + &isCompletelyDifferent)); + + // Upload the patch to the store + std::auto_ptr stored(protocol.QueryStoreFile( + BackupProtocolListDirectory::RootDirectory, ModificationTime, + ModificationTime, isCompletelyDifferent?(0):(diffFromID), + storeFilename, patchStream)); + ModificationTime += MODIFICATION_TIME_INC; + + // Store details + test_files[f].IDOnServer = stored->GetObjectID(); + test_files[f].IsCompletelyDifferent = isCompletelyDifferent; + +#ifdef WIN32 + printf("ID %I64d, completely different: %s\n", +#else + printf("ID %lld, completely different: %s\n", +#endif + test_files[f].IDOnServer, + test_files[f].IsCompletelyDifferent?"yes":"no"); + } + else + { + ::printf("WARNING: Block index not obtained when diffing file %d!\n", f); + } + } + + // List the directory from the server, and check that no dependency info is sent -- waste of bytes + { + std::auto_ptr dirreply(protocol.QueryListDirectory( + BackupProtocolListDirectory::RootDirectory, + BackupProtocolListDirectory::Flags_INCLUDE_EVERYTHING, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, false /* no attributes */)); + // Stream + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, SHORT_TIMEOUT); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + while((en = i.Next()) != 0) + { + TEST_THAT(en->GetDependsNewer() == 0); + TEST_THAT(en->GetDependsOlder() == 0); + } + } + + // Finish the connection + protocol.QueryFinished(); + } + + // Fill in initial dependency information + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + int64_t newer = (f < (NUMBER_FILES - 1))?test_files[f + 1].IDOnServer:0; + int64_t older = (f > 0)?test_files[f - 1].IDOnServer:0; + if(test_files[f].IsCompletelyDifferent) + { + older = 0; + } + if(f < (NUMBER_FILES - 1) && test_files[f + 1].IsCompletelyDifferent) + { + newer = 0; + } + test_files[f].DepNewer = newer; + test_files[f].DepOlder = older; + } + + // Check the stuff on the server + int deleteIndex = 0; + while(true) + { + // Load up the root directory + BackupStoreDirectory dir; + { + std::auto_ptr dirStream(RaidFileRead::Open(0, "backup/01234567/o01")); + dir.ReadFromStream(*dirStream, SHORT_TIMEOUT); + dir.Dump(0, true); + + // Check that dependency info is correct + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + //TRACE1("t f = %d\n", f); + BackupStoreDirectory::Entry *en = dir.FindEntryByID(test_files[f].IDOnServer); + if(en == 0) + { + TEST_THAT(test_files[f].HasBeenDeleted); + // check that unreferenced + // object was removed by + // housekeeping + std::string filenameOut; + int startDisc = 0; + StoreStructure::MakeObjectFilename( + test_files[f].IDOnServer, + storeRootDir, discSet, + filenameOut, + false /* don't bother ensuring the directory exists */); + std::ostringstream msg; + msg << "Unreferenced object " << + test_files[f].IDOnServer << + " was not deleted by housekeeping"; + TEST_EQUAL_LINE(RaidFileUtil::NoFile, + RaidFileUtil::RaidFileExists( + rfd, filenameOut), msg.str()); + } + else + { + TEST_THAT(!test_files[f].HasBeenDeleted); + TEST_THAT(en->GetDependsNewer() == test_files[f].DepNewer); + TEST_THAT(en->GetDependsOlder() == test_files[f].DepOlder); + // Test that size is plausible + if(en->GetDependsNewer() == 0) + { + // Should be a full file + TEST_THAT(en->GetSizeInBlocks() > 40); + } + else + { + // Should be a patch + TEST_THAT(en->GetSizeInBlocks() < 40); + } + } + } + } + + // Open a connection to the server (need to do this each time, otherwise housekeeping won't delete files) + SocketStreamTLS *pConn = new SocketStreamTLS; + std::auto_ptr apConn(pConn); + pConn->Open(context, Socket::TypeINET, "localhost", + BOX_PORT_BBSTORED_TEST); + BackupProtocolClient protocol(apConn); + { + std::auto_ptr serverVersion(protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION)); + TEST_THAT(serverVersion->GetVersion() == BACKUP_STORE_SERVER_VERSION); + protocol.QueryLogin(0x01234567, 0); + } + + // Pull all the files down, and check that they match the files on disc + for(unsigned int f = 0; f < NUMBER_FILES; ++f) + { + ::printf("r=%d, f=%d\n", deleteIndex, f); + + // Might have been deleted + if(test_files[f].HasBeenDeleted) + { + continue; + } + + // Filenames + char filename[64], filename_fetched[64]; + ::sprintf(filename, "testfiles/%d.test", f); + ::sprintf(filename_fetched, "testfiles/%d.test.fetched", f); + ::unlink(filename_fetched); + + // Fetch the file + { + std::auto_ptr getobj(protocol.QueryGetFile( + BackupProtocolListDirectory::RootDirectory, + test_files[f].IDOnServer)); + TEST_THAT(getobj->GetObjectID() == test_files[f].IDOnServer); + // BLOCK + { + // Get stream + std::auto_ptr filestream(protocol.ReceiveStream()); + // Get and decode + BackupStoreFile::DecodeFile(*filestream, filename_fetched, SHORT_TIMEOUT); + } + } + // Test for identicalness + TEST_THAT(files_identical(filename_fetched, filename)); + + // Download the index, and check it looks OK + { + std::auto_ptr getblockindex(protocol.QueryGetBlockIndexByID(test_files[f].IDOnServer)); + TEST_THAT(getblockindex->GetObjectID() == test_files[f].IDOnServer); + std::auto_ptr blockIndexStream(protocol.ReceiveStream()); + TEST_THAT(BackupStoreFile::CompareFileContentsAgainstBlockIndex(filename, *blockIndexStream, SHORT_TIMEOUT)); + } + } + + // Close the connection + protocol.QueryFinished(); + + // Mark one of the elements as deleted + if(test_file_remove_order[deleteIndex] == -1) + { + // Nothing left to do + break; + } + int todel = test_file_remove_order[deleteIndex++]; + + // Modify the entry + BackupStoreDirectory::Entry *pentry = dir.FindEntryByID(test_files[todel].IDOnServer); + TEST_THAT(pentry != 0); + pentry->AddFlags(BackupStoreDirectory::Entry::Flags_RemoveASAP); + // Save it back + { + RaidFileWrite writedir(0, "backup/01234567/o01"); + writedir.Open(true /* overwrite */); + dir.WriteToStream(writedir); + writedir.Commit(true); + } + +#ifdef WIN32 + // Cannot signal bbstored to do housekeeping now, + // so just wait until we're sure it's done + wait_for_operation(12, "housekeeping to run"); +#else + // Send the server a restart signal, so it does + // housekeeping immediately, and wait for it to happen + // Wait for old connections to terminate + ::sleep(1); + ::kill(pid, SIGHUP); +#endif + + // Get the revision number of the info file + int64_t first_revision = 0; + RaidFileRead::FileExists(0, "backup/01234567/o01", &first_revision); + for(int l = 0; l < 32; ++l) + { + // Sleep a while, and print a dot + ::sleep(1); + ::printf("."); + ::fflush(stdout); + + // Early end? + if(l > 2) + { + int64_t revid = 0; + RaidFileRead::FileExists(0, "backup/01234567/o01", &revid); + if(revid != first_revision) + { + break; + } + } + } + ::printf("\n"); + + // Flag for test + test_files[todel].HasBeenDeleted = true; + // Update dependency info + int z = todel; + while(z > 0 && test_files[z].HasBeenDeleted && test_files[z].DepOlder != 0) + { + --z; + } + if(z >= 0) test_files[z].DepNewer = test_files[todel].DepNewer; + z = todel; + while(z < (int)NUMBER_FILES && test_files[z].HasBeenDeleted && test_files[z].DepNewer != 0) + { + ++z; + } + if(z < (int)NUMBER_FILES) test_files[z].DepOlder = test_files[todel].DepOlder; + } + + // Kill store server + TEST_THAT(KillServer(pid)); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("bbstored.memleaks"); + #endif + } + + ::free(buffer); + + return 0; +} diff --git a/test/backupstorepatch/testextra b/test/backupstorepatch/testextra new file mode 100644 index 00000000..cacda911 --- /dev/null +++ b/test/backupstorepatch/testextra @@ -0,0 +1,6 @@ +rm -rf testfiles +mkdir testfiles +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +cp ../../../test/backupstore/testfiles/*.* testfiles/ diff --git a/test/basicserver/Makefile.extra b/test/basicserver/Makefile.extra new file mode 100644 index 00000000..e6a4675e --- /dev/null +++ b/test/basicserver/Makefile.extra @@ -0,0 +1,12 @@ + +MAKEPROTOCOL = ../../lib/server/makeprotocol.pl + +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) + diff --git a/test/basicserver/TestCommands.cpp b/test/basicserver/TestCommands.cpp new file mode 100644 index 00000000..bdbdffeb --- /dev/null +++ b/test/basicserver/TestCommands.cpp @@ -0,0 +1,108 @@ + +#include "Box.h" + +#ifdef HAVE_SYSLOG_H +#include +#endif + +#include "autogen_TestProtocol.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + + +std::auto_ptr TestProtocolReplyable::HandleException(BoxException& e) const +{ + throw; +} + +std::auto_ptr TestProtocolHello::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + if(mNumber32 != 41 || mNumber16 != 87 || mNumber8 != 11 || mText != "pingu") + { + return std::auto_ptr(new TestProtocolError(0, 0)); + } + return std::auto_ptr(new TestProtocolHello(12,89,22,std::string("Hello world!"))); +} + +std::auto_ptr TestProtocolLists::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + return std::auto_ptr(new TestProtocolListsReply(mLotsOfText.size())); +} + +std::auto_ptr TestProtocolQuit::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + return std::auto_ptr(new TestProtocolQuit); +} + +std::auto_ptr TestProtocolSimple::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + return std::auto_ptr(new TestProtocolSimpleReply(mValue+1)); +} + +class UncertainBufferStream : public CollectInBufferStream +{ +public: + // make the collect in buffer stream pretend not to know how many bytes are left + pos_type BytesLeftToRead() + { + return IOStream::SizeOfStreamUnknown; + } +}; + +std::auto_ptr TestProtocolGetStream::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + // make a new stream object + std::auto_ptr apStream( + mUncertainSize?(new UncertainBufferStream):(new CollectInBufferStream)); + + // Data. + int values[24273]; + int v = mStartingValue; + for(int l = 0; l < 3; ++l) + { + for(int x = 0; x < 24273; ++x) + { + values[x] = v++; + } + apStream->Write(values, sizeof(values)); + } + + // Finished + apStream->SetForReading(); + + // Get it to be sent + rProtocol.SendStreamAfterCommand((std::auto_ptr)apStream); + + return std::auto_ptr(new TestProtocolGetStream(mStartingValue, mUncertainSize)); +} + +std::auto_ptr TestProtocolSendStream::DoCommand( + TestProtocolReplyable &rProtocol, TestContext &rContext, + IOStream& rDataStream) const +{ + if(mValue != 0x73654353298ffLL) + { + return std::auto_ptr(new TestProtocolError(0, 0)); + } + + // Get a stream + bool uncertain = (rDataStream.BytesLeftToRead() == IOStream::SizeOfStreamUnknown); + + // Count how many bytes in it + int bytes = 0; + char buffer[125]; + while(rDataStream.StreamDataLeft()) + { + bytes += rDataStream.Read(buffer, sizeof(buffer)); + } + + // tell the caller how many bytes there were + return std::auto_ptr(new TestProtocolGetStream(bytes, uncertain)); +} + +std::auto_ptr TestProtocolString::DoCommand(TestProtocolReplyable &rProtocol, TestContext &rContext) const +{ + return std::auto_ptr(new TestProtocolString(mTest)); +} + diff --git a/test/basicserver/TestContext.cpp b/test/basicserver/TestContext.cpp new file mode 100644 index 00000000..4b92766e --- /dev/null +++ b/test/basicserver/TestContext.cpp @@ -0,0 +1,16 @@ + +#include "Box.h" +#include "Test.h" +#include "TestContext.h" + +#include "MemLeakFindOn.h" + +TestContext::TestContext() +{ +} + +TestContext::~TestContext() +{ +} + + diff --git a/test/basicserver/TestContext.h b/test/basicserver/TestContext.h new file mode 100644 index 00000000..d0c28349 --- /dev/null +++ b/test/basicserver/TestContext.h @@ -0,0 +1,7 @@ + +class TestContext +{ +public: + TestContext(); + ~TestContext(); +}; diff --git a/test/basicserver/testbasicserver.cpp b/test/basicserver/testbasicserver.cpp new file mode 100644 index 00000000..6a1e15ad --- /dev/null +++ b/test/basicserver/testbasicserver.cpp @@ -0,0 +1,777 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbasicserver.cpp +// Purpose: Test basic server classes +// Created: 2003/07/29 +// +// -------------------------------------------------------------------------- + + +#include "Box.h" + +#include +#include + +#include + +#include "Test.h" +#include "Daemon.h" +#include "Configuration.h" +#include "ServerStream.h" +#include "SocketStream.h" +#include "IOStreamGetLine.h" +#include "ServerTLS.h" +#include "CollectInBufferStream.h" + +#include "TestContext.h" +#include "autogen_TestProtocol.h" +#include "ServerControl.h" + +#include "MemLeakFindOn.h" + +#define SERVER_LISTEN_PORT 2003 + +// in ms +#define COMMS_READ_TIMEOUT 4 +#define COMMS_SERVER_WAIT_BEFORE_REPLYING 40 +#define SHORT_TIMEOUT 5000 + +class basicdaemon : public Daemon +{ +public: +basicdaemon() {}; +~basicdaemon() {} +virtual void Run(); +}; + +void basicdaemon::Run() +{ + // Write a file to check it's done... + const Configuration &c(GetConfiguration()); + + FILE *f = fopen(c.GetKeyValue("TestFile").c_str(), "w"); + fclose(f); + + while(!StopRun()) + { + ::sleep(10); + } +} + +void testservers_pause_before_reply() +{ +#ifdef WIN32 + Sleep(COMMS_SERVER_WAIT_BEFORE_REPLYING); +#else + struct timespec t; + t.tv_sec = 0; + t.tv_nsec = COMMS_SERVER_WAIT_BEFORE_REPLYING * 1000 * 1000; // convert to ns + ::nanosleep(&t, NULL); +#endif +} + +#define LARGE_DATA_BLOCK_SIZE 19870 +#define LARGE_DATA_SIZE (LARGE_DATA_BLOCK_SIZE*1000) + +void testservers_connection(SocketStream &rStream) +{ + IOStreamGetLine getline(rStream); + + if(typeid(rStream) == typeid(SocketStreamTLS)) + { + // need to wait for some data before sending stuff, otherwise timeout test doesn't work + std::string line; + while(!getline.GetLine(line)) + ; + SocketStreamTLS &rtls = (SocketStreamTLS&)rStream; + std::string line1("CONNECTED:"); + line1 += rtls.GetPeerCommonName(); + line1 += '\n'; + testservers_pause_before_reply(); + rStream.Write(line1.c_str(), line1.size()); + } + + while(!getline.IsEOF()) + { + std::string line; + while(!getline.GetLine(line)) + ; + if(line == "QUIT") + { + break; + } + if(line == "LARGEDATA") + { + { + // Send lots of data + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + rStream.Write(data, sizeof(data), SHORT_TIMEOUT); + } + } + { + // Receive lots of data + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && + (r = rStream.Read(buf, sizeof(buf), SHORT_TIMEOUT)) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + if (total != LARGE_DATA_SIZE) + { + BOX_ERROR("Expected " << + LARGE_DATA_SIZE << " bytes " << + "but was " << total); + return; + } + } + { + // Send lots of data again + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + rStream.Write(data, sizeof(data), SHORT_TIMEOUT); + } + } + + // next! + continue; + } + std::string backwards; + for(std::string::const_reverse_iterator i(line.end()); i != std::string::const_reverse_iterator(line.begin()); ++i) + { + backwards += (*i); + } + backwards += '\n'; + testservers_pause_before_reply(); + rStream.Write(backwards.c_str(), backwards.size()); + } + rStream.Shutdown(); + rStream.Close(); +} + + + +class testserver : public ServerStream +{ +public: + testserver() {} + ~testserver() {} + + void Connection(std::auto_ptr apStream); + + virtual const char *DaemonName() const + { + return "test-srv2"; + } + const ConfigurationVerify *GetConfigVerify() const; + +}; + +const ConfigurationVerify *testserver::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + SERVERSTREAM_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", /* mName */ + 0, /* mpSubConfigurations */ + verifyserverkeys, /* mpKeys */ + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", /* mName */ + verifyserver, /* mpSubConfigurations */ + 0, /* mpKeys */ + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +void testserver::Connection(std::auto_ptr apStream) +{ + testservers_connection(*apStream); +} + +class testProtocolServer : public testserver +{ +public: + testProtocolServer() {} + ~testProtocolServer() {} + + void Connection(std::auto_ptr apStream); + + virtual const char *DaemonName() const + { + return "test-srv4"; + } +}; + +void testProtocolServer::Connection(std::auto_ptr apStream) +{ + TestProtocolServer server(apStream); + TestContext context; + server.DoServer(context); +} + + +class testTLSserver : public ServerTLS +{ +public: + testTLSserver() {} + ~testTLSserver() {} + + void Connection(std::auto_ptr apStream); + + virtual const char *DaemonName() const + { + return "test-srv3"; + } + const ConfigurationVerify *GetConfigVerify() const; + +}; + +const ConfigurationVerify *testTLSserver::GetConfigVerify() const +{ + static ConfigurationVerifyKey verifyserverkeys[] = + { + SERVERTLS_VERIFY_SERVER_KEYS(ConfigurationVerifyKey::NoDefaultValue) // no default listen addresses + }; + + static ConfigurationVerify verifyserver[] = + { + { + "Server", + 0, + verifyserverkeys, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } + }; + + static ConfigurationVerify verify = + { + "root", + verifyserver, + 0, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + }; + + return &verify; +} + +void testTLSserver::Connection(std::auto_ptr apStream) +{ + testservers_connection(*apStream); +} + + +void Srv2TestConversations(const std::vector &conns) +{ + const static char *tosend[] = { + "test 1\n", "carrots\n", "pineapples\n", "booo!\n", 0 + }; + const static char *recieve[] = { + "1 tset", "storrac", "selppaenip", "!ooob", 0 + }; + + IOStreamGetLine **getline = new IOStreamGetLine*[conns.size()]; + for(unsigned int c = 0; c < conns.size(); ++c) + { + getline[c] = new IOStreamGetLine(*conns[c]); + + bool hadTimeout = false; + if(typeid(*conns[c]) == typeid(SocketStreamTLS)) + { + SocketStreamTLS *ptls = (SocketStreamTLS *)conns[c]; + printf("Connected to '%s'\n", ptls->GetPeerCommonName().c_str()); + + // Send some data, any data, to get the first response. + conns[c]->Write("Hello\n", 6); + + std::string line1; + while(!getline[c]->GetLine(line1, false, COMMS_READ_TIMEOUT)) + hadTimeout = true; + TEST_THAT(line1 == "CONNECTED:CLIENT"); + TEST_THAT(hadTimeout) + } + } + + for(int q = 0; tosend[q] != 0; ++q) + { + for(unsigned int c = 0; c < conns.size(); ++c) + { + //printf("%d: %s", c, tosend[q]); + conns[c]->Write(tosend[q], strlen(tosend[q])); + std::string rep; + bool hadTimeout = false; + while(!getline[c]->GetLine(rep, false, COMMS_READ_TIMEOUT)) + hadTimeout = true; + TEST_EQUAL_LINE(rep, recieve[q], "Line " << q); + TEST_LINE(hadTimeout, "Line " << q) + } + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + conns[c]->Write("LARGEDATA\n", 10, SHORT_TIMEOUT); + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Receive lots of data + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && + (r = conns[c]->Read(buf, sizeof(buf), SHORT_TIMEOUT)) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Send lots of data + char data[LARGE_DATA_BLOCK_SIZE]; + for(unsigned int y = 0; y < sizeof(data); y++) + { + data[y] = y & 0xff; + } + for(int s = 0; s < (LARGE_DATA_SIZE / LARGE_DATA_BLOCK_SIZE); ++s) + { + conns[c]->Write(data, sizeof(data), SHORT_TIMEOUT); + } + } + for(unsigned int c = 0; c < conns.size(); ++c) + { + // Receive lots of data again + char buf[1024]; + int total = 0; + int r = 0; + while(total < LARGE_DATA_SIZE && + (r = conns[c]->Read(buf, sizeof(buf), SHORT_TIMEOUT)) != 0) + { + total += r; + } + TEST_THAT(total == LARGE_DATA_SIZE); + } + + for(unsigned int c = 0; c < conns.size(); ++c) + { + conns[c]->Write("QUIT\n", 5); + } + + for(unsigned int c = 0; c < conns.size(); ++c) + { + if ( getline[c] ) delete getline[c]; + getline[c] = 0; + } + if ( getline ) delete [] getline; + getline = 0; +} + +void TestStreamReceive(TestProtocolClient &protocol, int value, bool uncertainstream) +{ + std::auto_ptr reply(protocol.QueryGetStream(value, uncertainstream)); + TEST_THAT(reply->GetStartingValue() == value); + + // Get a stream + std::auto_ptr stream(protocol.ReceiveStream()); + + // check uncertainty + TEST_THAT(uncertainstream == (stream->BytesLeftToRead() == IOStream::SizeOfStreamUnknown)); + + printf("stream is %s\n", uncertainstream?"uncertain size":"fixed size"); + + // Then check the contents + int values[998]; + int v = value; + int count = 0; + int bytesleft = 0; + int bytessofar = 0; + while(stream->StreamDataLeft()) + { + // Read some data + int bytes = stream->Read(((char*)values) + bytesleft, + sizeof(values) - bytesleft, SHORT_TIMEOUT); + bytessofar += bytes; + bytes += bytesleft; + int n = bytes / 4; + //printf("read %d, n = %d, so far = %d\n", bytes, n, bytessofar); + for(int t = 0; t < n; ++t) + { + if(values[t] != v) printf("%d, %d, %d\n", t, values[t], v); + TEST_THAT(values[t] == v++); + } + count += n; + bytesleft = bytes - (n*4); + if(bytesleft) ::memmove(values, ((char*)values) + bytes - bytesleft, bytesleft); + } + + TEST_THAT(bytesleft == 0); + TEST_THAT(count == (24273*3)); // over 64 k of data, definately +} + + +int test(int argc, const char *argv[]) +{ + // Server launching stuff + if(argc >= 2) + { + // this is a quick hack to allow passing some options + // to the daemon + + const char* mode = argv[1]; + + if (test_args.length() > 0) + { + argv[1] = test_args.c_str(); + } + else + { + argc--; + argv++; + } + + if(strcmp(mode, "srv1") == 0) + { + // Run very basic daemon + basicdaemon daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv2") == 0) + { + // Run daemon which accepts connections + testserver daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv3") == 0) + { + testTLSserver daemon; + return daemon.Main("doesnotexist", argc, argv); + } + else if(strcmp(mode, "srv4") == 0) + { + testProtocolServer daemon; + return daemon.Main("doesnotexist", argc, argv); + } + } + + //printf("SKIPPING TESTS------------------------\n"); + //goto protocolserver; + + // Launch a basic server + { + std::string cmd = TEST_EXECUTABLE " --test-daemon-args="; + cmd += test_args; + cmd += " srv1 testfiles/srv1.conf"; + int pid = LaunchServer(cmd, "testfiles/srv1.pid"); + + TEST_THAT(pid != -1 && pid != 0); + if(pid > 0) + { + // Check that it's written the expected file + TEST_THAT(TestFileExists("testfiles" + DIRECTORY_SEPARATOR "srv1.test1")); + TEST_THAT(ServerIsAlive(pid)); + + // Move the config file over + #ifdef WIN32 + TEST_THAT(::unlink("testfiles" + DIRECTORY_SEPARATOR "srv1.conf") != -1); + #endif + + TEST_THAT(::rename( + "testfiles" DIRECTORY_SEPARATOR "srv1b.conf", + "testfiles" DIRECTORY_SEPARATOR "srv1.conf") + != -1); + + #ifndef WIN32 + // Get it to reread the config file + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + // Check that new file exists + TEST_THAT(TestFileExists("testfiles" + DIRECTORY_SEPARATOR "srv1.test2")); + #endif // !WIN32 + + // Kill it off + TEST_THAT(KillServer(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks( + "generic-daemon.memleaks"); + #endif // !WIN32 + } + } + + // Launch a test forking server + { + std::string cmd = TEST_EXECUTABLE " --test-daemon-args="; + cmd += test_args; + cmd += " srv2 testfiles/srv2.conf"; + int pid = LaunchServer(cmd, "testfiles/srv2.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + // Will it restart? + TEST_THAT(ServerIsAlive(pid)); + + #ifndef WIN32 + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif // !WIN32 + + // Make some connections + { + SocketStream conn1; + conn1.Open(Socket::TypeINET, "localhost", 2003); + + #ifndef WIN32 + SocketStream conn2; + conn2.Open(Socket::TypeUNIX, + "testfiles/srv2.sock"); + SocketStream conn3; + conn3.Open(Socket::TypeINET, + "localhost", 2003); + #endif // !WIN32 + + // Quick check that reconnections fail + TEST_CHECK_THROWS(conn1.Open(Socket::TypeUNIX, + "testfiles/srv2.sock");, + ServerException, SocketAlreadyOpen); + + // Stuff some data around + std::vector conns; + conns.push_back(&conn1); + + #ifndef WIN32 + conns.push_back(&conn2); + conns.push_back(&conn3); + #endif // !WIN32 + + Srv2TestConversations(conns); + // Implicit close + } + + #ifndef WIN32 + // HUP again + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif // !WIN32 + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv2.memleaks"); + #endif // !WIN32 + } + } + + // Launch a test SSL server + { + std::string cmd = TEST_EXECUTABLE " --test-daemon-args="; + cmd += test_args; + cmd += " srv3 testfiles/srv3.conf"; + int pid = LaunchServer(cmd, "testfiles/srv3.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + // Will it restart? + TEST_THAT(ServerIsAlive(pid)); + + #ifndef WIN32 + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif + + // Make some connections + { + // SSL library + SSLLib::Initialise(); + + // Context first + TLSContext context; + context.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + SocketStreamTLS conn1; + conn1.Open(context, Socket::TypeINET, "localhost", 2003); + #ifndef WIN32 + SocketStreamTLS conn2; + conn2.Open(context, Socket::TypeUNIX, + "testfiles/srv3.sock"); + SocketStreamTLS conn3; + conn3.Open(context, Socket::TypeINET, + "localhost", 2003); + #endif + + // Quick check that reconnections fail + TEST_CHECK_THROWS(conn1.Open(context, + Socket::TypeUNIX, + "testfiles/srv3.sock");, + ServerException, SocketAlreadyOpen); + + // Stuff some data around + std::vector conns; + conns.push_back(&conn1); + + #ifndef WIN32 + conns.push_back(&conn2); + conns.push_back(&conn3); + #endif + + Srv2TestConversations(conns); + // Implicit close + } + + #ifndef WIN32 + // HUP again + TEST_THAT(HUPServer(pid)); + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + #endif + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv3.memleaks"); + #endif + } + } + +//protocolserver: + // Launch a test protocol handling server + { + std::string cmd = TEST_EXECUTABLE " --test-daemon-args="; + cmd += test_args; + cmd += " srv4 testfiles/srv4.conf"; + int pid = LaunchServer(cmd, "testfiles/srv4.pid"); + + TEST_THAT(pid != -1 && pid != 0); + + if(pid > 0) + { + ::sleep(1); + TEST_THAT(ServerIsAlive(pid)); + + // Open a connection to it + std::auto_ptr apConn(new SocketStream); + #ifdef WIN32 + apConn->Open(Socket::TypeINET, "localhost", 2003); + #else + apConn->Open(Socket::TypeUNIX, "testfiles/srv4.sock"); + #endif + + // Create a protocol + TestProtocolClient protocol(apConn); + + // Simple query + { + std::auto_ptr reply(protocol.QuerySimple(41)); + TEST_THAT(reply->GetValuePlusOne() == 42); + } + { + std::auto_ptr reply(protocol.QuerySimple(809)); + TEST_THAT(reply->GetValuePlusOne() == 810); + } + + // Streams, twice, both uncertain and certain sizes + TestStreamReceive(protocol, 374, false); + TestStreamReceive(protocol, 23983, true); + TestStreamReceive(protocol, 12098, false); + TestStreamReceive(protocol, 4342, true); + + // Try to send a stream + { + std::auto_ptr + s(new CollectInBufferStream()); + char buf[1663]; + s->Write(buf, sizeof(buf)); + s->SetForReading(); + std::auto_ptr reply(protocol.QuerySendStream(0x73654353298ffLL, + (std::auto_ptr)s)); + TEST_THAT(reply->GetStartingValue() == sizeof(buf)); + } + + // Lots of simple queries + for(int q = 0; q < 514; q++) + { + std::auto_ptr reply(protocol.QuerySimple(q)); + TEST_THAT(reply->GetValuePlusOne() == (q+1)); + } + // Send a list of strings to it + { + std::vector strings; + strings.push_back(std::string("test1")); + strings.push_back(std::string("test2")); + strings.push_back(std::string("test3")); + std::auto_ptr reply(protocol.QueryLists(strings)); + TEST_THAT(reply->GetNumberOfStrings() == 3); + } + + // And another + { + std::auto_ptr reply(protocol.QueryHello(41,87,11,std::string("pingu"))); + TEST_THAT(reply->GetNumber32() == 12); + TEST_THAT(reply->GetNumber16() == 89); + TEST_THAT(reply->GetNumber8() == 22); + TEST_THAT(reply->GetText() == "Hello world!"); + } + + // Quit query to finish + protocol.QueryQuit(); + + // Kill it + TEST_THAT(KillServer(pid)); + ::sleep(1); + TEST_THAT(!ServerIsAlive(pid)); + + #ifndef WIN32 + TestRemoteProcessMemLeaks("test-srv4.memleaks"); + #endif + } + } + + return 0; +} + diff --git a/test/basicserver/testfiles/clientCerts.pem b/test/basicserver/testfiles/clientCerts.pem new file mode 100644 index 00000000..81d4c5cc --- /dev/null +++ b/test/basicserver/testfiles/clientCerts.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICOTCCAaICAQUwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDQz +WhcNMzEwMTIyMTYyNDQzWjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjEPMA0GA1UEAxMGQ0xJRU5UMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Zcr+TRrHk +Q8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65y417qk5z +NjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQABMA0GCSqG +SIb3DQEBBQUAA4GBACYkSYlrKNv1v6lrES4j68S8u8SNlnSM+Z4pTHF/7K7SQeIn +SKVV8EI8CLR5jIsQRRHKB9rYgYS4kB8SFbPyrsH8VKngjIUcjmTKLq9zpAt2zDNo +m+y5SMXsaJF6Xbtbz+MSxXZZ6YBBuseY+Wkpz4ZGSVlQrHxjsuYdBFHIguM3 +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/clientPrivKey.pem b/test/basicserver/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..a4797b66 --- /dev/null +++ b/test/basicserver/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC7AJDQJdGHi4HO7VXZJdi/3C8rQx1uTxMO6QHBFep0wQZ6I37Z +cr+TRrHkQ8CelymIBx2ZfQXMLKsoB8FScIp0zIT/drK0AghuWE5UPU6dntPlrA65 +y417qk5zNjiOy6coWl+7ktZ0ItCuy7VHWrTmHRbNZeXKub7fjuccDJdiywIDAQAB +AoGAF92enbH158KaMnp/tlLqMrI7It5R5z4YRJLgMnBFl9j6pqPZEI9ge79N/L/Y +2WSZXE7sLCaUktYwkc9LkOXkBYQI7EIOonLdmSsNCMbSBVbeczdM77dBscuCTKva +nvre/2+hlmuWBNINqXlprBkvd5YF4Q/yeXzoXPuMIQ0tROECQQDqifOZOfCle8uA +CgdHT9pO638PwrrldMHmZSK3gUmHmFe7ziGpNGCfKZ+wkSIvDg9INQvEXvQfLZiV +n4J78IOHAkEAzB0SoUU0cL+wK3OQTTOlx4cgxaxgtsuvccIhqTh4Jp1Aj9iMKiPW +yXvbGhDBTZP2IL5HoqSLc3SxfXgvn6O/nQJBALgJMYWdalBf2GoK9HUnmpTsw1I5 +qe/c8z13RIubvnfQuZ8be1xLRjn+LlkdOSaVMLanMSmQnJxOafmWJYxdSMcCQFBc +5ffe8n2tyyPgdSEgQ5YiatHJQ67U1Te50lz44b16TnAUN2NkBu3/OM2zaRgtOEu9 +/yBXHpyPhk47Iqz84LUCQQCIDIKluoughLVjJS2eD28UJHM9Z+OvmyIE0fF0Q0vi +E+Rn/+iWCoEJYa7WP5AEo/aeVXiCeHONXGF1AI8a8gb5 +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/clientReq.pem b/test/basicserver/testfiles/clientReq.pem new file mode 100644 index 00000000..14e2c6df --- /dev/null +++ b/test/basicserver/testfiles/clientReq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDzANBgNVBAMTBkNMSUVOVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +uwCQ0CXRh4uBzu1V2SXYv9wvK0Mdbk8TDukBwRXqdMEGeiN+2XK/k0ax5EPAnpcp +iAcdmX0FzCyrKAfBUnCKdMyE/3aytAIIblhOVD1OnZ7T5awOucuNe6pOczY4jsun +KFpfu5LWdCLQrsu1R1q05h0WzWXlyrm+347nHAyXYssCAwEAAaAAMA0GCSqGSIb3 +DQEBBQUAA4GBAKV3H/yWrYep6yfEDQp61zn60tEnJOS5LVbuV7ivNjAue0/09wBT +PGzTblwx116AT9GbTcbERK/ll549+tziTLT9NUT12ZcvaRezYP2PpaD8fiDKHs3D +vSwpFoihLmUnDeMWE9Vbt+b0Fl/mdsH6sm3Mo0COG/DkolOVsydOj2Hp +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/clientTrustedCAs.pem b/test/basicserver/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/clientTrustedCAs.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/key-creation.txt b/test/basicserver/testfiles/key-creation.txt new file mode 100644 index 00000000..51f4eb77 --- /dev/null +++ b/test/basicserver/testfiles/key-creation.txt @@ -0,0 +1,83 @@ +$ openssl genrsa -out rootkey.pem 1024 + +$ openssl req -new -key rootkey.pem -sha1 -out rootreq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:ROOT +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ openssl x509 -req -in rootreq.pem -sha1 -extensions v3_ca -signkey rootkey.pem -out rootcert.pem -days 10000 +Signature ok +subject=/C=GB/ST=London/L=London/O=Test/OU=basic server/CN=ROOT +Getting Private key + +$ cp rootcert.pem serverTrustedCAs.pem +$ cp rootcert.pem clientTrustedCAs.pem + +$ openssl genrsa -out clientPrivKey.pem 1024 +$ openssl req -new -key clientPrivKey.pem -sha1 -out clientReq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:CLIENT +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ cat rootcert.pem rootkey.pem > root.pem + +$ echo 01 > root.srl + +$ openssl x509 -req -in clientReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out clientCerts.pem -days 10000 + +$ openssl genrsa -out serverPrivKey.pem 1024 +$ openssl req -new -key serverPrivKey.pem -sha1 -out serverReq.pem +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) []:GB +State or Province Name (full name) []:London +Locality Name (eg, city) []:London +Organization Name (eg, company) []:Test +Organizational Unit Name (eg, section) []:basic server +Common Name (eg, fully qualified host name) []:SERVER +Email Address []: + +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []: +An optional company name []: + +$ openssl x509 -req -in serverReq.pem -sha1 -extensions usr_crt -CA root.pem -CAkey root.pem -out serverCerts.pem -days 10000 + diff --git a/test/basicserver/testfiles/root.pem b/test/basicserver/testfiles/root.pem new file mode 100644 index 00000000..020c25c3 --- /dev/null +++ b/test/basicserver/testfiles/root.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t +gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI +UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB +AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW +g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm +ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja +pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw +iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb +Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc +lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x +fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS +8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS +GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/root.srl b/test/basicserver/testfiles/root.srl new file mode 100644 index 00000000..2c7456e3 --- /dev/null +++ b/test/basicserver/testfiles/root.srl @@ -0,0 +1 @@ +07 diff --git a/test/basicserver/testfiles/rootcert.pem b/test/basicserver/testfiles/rootcert.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/rootcert.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/rootkey.pem b/test/basicserver/testfiles/rootkey.pem new file mode 100644 index 00000000..4eb0f59d --- /dev/null +++ b/test/basicserver/testfiles/rootkey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDMYXJ+II0Ck9icrwDeGymdZ3Ah7ROmC32LpS3sQVchQd752O4t +gyTUP6d47rLdM2xsVjdRHeb9AD+DVrzDNRWrtPYxQMyjPhXweQz/jwxAyVQdyYEI +UkecYAvWDwpt9f6zF5Ipcq6udkxtE3JHCnR31EOW1Ccct+pyg8KBgj3kUwIDAQAB +AoGAFsGO3u4+5ReTGbb+kLxTgNwghxZ/hpBm9SJ6H4ES83gDHKyDsHuWoS9JNVTW +g3yTSOi8lgKPUoIxkC0bLVz+wYF0UWysOzhxbTqq43CdJM/HDuHbFGHs2MAKyvdm +ai7ccJMISDATN6XT7BLRBE5AAVqDhNllvmr92niZS51yzJECQQD4LQWdK9IUjsja +pYEeQKZENmC2pstAVYDyd3wuXaE8wiiTG86L/5zVRfEVpbD3rKPZVjcZKx+VZoIw +iyW9WntbAkEA0tL2fSeBC1V9Jcj8TOuMmEaoPMclJLUBDLJPxFmHCguwvcH8cgTb +Nr08FFqz62gZxudcrl5nISw3G0Rm3UGkaQJALRfhIUHJFjsre67+2wRcMaC/yfBc +lf/zQhs70SDqHyQYQ0KWMRHs6UOgHpLQqPARhXgI4uXXA0pw9WkTHmjGaQJBAJ1x +fTEkQmPjeS2xtnH/ayUBh3y0QJH0Nw9zTszVC1s+NcTQzSWdaNStZ+PPhRQlzzJS +8E0sJRqJ+bF8WNGdxxkCQQCTpEUpqsVykhucZ3GsCTlI4o3HNmYFarKDDEHgppLS +GKoUzTX2UMPJgeRITwacIh3lFhAily2PMFmlF+B7b5ep +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/rootreq.pem b/test/basicserver/testfiles/rootreq.pem new file mode 100644 index 00000000..6da1e428 --- /dev/null +++ b/test/basicserver/testfiles/rootreq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpDCCAQ0CAQAwZDELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDTALBgNVBAMTBFJPT1QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMxh +cn4gjQKT2JyvAN4bKZ1ncCHtE6YLfYulLexBVyFB3vnY7i2DJNQ/p3just0zbGxW +N1Ed5v0AP4NWvMM1Fau09jFAzKM+FfB5DP+PDEDJVB3JgQhSR5xgC9YPCm31/rMX +kilyrq52TG0TckcKdHfUQ5bUJxy36nKDwoGCPeRTAgMBAAGgADANBgkqhkiG9w0B +AQUFAAOBgQCmy4L/D/m1Q23y+WB1Ub2u1efl0sb7zMWNzHsD/IR1CXSvXmAfPpr2 +hpJQj118ccaTqkRhA8gwhktMTBuGH5KiOLHYXRlniKo3G0yr0+fHWnjclZ+m6Bg1 +9HjJZYqIWRMQ78+wTpLCxliX6yp0JxMdx/v6/7jx3BtXz8cyU8ANAw== +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/serverCerts.pem b/test/basicserver/testfiles/serverCerts.pem new file mode 100644 index 00000000..f61c554e --- /dev/null +++ b/test/basicserver/testfiles/serverCerts.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICOTCCAaICAQYwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNTA0 +WhcNMzEwMTIyMTYyNTA0WjBmMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjEPMA0GA1UEAxMGU0VSVkVSMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB +iQKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40XsmCEdHnDl +AjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrYaQkgME2O +mPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQABMA0GCSqG +SIb3DQEBBQUAA4GBALgh7u/7GZUMjzOPGuIenkdrsP0Gbst7wuXrLaMrAMlAaWMH +E9AgU/6Q9+2yFxisgAzRmyKydNP4E4YomsE8rbx08vGw/6Rc7L19/UsFJxeNC5Ue +6hziI9boB9LL5em4N8v+z4yhGvj2CrKzBxLNy8MYPi2S3KfQ69FdipvRQRp/ +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/serverPrivKey.pem b/test/basicserver/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..f2d73fd4 --- /dev/null +++ b/test/basicserver/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDLR7tFaeNvCdvC5nQgfYggFHxZM5NcsxJSYcF27GhPylHE40Xs +mCEdHnDlAjWs48GrYN7tfTa7/JEFM9s7sgF9Oxj+tshMTNZvx25uih8gHFCg0RrY +aQkgME2OmPuPtFcA/isTMCKO7D/aG2SapjY8/Xke0TseKO3jfP9LtxZz7QIDAQAB +AoGBAJSH7zAC9OmXXHoGhWeQEbzO+yT6aHxdY8/KGeBZUMasYB7qqZb8eYWbToYm +nS2cpVAh0gHZcfrdyuDwSQpPQIIA8gAPFHqR8T8VGrpChxgetYzkoPDapmcqKU4H +YobFVA1gypK1IM5z3Z5kargqGmmzRIxX8BwWr6FGmFPp2+NBAkEA7A17g4JewNtY +vtpM0NhIyw+7HN3ljf+pAvHM2pMw1Wk8TrbPJNQ20ZWnhGMdIvP0m25zna6pShL6 +0laf5EUWFQJBANx1SJ+Xb3P9IyrIlyMhrsYvAveezh6wimjAFFNYWmGEZ6uuHM5P +eBSc3P0x0LbFKlGQWomxMb3ULwpjEueX9HkCQDMf0GpxJ/h5CUV8njp1PX7NT2c3 +H+qbPo2mtQl564+tFSSvLzn4xE6sLPXdSYgycf3f9CZol721UqGPpV2ZIOkCQQCQ +trxxZmrW7LgFAZ+UhCvCFGISQcB0DNcOY+fzve+2S7/xxl1KYIgmn8HAws6K62oY +GHYWJKbOQVaPrvFd7TWhAkA8VQPjDSRkdg2fU5RDTRfOQBczgc8aHTiqAv/S2g47 +lpsw8CLitobBvi3e5XuBKNIbnjeoZMbHcBZ+RXAAZe/Q +-----END RSA PRIVATE KEY----- diff --git a/test/basicserver/testfiles/serverReq.pem b/test/basicserver/testfiles/serverReq.pem new file mode 100644 index 00000000..ce510fae --- /dev/null +++ b/test/basicserver/testfiles/serverReq.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBpjCCAQ8CAQAwZjELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYDVQQLEwxiYXNpYyBzZXJ2 +ZXIxDzANBgNVBAMTBlNFUlZFUjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +y0e7RWnjbwnbwuZ0IH2IIBR8WTOTXLMSUmHBduxoT8pRxONF7JghHR5w5QI1rOPB +q2De7X02u/yRBTPbO7IBfTsY/rbITEzWb8duboofIBxQoNEa2GkJIDBNjpj7j7RX +AP4rEzAijuw/2htkmqY2PP15HtE7Hijt43z/S7cWc+0CAwEAAaAAMA0GCSqGSIb3 +DQEBBQUAA4GBAGdUCS76aBzPw4zcU999r6gE7/F8/bYlT/tr2SEyKzF+vC0widZN +P3bg9IaNAWi84vw8WEB+j2wM3TPB5/kSKFpO2MxOHPERX+aOXh6JkN6a/ay5CDOT +r/wCERRkqY2gphU5m3/S0Gd7wLbH/neBgNsHUzbNwwQ+uqkF2NRGg0V/ +-----END CERTIFICATE REQUEST----- diff --git a/test/basicserver/testfiles/serverTrustedCAs.pem b/test/basicserver/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..d72b70e5 --- /dev/null +++ b/test/basicserver/testfiles/serverTrustedCAs.pem @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICNzCCAaACAQAwDQYJKoZIhvcNAQEFBQAwZDELMAkGA1UEBhMCR0IxDzANBgNV +BAgTBkxvbmRvbjEPMA0GA1UEBxMGTG9uZG9uMQ0wCwYDVQQKEwRUZXN0MRUwEwYD +VQQLEwxiYXNpYyBzZXJ2ZXIxDTALBgNVBAMTBFJPT1QwHhcNMDMwOTA2MTYyNDA4 +WhcNMzEwMTIyMTYyNDA4WjBkMQswCQYDVQQGEwJHQjEPMA0GA1UECBMGTG9uZG9u +MQ8wDQYDVQQHEwZMb25kb24xDTALBgNVBAoTBFRlc3QxFTATBgNVBAsTDGJhc2lj +IHNlcnZlcjENMAsGA1UEAxMEUk9PVDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC +gYEAzGFyfiCNApPYnK8A3hspnWdwIe0Tpgt9i6Ut7EFXIUHe+djuLYMk1D+neO6y +3TNsbFY3UR3m/QA/g1a8wzUVq7T2MUDMoz4V8HkM/48MQMlUHcmBCFJHnGAL1g8K +bfX+sxeSKXKurnZMbRNyRwp0d9RDltQnHLfqcoPCgYI95FMCAwEAATANBgkqhkiG +9w0BAQUFAAOBgQDIAhGUvs47CQeKiF6GDxFfSseCk6UWB1lFe154ZpexgMTp8Dgu +leJOvnZPmkywovIcxr2YZAM33e+3+rKDJEy9PJ9mGLsrZMHSi4v3U0e9bBDGCkKH +1sSrbEGIc02HIo8m3PGUdrNJ8GNJdcYUghtoZbe01sIqVmWWLA8XXDQmOQ== +-----END CERTIFICATE----- diff --git a/test/basicserver/testfiles/srv1.conf b/test/basicserver/testfiles/srv1.conf new file mode 100644 index 00000000..ee68704e --- /dev/null +++ b/test/basicserver/testfiles/srv1.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv1.pid +} + +TestFile = testfiles/srv1.test1 diff --git a/test/basicserver/testfiles/srv1b.conf b/test/basicserver/testfiles/srv1b.conf new file mode 100644 index 00000000..d6d6eebd --- /dev/null +++ b/test/basicserver/testfiles/srv1b.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv1.pid +} + +TestFile = testfiles/srv1.test2 diff --git a/test/basicserver/testfiles/srv2.conf b/test/basicserver/testfiles/srv2.conf new file mode 100644 index 00000000..ef1d7c49 --- /dev/null +++ b/test/basicserver/testfiles/srv2.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv2.pid + ListenAddresses = inet:localhost,unix:testfiles/srv2.sock +} + diff --git a/test/basicserver/testfiles/srv3.conf b/test/basicserver/testfiles/srv3.conf new file mode 100644 index 00000000..e2211553 --- /dev/null +++ b/test/basicserver/testfiles/srv3.conf @@ -0,0 +1,9 @@ +Server +{ + PidFile = testfiles/srv3.pid + ListenAddresses = inet:localhost,unix:testfiles/srv3.sock + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/basicserver/testfiles/srv4.conf b/test/basicserver/testfiles/srv4.conf new file mode 100644 index 00000000..f05dff75 --- /dev/null +++ b/test/basicserver/testfiles/srv4.conf @@ -0,0 +1,6 @@ +Server +{ + PidFile = testfiles/srv4.pid + ListenAddresses = unix:testfiles/srv4.sock,inet:localhost +} + diff --git a/test/basicserver/testprotocol.txt b/test/basicserver/testprotocol.txt new file mode 100644 index 00000000..5bca9f49 --- /dev/null +++ b/test/basicserver/testprotocol.txt @@ -0,0 +1,42 @@ +# test protocol file + +Name Test +IdentString Test-0.00 +ServerContextClass TestContext TestContext.h + +BEGIN_OBJECTS + +Error 0 IsError(Type,SubType) Reply + int32 Type + int32 SubType + +Hello 1 Command(Hello) Reply + int32 Number32 + int16 Number16 + int8 Number8 + string Text + +Lists 2 Command(ListsReply) + vector LotsOfText + +ListsReply 3 Reply + int32 NumberOfStrings + +Quit 4 Command(Quit) Reply EndsConversation + +Simple 5 Command(SimpleReply) + int32 Value + +SimpleReply 6 Reply + int32 ValuePlusOne + +GetStream 7 Command(GetStream) Reply + int32 StartingValue + bool UncertainSize + +SendStream 8 Command(GetStream) StreamWithCommand + int64 Value + +String 9 Command(String) Reply + string Test + diff --git a/test/bbackupd/testbbackupd.cpp b/test/bbackupd/testbbackupd.cpp new file mode 100644 index 00000000..dd50b862 --- /dev/null +++ b/test/bbackupd/testbbackupd.cpp @@ -0,0 +1,4115 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testbbackupd.cpp +// Purpose: test backup daemon (and associated client bits) +// Created: 2003/10/07 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +// do not include MinGW's dirent.h on Win32, +// as we override some of it in lib/win32. + +#ifndef WIN32 + #include +#endif + +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_WAIT_H + #include +#endif + +#ifdef HAVE_SYS_XATTR_H + #include + #include +#endif + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#ifdef WIN32 + #include +#endif + +#include + +#ifdef HAVE_SYSCALL + #include +#endif + +#include "BackupClientCryptoKeys.h" +#include "BackupClientContext.h" +#include "BackupClientFileAttributes.h" +#include "BackupClientInodeToIDMap.h" +#include "BackupClientRestore.h" +#include "BackupDaemon.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupProtocol.h" +#include "BackupQueries.h" +#include "BackupStoreAccounts.h" +#include "BackupStoreConstants.h" +#include "BackupStoreContext.h" +#include "BackupStoreDaemon.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreException.h" +#include "BackupStoreConfigVerify.h" +#include "BackupStoreFileEncodeStream.h" +#include "BoxPortsAndFiles.h" +#include "BoxTime.h" +#include "BoxTimeToUnix.h" +#include "CollectInBufferStream.h" +#include "CommonException.h" +#include "Configuration.h" +#include "FileModificationTime.h" +#include "FileStream.h" +#include "intercept.h" +#include "IOStreamGetLine.h" +#include "LocalProcessStream.h" +#include "MemBlockStream.h" +#include "RaidFileController.h" +#include "SSLLib.h" +#include "ServerControl.h" +#include "Socket.h" +#include "SocketStreamTLS.h" +#include "StoreTestUtils.h" +#include "TLSContext.h" +#include "Test.h" +#include "Timer.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +// ENOATTR may be defined in a separate header file which we may not have +#ifndef ENOATTR +#define ENOATTR ENODATA +#endif + +// two cycles and a bit +#define TIME_TO_WAIT_FOR_BACKUP_OPERATION 12 +#define SHORT_TIMEOUT 5000 +#define BACKUP_ERROR_DELAY_SHORTENED 10 +#define DEFAULT_BBACKUPD_CONFIG_FILE "testfiles/bbackupd.conf" + +void wait_for_backup_operation(const char* message) +{ + wait_for_operation(TIME_TO_WAIT_FOR_BACKUP_OPERATION, message); +} + +#ifdef HAVE_SYS_XATTR_H +bool readxattr_into_map(const char *filename, std::map &rOutput) +{ + rOutput.clear(); + + ssize_t xattrNamesBufferSize = llistxattr(filename, NULL, 0); + if(xattrNamesBufferSize < 0) + { +#if HAVE_DECL_ENOTSUP + if(errno == ENOTSUP) + { + // Pretend that it worked, leaving an empty map, so + // that the rest of the attribute comparison will + // proceed as normal. + return true; + } +#endif // HAVE_DECL_ENOTSUP + + return false; + } + else if(xattrNamesBufferSize > 0) + { + // There is some data there to look at + char *xattrNamesBuffer = (char*)malloc(xattrNamesBufferSize + 4); + if(xattrNamesBuffer == NULL) return false; + char *xattrDataBuffer = 0; + int xattrDataBufferSize = 0; + // note: will leak these buffers if a read error occurs. (test code, so doesn't matter) + + ssize_t ns = llistxattr(filename, xattrNamesBuffer, xattrNamesBufferSize); + if(ns < 0) + { + return false; + } + else if(ns > 0) + { + // Read all the attribute values + const char *xattrName = xattrNamesBuffer; + while(xattrName < (xattrNamesBuffer + ns)) + { + // Store size of name + int xattrNameSize = strlen(xattrName); + + bool ok = true; + + ssize_t dataSize = lgetxattr(filename, xattrName, NULL, 0); + if(dataSize < 0) + { + if(errno == ENOATTR) + { + // Deleted from under us + ok = false; + } + else + { + return false; + } + } + else if(dataSize == 0) + { + // something must have removed all the data from under us + ok = false; + } + else + { + // Make sure there's enough space in the buffer to get the attribute + if(xattrDataBuffer == 0) + { + xattrDataBuffer = (char*)malloc(dataSize + 4); + xattrDataBufferSize = dataSize + 4; + } + else if(xattrDataBufferSize < (dataSize + 4)) + { + char *resized = (char*)realloc(xattrDataBuffer, dataSize + 4); + if(resized == NULL) return false; + xattrDataBuffer = resized; + xattrDataBufferSize = dataSize + 4; + } + } + + // Read the data! + dataSize = 0; + if(ok) + { + dataSize = lgetxattr(filename, xattrName, xattrDataBuffer, + xattrDataBufferSize - 1 /*for terminator*/); + if(dataSize < 0) + { + if(errno == ENOATTR) + { + // Deleted from under us + ok = false; + } + else + { + return false; + } + } + else if(dataSize == 0) + { + // something must have deleted this from under us + ok = false; + } + else + { + // Terminate the data + xattrDataBuffer[dataSize] = '\0'; + } + // Got the data in the buffer + } + + // Store in map + if(ok) + { + rOutput[std::string(xattrName)] = std::string(xattrDataBuffer, dataSize); + } + + // Next attribute + xattrName += xattrNameSize + 1; + } + } + + if(xattrNamesBuffer != 0) ::free(xattrNamesBuffer); + if(xattrDataBuffer != 0) ::free(xattrDataBuffer); + } + + return true; +} + +static FILE *xattrTestDataHandle = 0; +bool write_xattr_test(const char *filename, const char *attrName, unsigned int length, bool *pNotSupported = 0) +{ + if(xattrTestDataHandle == 0) + { + xattrTestDataHandle = ::fopen("testfiles/test3.tgz", "rb"); // largest test file + } + if(xattrTestDataHandle == 0) + { + return false; + } + else + { + char data[1024]; + if(length > sizeof(data)) length = sizeof(data); + + if(::fread(data, length, 1, xattrTestDataHandle) != 1) + { + return false; + } + + if(::lsetxattr(filename, attrName, data, length, 0) != 0) + { + if(pNotSupported != 0) + { + *pNotSupported = (errno == ENOTSUP); + } + return false; + } + } + + return true; +} +void finish_with_write_xattr_test() +{ + if(xattrTestDataHandle != 0) + { + ::fclose(xattrTestDataHandle); + } +} +#endif // HAVE_SYS_XATTR_H + +bool attrmatch(const char *f1, const char *f2) +{ + EMU_STRUCT_STAT s1, s2; + TEST_THAT(EMU_LSTAT(f1, &s1) == 0); + TEST_THAT(EMU_LSTAT(f2, &s2) == 0); + +#ifdef HAVE_SYS_XATTR_H + { + std::map xattr1, xattr2; + if(!readxattr_into_map(f1, xattr1) + || !readxattr_into_map(f2, xattr2)) + { + return false; + } + if(!(xattr1 == xattr2)) + { + return false; + } + } +#endif // HAVE_SYS_XATTR_H + + // if link, just make sure other file is a link too, and that the link to names match + if((s1.st_mode & S_IFMT) == S_IFLNK) + { +#ifdef WIN32 + TEST_FAIL_WITH_MESSAGE("No symlinks on win32!") +#else + if((s2.st_mode & S_IFMT) != S_IFLNK) return false; + + char p1[PATH_MAX], p2[PATH_MAX]; + int p1l = ::readlink(f1, p1, PATH_MAX); + int p2l = ::readlink(f2, p2, PATH_MAX); + TEST_THAT(p1l != -1 && p2l != -1); + // terminate strings properly + p1[p1l] = '\0'; + p2[p2l] = '\0'; + return strcmp(p1, p2) == 0; +#endif + } + + // modification times + if(FileModificationTime(s1) != FileModificationTime(s2)) + { + return false; + } + + // compare the rest + return (s1.st_mode == s2.st_mode && s1.st_uid == s2.st_uid && s1.st_gid == s2.st_gid); +} + +bool unpack_files(const std::string& archive_file, + const std::string& destination_dir = "testfiles", + const std::string& tar_options = "") +{ + BOX_INFO("Unpacking test fixture archive into " << destination_dir + << ": " << archive_file); + +#ifdef _MSC_VER // No tar, use 7zip. + // 7za only extracts the tgz file to a tar file, which we have to extract in a + // separate step. + std::string cmd = std::string("7za x testfiles/") + archive_file + ".tgz -aos " + "-otestfiles >nul:"; + TEST_LINE_OR(::system(cmd.c_str()) == 0, cmd, return false); + + cmd = std::string("7za x testfiles/") + archive_file + ".tar -aos " + "-o" + destination_dir + " -x!.\\TestDir1\\symlink? -x!.\\test2 >nul:"; +#elif defined WIN32 // Cygwin + MinGW, we can use real tar. + std::string cmd("tar xz"); + cmd += tar_options + " -f testfiles/" + archive_file + ".tgz " + + "-C " + destination_dir; +#else // Unixish, but Solaris tar doesn't like decompressing gzip files. + std::string cmd("gzip -d < testfiles/"); + cmd += archive_file + ".tgz | ( cd " + destination_dir + " && tar xf" + + tar_options + " -)"; +#endif + + TEST_LINE_OR(::system(cmd.c_str()) == 0, cmd, return false); + return true; +} + +Daemon* spDaemon = NULL; + +bool configure_bbackupd(BackupDaemon& bbackupd, const std::string& config_file) +{ + // Stop bbackupd initialisation from changing the console logging level + // and the program name tag. + Logger& console(Logging::GetConsole()); + Logger::LevelGuard guard(console, console.GetLevel()); + Logging::Tagger(); + + std::vector args; + size_t last_arg_start = 0; + for (size_t pos = 0; pos <= bbackupd_args.size(); pos++) + { + char c; + + if (pos == bbackupd_args.size()) + { + c = ' '; // finish last argument + } + else + { + c = bbackupd_args[pos]; + } + + if (c == ' ') + { + if (last_arg_start < pos) + { + std::string last_arg = + bbackupd_args.substr(last_arg_start, + pos - last_arg_start); + args.push_back(last_arg); + } + + last_arg_start = pos + 1; + } + } + + MemoryBlockGuard argv_buffer(sizeof(const char*) * (args.size() + 1)); + const char **argv = argv_buffer; + argv_buffer[0] = "bbackupd"; + for (size_t i = 0; i < args.size(); i++) + { + argv_buffer[i + 1] = args[i].c_str(); + } + + TEST_EQUAL_LINE(0, bbackupd.ProcessOptions(args.size() + 1, argv), + "processing command-line options"); + + bbackupd.Configure(config_file); + bbackupd.InitCrypto(); + + return true; +} + +bool kill_running_daemons() +{ + bool success = true; + + if(FileExists("testfiles/bbstored.pid")) + { + TEST_THAT_OR(KillServer("testfiles/bbstored.pid", true), success = false); + } + + if(FileExists("testfiles/bbackupd.pid")) + { + TEST_THAT_OR(KillServer("testfiles/bbackupd.pid", true), success = false); + } + + return success; +} + +bool setup_test_bbackupd(BackupDaemon& bbackupd, bool do_unpack_files = true, + bool do_start_bbstored = true) +{ + Timers::Cleanup(false); // don't throw exception if not initialised + Timers::Init(); + + if (do_start_bbstored) + { + TEST_THAT_OR(StartServer(), FAIL); + } + + if (do_unpack_files) + { + TEST_THAT_OR(unpack_files("test_base"), FAIL); + // Older versions of GNU tar fail to set the timestamps on + // symlinks, which makes them appear too recent to be backed + // up immediately, causing test_bbackupd_uploads_files() for + // example to fail. So restore the timestamps manually. + // http://lists.gnu.org/archive/html/bug-tar/2009-08/msg00007.html + // http://git.savannah.gnu.org/cgit/tar.git/plain/NEWS?id=release_1_24 + #ifdef HAVE_UTIMENSAT + const struct timespec times[2] = { + {1065707200, 0}, + {1065707200, 0}, + }; + const char * filenames[] = { + "testfiles/TestDir1/symlink1", + "testfiles/TestDir1/symlink2", + "testfiles/TestDir1/symlink3", + NULL, + }; + for (int i = 0; filenames[i] != NULL; i++) + { + TEST_THAT_OR(utimensat(AT_FDCWD, filenames[i], + times, AT_SYMLINK_NOFOLLOW) == 0, + BOX_LOG_SYS_ERROR("Failed to change " + "timestamp on symlink: " << + filenames[i])); + } + #endif + } + + TEST_THAT_OR(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf"), + FAIL); + spDaemon = &bbackupd; + return true; +} + +//! Simplifies calling setUp() with the current function name in each test. +#define SETUP_TEST_BBACKUPD() \ + SETUP(); \ + TEST_THAT(bbackupd_pid == 0 || StopClient()); \ + TEST_THAT(bbstored_pid == 0 || StopServer()); \ + TEST_THAT(kill_running_daemons()); \ + TEST_THAT(create_account(10000, 20000)); + +#define SETUP_WITHOUT_FILES() \ + SETUP_TEST_BBACKUPD(); \ + BackupDaemon bbackupd; \ + TEST_THAT_OR(setup_test_bbackupd(bbackupd, false), FAIL); \ + TEST_THAT_OR(::mkdir("testfiles/TestDir1", 0755) == 0, FAIL); + +#define SETUP_WITH_BBSTORED() \ + SETUP_TEST_BBACKUPD(); \ + BackupDaemon bbackupd; \ + TEST_THAT_OR(setup_test_bbackupd(bbackupd), FAIL); + +#define TEARDOWN_TEST_BBACKUPD() \ + TEST_THAT(bbackupd_pid == 0 || StopClient()); \ + TEST_THAT(bbstored_pid == 0 || StopServer()); \ + TEST_THAT(kill_running_daemons()); \ + TEARDOWN(); + +bool test_basics() +{ + SETUP_TEST_BBACKUPD(); + TEST_THAT_OR(unpack_files("test_base"), FAIL); + + // Read attributes from files + BackupClientFileAttributes t1; + t1.ReadAttributes("testfiles/test1"); + TEST_THAT(!t1.IsSymLink()); + +#ifndef WIN32 + BackupClientFileAttributes t2; + t2.ReadAttributes("testfiles/test2"); + TEST_THAT(t2.IsSymLink()); + // Check that it's actually been encrypted (search for symlink name encoded in it) + void *te = ::memchr(t2.GetBuffer(), 't', t2.GetSize() - 3); + TEST_THAT(te == 0 || ::memcmp(te, "test", 4) != 0); +#endif + + BackupClientFileAttributes t3; + { + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); + TEST_CHECK_THROWS(t3.ReadAttributes("doesn't exist"), + CommonException, OSFileError); + } + + // Create some more files + FILE *f = fopen("testfiles/test1_n", "w"); + fclose(f); + f = fopen("testfiles/test2_n", "w"); + fclose(f); + + // Apply attributes to these new files + t1.WriteAttributes("testfiles/test1_n"); +#ifdef WIN32 + // We can't apply symlink attributes on Win32, so use a normal file's + // attributes instead. + t1.WriteAttributes("testfiles/test2_n"); +#else + t2.WriteAttributes("testfiles/test2_n"); +#endif + +#ifndef WIN32 + { + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); + TEST_CHECK_THROWS(t1.WriteAttributes("testfiles/test1_nXX"), + CommonException, OSFileError); + TEST_CHECK_THROWS(t3.WriteAttributes("doesn't exist"), + BackupStoreException, AttributesNotLoaded); + } + + // Test that attributes are vaguely similar + TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_n")); + TEST_THAT(attrmatch("testfiles/test2", "testfiles/test2_n")); +#endif + + // Check encryption, and recovery from encryption + // First, check that two attributes taken from the same thing have different encrypted values (think IV) + BackupClientFileAttributes t1b; + t1b.ReadAttributes("testfiles/test1"); + TEST_THAT(::memcmp(t1.GetBuffer(), t1b.GetBuffer(), t1.GetSize()) != 0); + // But that comparing them works OK. + TEST_THAT(t1 == t1b); + // Then store them both to a stream + CollectInBufferStream stream; + t1.WriteToStream(stream); + t1b.WriteToStream(stream); + // Read them back again + stream.SetForReading(); + BackupClientFileAttributes t1_r, t1b_r; + t1_r.ReadFromStream(stream, 1000); + t1b_r.ReadFromStream(stream, 1000); + TEST_THAT(::memcmp(t1_r.GetBuffer(), t1b_r.GetBuffer(), t1_r.GetSize()) != 0); + TEST_THAT(t1_r == t1b_r); + TEST_THAT(t1 == t1_r); + TEST_THAT(t1b == t1b_r); + TEST_THAT(t1_r == t1b); + TEST_THAT(t1b_r == t1); + +#ifdef HAVE_SYS_XATTR_H + // Write some attributes to the file, checking for ENOTSUP + bool xattrNotSupported = false; + if(!write_xattr_test("testfiles/test1", "user.attr_1", 1000, &xattrNotSupported) && xattrNotSupported) + { + ::printf("***********\nYour platform supports xattr, but your filesystem does not.\nSkipping tests.\n***********\n"); + } + else + { + BackupClientFileAttributes x1, x2, x3, x4; + + // Write more attributes + TEST_THAT(write_xattr_test("testfiles/test1", "user.attr_2", 947)); + TEST_THAT(write_xattr_test("testfiles/test1", "user.sadfohij39998.3hj", 123)); + + // Read file attributes + x1.ReadAttributes("testfiles/test1"); + + // Write file attributes + FILE *f = fopen("testfiles/test1_nx", "w"); + fclose(f); + x1.WriteAttributes("testfiles/test1_nx"); + + // Compare to see if xattr copied + TEST_THAT(attrmatch("testfiles/test1", "testfiles/test1_nx")); + + // Add more attributes to a file + x2.ReadAttributes("testfiles/test1"); + TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); + + // Read them again, and check that the Compare() function detects that they're different + x3.ReadAttributes("testfiles/test1"); + TEST_THAT(x1.Compare(x2, true, true)); + TEST_THAT(!x1.Compare(x3, true, true)); + + // Change the value of one of them, leaving the size the same. + TEST_THAT(write_xattr_test("testfiles/test1", "user.328989sj..sdf", 23)); + x4.ReadAttributes("testfiles/test1"); + TEST_THAT(!x1.Compare(x4, true, true)); + } + finish_with_write_xattr_test(); +#endif // HAVE_SYS_XATTR_H + + TEARDOWN_TEST_BBACKUPD(); +} + +int64_t GetDirID(BackupProtocolCallable &protocol, const char *name, int64_t InDirectory) +{ + protocol.QueryListDirectory( + InDirectory, + BackupProtocolListDirectory::Flags_Dir, + BackupProtocolListDirectory::Flags_EXCLUDE_NOTHING, + true /* want attributes */); + + // Retrieve the directory from the stream following + BackupStoreDirectory dir; + std::auto_ptr dirstream(protocol.ReceiveStream()); + dir.ReadFromStream(*dirstream, protocol.GetTimeout()); + + BackupStoreDirectory::Iterator i(dir); + BackupStoreDirectory::Entry *en = 0; + int64_t dirid = 0; + BackupStoreFilenameClear dirname(name); + while((en = i.Next()) != 0) + { + if(en->GetName() == dirname) + { + dirid = en->GetObjectID(); + } + } + return dirid; +} + +void terminate_on_alarm(int sigraised) +{ + abort(); +} + +#ifndef WIN32 +void do_interrupted_restore(const TLSContext &context, int64_t restoredirid) +{ + int pid = 0; + switch((pid = fork())) + { + case 0: + // child process + { + // connect and log in + SocketStreamTLS* pConn = new SocketStreamTLS; + std::auto_ptr apConn(pConn); + pConn->Open(context, Socket::TypeINET, "localhost", 22011); + BackupProtocolClient protocol(apConn); + + protocol.QueryVersion(BACKUP_STORE_SERVER_VERSION); + std::auto_ptr + 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); + + // Log out + protocol.QueryFinished(); + } + exit(0); + break; + + case -1: + { + printf("Fork failed\n"); + exit(1); + } + + default: + { + // Wait until a resume file is written, then terminate the child + while(true) + { + // Test for existence of the result file + int64_t resumesize = 0; + if(FileExists("testfiles/restore-interrupt.boxbackupresume", &resumesize) && resumesize > 16) + { + // It's done something. Terminate it. + ::kill(pid, SIGTERM); + break; + } + + // Process finished? + int status = 0; + if(waitpid(pid, &status, WNOHANG) != 0) + { + // child has finished anyway. + return; + } + + // Give up timeslot so as not to hog the processor + ::sleep(0); + } + + // Just wait until the child has completed + int status = 0; + waitpid(pid, &status, 0); + } + } +} +#endif // !WIN32 + +#ifdef WIN32 +bool set_file_time(const char* filename, FILETIME creationTime, + FILETIME lastModTime, FILETIME lastAccessTime) +{ + HANDLE handle = openfile(filename, O_RDWR, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + if (handle == INVALID_HANDLE_VALUE) return false; + + BOOL success = SetFileTime(handle, &creationTime, &lastAccessTime, + &lastModTime); + TEST_THAT(success); + + TEST_THAT(CloseHandle(handle)); + return success; +} +#endif + +void intercept_setup_delay(const char *filename, unsigned int delay_after, + int delay_ms, int syscall_to_delay); +bool intercept_triggered(); + +int64_t SearchDir(BackupStoreDirectory& rDir, + const std::string& rChildName) +{ + BackupStoreDirectory::Iterator i(rDir); + BackupStoreFilenameClear child(rChildName); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); + if (en == 0) return 0; + int64_t id = en->GetObjectID(); + TEST_THAT(id > 0); + TEST_THAT(id != BackupProtocolListDirectory::RootDirectory); + return id; +} + +std::auto_ptr ReadDirectory +( + BackupProtocolCallable& rClient, + int64_t id = BackupProtocolListDirectory::RootDirectory +) +{ + std::auto_ptr dirreply( + rClient.QueryListDirectory(id, false, 0, false)); + std::auto_ptr apDir( + new BackupStoreDirectory(rClient.ReceiveStream(), SHORT_TIMEOUT)); + return apDir; +} + +int start_internal_daemon() +{ + // ensure that no child processes end up running tests! + int own_pid = getpid(); + BOX_TRACE("Test PID is " << own_pid); + + // this is a quick hack to allow passing some options to the daemon + const char* argv[] = { + "dummy", + bbackupd_args.c_str(), + }; + + BackupDaemon daemon; + spDaemon = &daemon; // to propagate into child + int result; + + if (bbackupd_args.size() > 0) + { + result = daemon.Main("testfiles/bbackupd.conf", 2, argv); + } + else + { + result = daemon.Main("testfiles/bbackupd.conf", 1, argv); + } + + spDaemon = NULL; // to ensure not used by parent + + TEST_EQUAL_LINE(0, result, "Daemon exit code"); + + // ensure that no child processes end up running tests! + if (getpid() != own_pid) + { + // abort! + BOX_INFO("Daemon child finished, exiting now."); + _exit(0); + } + + TEST_THAT(TestFileExists("testfiles/bbackupd.pid")); + + printf("Waiting for backup daemon to start: "); + int pid = -1; + + for (int i = 0; i < 30; i++) + { + printf("."); + fflush(stdout); + safe_sleep(1); + + if (TestFileExists("testfiles/bbackupd.pid")) + { + pid = ReadPidFile("testfiles/bbackupd.pid"); + } + + if (pid > 0) + { + break; + } + } + + printf(" done.\n"); + fflush(stdout); + + TEST_THAT(pid > 0); + spDaemon = &daemon; + return pid; +} + +bool stop_internal_daemon(int pid) +{ + bool killed_server = KillServer(pid, false); + TEST_THAT(killed_server); + return killed_server; +} + +static struct dirent readdir_test_dirent; +static int readdir_test_counter = 0; +static int readdir_stop_time = 0; +static char stat_hook_filename[512]; + +// First test hook, during the directory scanning stage, returns empty. +// (Where is this stage? I can't find it, so I switched from using +// readdir_test_hook_1 to readdir_test_hook_2 in intercept tests.) +// This will not match the directory on the store, so a sync will start. +// We set up the next intercept for the same directory by passing NULL. + +extern "C" struct dirent *readdir_test_hook_2(DIR *dir); + +#ifdef LINUX_WEIRD_LSTAT +extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf); +#else +extern "C" int lstat_test_hook(const char *file_name, struct stat *buf); +#endif + +extern "C" struct dirent *readdir_test_hook_1(DIR *dir) +{ +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + intercept_setup_readdir_hook(NULL, readdir_test_hook_2); +#endif + return NULL; +} + +// Second test hook, called by BackupClientDirectoryRecord::SyncDirectory, +// keeps returning new filenames until the timer expires, then disables the +// intercept. + +extern "C" struct dirent *readdir_test_hook_2(DIR *dir) +{ + 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); + } + + // fill in the struct dirent appropriately + memset(&readdir_test_dirent, 0, sizeof(readdir_test_dirent)); + + #ifdef HAVE_STRUCT_DIRENT_D_INO + readdir_test_dirent.d_ino = ++readdir_test_counter; + #endif + + snprintf(readdir_test_dirent.d_name, + sizeof(readdir_test_dirent.d_name), + "test.%d", readdir_test_counter); + BOX_TRACE("readdir hook returning " << readdir_test_dirent.d_name); + + // ensure that when bbackupd stats the file, it gets the + // right answer + snprintf(stat_hook_filename, sizeof(stat_hook_filename), + "testfiles/TestDir1/spacetest/d1/test.%d", + readdir_test_counter); + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + intercept_setup_lstat_hook(stat_hook_filename, lstat_test_hook); +#endif + + // sleep a bit to reduce the number of dirents returned + if (time_now < readdir_stop_time) + { + ::safe_sleep(1); + } + + return &readdir_test_dirent; +} + +#ifdef LINUX_WEIRD_LSTAT +extern "C" int lstat_test_hook(int ver, const char *file_name, struct stat *buf) +#else +extern "C" int lstat_test_hook(const char *file_name, struct stat *buf) +#endif +{ + // TRACE1("lstat hook triggered for %s", file_name); + memset(buf, 0, sizeof(*buf)); + buf->st_mode = S_IFREG; + return 0; +} + +// Simulate a symlink that is on a different device than the file +// that it points to. +int lstat_test_post_hook(int old_ret, const char *file_name, struct stat *buf) +{ + BOX_TRACE("lstat post hook triggered for " << file_name); + if (old_ret == 0 && + strcmp(file_name, "testfiles/symlink-to-TestDir1") == 0) + { + buf->st_dev ^= 0xFFFF; + } + return old_ret; +} + +bool test_entry_deleted(BackupStoreDirectory& rDir, + const std::string& rName) +{ + BackupStoreDirectory::Iterator i(rDir); + + BackupStoreDirectory::Entry *en = i.FindMatchingClearName( + BackupStoreFilenameClear(rName)); + TEST_THAT_OR(en != 0, return false); + + int16_t flags = en->GetFlags(); + TEST_LINE(flags && BackupStoreDirectory::Entry::Flags_Deleted, + rName + " should have been deleted"); + return flags && BackupStoreDirectory::Entry::Flags_Deleted; +} + +bool compare(BackupQueries::ReturnCode::Type expected_status, + const std::string& bbackupquery_options = "", + const std::string& compare_options = "-acQ") +{ + std::string cmd = BBACKUPQUERY; + cmd += " "; + cmd += (expected_status == BackupQueries::ReturnCode::Compare_Same) + ? "-Wwarning" : "-Werror"; + cmd += " -c testfiles/bbackupd.conf "; + cmd += " " + bbackupquery_options; + cmd += " \"compare " + compare_options + "\" 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); +} + +bool compare_local(BackupQueries::ReturnCode::Type expected_status, + BackupProtocolCallable& client, + const std::string& compare_options = "acQ") +{ + std::auto_ptr config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + BackupQueries bbackupquery(client, *config, false); + + std::vector args; + bool opts[256] = {}; + for (std::string::const_iterator i = compare_options.begin(); + i != compare_options.end(); i++) + { + opts[(unsigned char)*i] = true; + } + bbackupquery.CommandCompare(args, opts); + TEST_EQUAL_OR(expected_status, bbackupquery.GetReturnCode(), + return false); + return true; +} + +bool bbackupquery(const std::string& arguments, + const std::string& memleaks_file = "bbackupquery.memleaks") +{ + std::string cmd = BBACKUPQUERY; + cmd += " -c testfiles/bbackupd.conf " + arguments + " quit"; + + int returnValue = ::system(cmd.c_str()); + +#ifndef WIN32 + returnValue >>= 8; +#endif + + TestRemoteProcessMemLeaks(memleaks_file.c_str()); + TEST_EQUAL(returnValue, BackupQueries::ReturnCode::Command_OK); + return (returnValue == BackupQueries::ReturnCode::Command_OK); +} + +bool restore(const std::string& location, const std::string& dest_dir) +{ + std::string cmd = "\"restore " + location + " " + dest_dir + "\""; + TEST_THAT_OR(bbackupquery(cmd), FAIL); + return true; +} + +bool touch_and_wait(const std::string& filename) +{ + int fd = open(filename.c_str(), O_CREAT | O_WRONLY, 0755); + TEST_THAT(fd > 0); + if (fd <= 0) return false; + + // write again, to update the file's timestamp + int write_result = write(fd, "z", 1); + TEST_EQUAL_LINE(1, write_result, "Buffer write"); + if (write_result != 1) return false; + + 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"); + return true; +} + +TLSContext sTlsContext; + +#define TEST_COMPARE(...) \ + TEST_THAT(compare(BackupQueries::ReturnCode::__VA_ARGS__)); +#define TEST_COMPARE_LOCAL(...) \ + TEST_THAT(compare_local(BackupQueries::ReturnCode::__VA_ARGS__)); + +bool search_for_file(const std::string& filename) +{ + std::auto_ptr client = + connect_and_login(sTlsContext, BackupProtocolLogin::Flags_ReadOnly); + + std::auto_ptr dir = ReadDirectory(*client); + int64_t testDirId = SearchDir(*dir, filename); + client->QueryFinished(); + + return (testDirId != 0); +} + +class MockClientContext : public BackupClientContext +{ +public: + BackupProtocolCallable& mrClient; + int mNumKeepAlivesPolled; + int mKeepAliveTime; + + MockClientContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode, + BackupProtocolCallable& rClient + ) + : BackupClientContext(rResolver, rTLSContext, + rHostname, Port, AccountNumber, ExtendedLogging, + ExtendedLogToFile, ExtendedLogFile, + rProgressNotifier, TcpNiceMode), + mrClient(rClient), + mNumKeepAlivesPolled(0), + mKeepAliveTime(-1) + { } + + BackupProtocolCallable &GetConnection() + { + return mrClient; + } + + virtual BackupProtocolCallable* GetOpenConnection() const + { + return &mrClient; + } + + void SetKeepAliveTime(int iSeconds) + { + mKeepAliveTime = iSeconds; + BackupClientContext::SetKeepAliveTime(iSeconds); + } + + virtual void DoKeepAlive() + { + mNumKeepAlivesPolled++; + BackupClientContext::DoKeepAlive(); + } +}; + +class MockBackupDaemon : public BackupDaemon +{ + BackupProtocolCallable& mrClient; + +public: + MockBackupDaemon(BackupProtocolCallable &rClient) + : mrClient(rClient) + { } + + std::auto_ptr GetNewContext + ( + LocationResolver &rResolver, + TLSContext &rTLSContext, + const std::string &rHostname, + int32_t Port, + uint32_t AccountNumber, + bool ExtendedLogging, + bool ExtendedLogToFile, + std::string ExtendedLogFile, + ProgressNotifier &rProgressNotifier, + bool TcpNiceMode + ) + { + std::auto_ptr context( + new MockClientContext(rResolver, + rTLSContext, rHostname, Port, + AccountNumber, ExtendedLogging, + ExtendedLogToFile, ExtendedLogFile, + rProgressNotifier, TcpNiceMode, mrClient)); + return context; + } +}; + +bool test_readdirectory_on_nonexistent_dir() +{ + SETUP_WITH_BBSTORED(); + + { + std::auto_ptr client = connect_and_login( + sTlsContext, 0 /* read-write */); + + { + Logger::LevelGuard(Logging::GetConsole(), Log::ERROR); + TEST_CHECK_THROWS(ReadDirectory(*client, 0x12345678), + ConnectionException, + Protocol_UnexpectedReply); + TEST_PROTOCOL_ERROR_OR(*client, Err_DoesNotExist,); + } + + client->QueryFinished(); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_bbackupquery_parser_escape_slashes() +{ + SETUP_WITH_BBSTORED(); + + BackupProtocolLocal2 connection(0x01234567, "test", + "backup/01234567/", 0, false); + + BackupClientFileAttributes attr; + attr.ReadAttributes("testfiles/TestDir1", + false /* put mod times in the attributes, please */); + std::auto_ptr attrStream(new MemBlockStream(attr)); + BackupStoreFilenameClear dirname("foo"); + int64_t foo_id = connection.QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)->GetObjectID(); + + attrStream.reset(new MemBlockStream(attr)); + dirname = BackupStoreFilenameClear("/bar"); + int64_t bar_id = connection.QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)->GetObjectID(); + + std::auto_ptr config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + BackupQueries query(connection, *config, false); // read-only + + TEST_EQUAL(foo_id, query.FindDirectoryObjectID("foo")); + TEST_EQUAL(foo_id, query.FindDirectoryObjectID("/foo")); + TEST_EQUAL(0, query.FindDirectoryObjectID("\\/foo")); + TEST_EQUAL(0, query.FindDirectoryObjectID("/bar")); + TEST_EQUAL(bar_id, query.FindDirectoryObjectID("\\/bar")); + connection.QueryFinished(); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_getobject_on_nonexistent_file() +{ + SETUP_WITH_BBSTORED(); + + { + std::auto_ptr config = + load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, BackupDaemonConfigVerify); + TEST_THAT_OR(config.get(), return false); + + std::auto_ptr connection = + connect_and_login(sTlsContext, 0 /* read-write */); + BackupQueries query(*connection, *config, false); // read-only + std::vector args; + args.push_back("2"); // object ID + args.push_back("testfiles/2.obj"); // output file + bool opts[256]; + + Capture capture; + Logging::TempLoggerGuard guard(&capture); + query.CommandGetObject(args, opts); + std::vector messages = capture.GetMessages(); + TEST_THAT(!messages.empty()); + if (!messages.empty()) + { + std::string last_message = messages.back().message; + TEST_EQUAL("Object ID 0x2 does not exist on store.", + last_message); + } + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// ASSERT((mpBlockIndex == 0) || (NumBlocksInIndex != 0)) in +// BackupStoreFileEncodeStream::Recipe::Recipe once failed, apparently because +// a zero byte file had a block index but no entries in it. But this test +// doesn't reproduce the error, so it's not enabled for now. + +bool test_replace_zero_byte_file_with_nonzero_byte_file() +{ + SETUP_TEST_BBACKUPD(); + + TEST_THAT_OR(mkdir("testfiles/TestDir1", 0755) == 0, FAIL); + FileStream emptyFile("testfiles/TestDir1/f2", + O_WRONLY | O_CREAT | O_EXCL, 0755); + wait_for_operation(5, "f2 to be old enough"); + + BackupProtocolLocal2 client(0x01234567, "test", + "backup/01234567/", 0, false); + MockBackupDaemon bbackupd(client); + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf")); + bbackupd.RunSyncNow(); + TEST_COMPARE_LOCAL(Compare_Same, client); + + MemBlockStream stream("Hello world"); + stream.CopyStreamTo(emptyFile); + emptyFile.Close(); + wait_for_operation(5, "f2 to be old enough"); + + bbackupd.RunSyncNow(); + TEST_COMPARE_LOCAL(Compare_Same, client); + + TEARDOWN_TEST_BBACKUPD(); +} + +// This caused the issue reported by Brendon Baumgartner and described in my +// email to the Box Backup list on Mon, 21 Apr 2014 at 18:44:38. If the +// directory disappears then we used to try to send an empty attributes block +// to the server, which is illegal. +bool test_backup_disappearing_directory() +{ + SETUP_WITH_BBSTORED(); + + class BackupClientDirectoryRecordHooked : public BackupClientDirectoryRecord + { + public: + BackupClientDirectoryRecordHooked(int64_t ObjectID, + const std::string &rSubDirName) + : BackupClientDirectoryRecord(ObjectID, rSubDirName), + mDeletedOnce(false) + { } + bool mDeletedOnce; + bool UpdateItems(SyncParams &rParams, const std::string &rLocalPath, + const std::string &rRemotePath, + const Location& rBackupLocation, + BackupStoreDirectory *pDirOnStore, + std::vector &rEntriesLeftOver, + std::vector &rFiles, + const std::vector &rDirs) + { + if(!mDeletedOnce) + { + TEST_THAT(::rmdir("testfiles/TestDir1/dir23") == 0); + mDeletedOnce = true; + } + + return BackupClientDirectoryRecord::UpdateItems(rParams, + rLocalPath, rRemotePath, rBackupLocation, + pDirOnStore, rEntriesLeftOver, rFiles, rDirs); + } + }; + + BackupClientContext clientContext + ( + bbackupd, // rLocationResolver + sTlsContext, + "localhost", + BOX_PORT_BBSTORED_TEST, + 0x01234567, + false, // ExtendedLogging + false, // ExtendedLogFile + "", // extendedLogFile + bbackupd, // rProgressNotifier + false // TcpNice + ); + + BackupClientInodeToIDMap oldMap, newMap; + oldMap.OpenEmpty(); + newMap.Open("testfiles/test_map.db", false, true); + clientContext.SetIDMaps(&oldMap, &newMap); + + BackupClientDirectoryRecord::SyncParams params( + bbackupd, // rRunStatusProvider, + bbackupd, // rSysadminNotifier, + bbackupd, // rProgressNotifier, + clientContext, + &bbackupd); + params.mSyncPeriodEnd = GetCurrentBoxTime(); + + BackupClientFileAttributes attr; + attr.ReadAttributes("testfiles/TestDir1", + false /* put mod times in the attributes, please */); + std::auto_ptr attrStream(new MemBlockStream(attr)); + BackupStoreFilenameClear dirname("Test1"); + std::auto_ptr + dirCreate(clientContext.GetConnection().QueryCreateDirectory( + BACKUPSTORE_ROOT_DIRECTORY_ID, // containing directory + 0, // attrModTime, + dirname, // dirname, + attrStream)); + clientContext.CloseAnyOpenConnection(); + + // Object ID for later creation + int64_t oid = dirCreate->GetObjectID(); + BackupClientDirectoryRecordHooked record(oid, "Test1"); + + TEST_COMPARE(Compare_Different); + + Location fakeLocation; + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); + + // Run another backup, check that we haven't got an inconsistent + // state that causes a crash. + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); + + // Now recreate it and run another backup, check that we haven't got + // an inconsistent state that causes a crash or prevents us from + // creating the directory if it appears later. + TEST_THAT(::mkdir("testfiles/TestDir1/dir23", 0755) == 0); + TEST_COMPARE(Compare_Different); + + record.SyncDirectory(params, + BACKUPSTORE_ROOT_DIRECTORY_ID, + "testfiles/TestDir1", // locationPath, + "/whee", // remotePath + fakeLocation); + clientContext.CloseAnyOpenConnection(); + TEST_COMPARE(Compare_Same); + + TEARDOWN_TEST_BBACKUPD(); +} + +class KeepAliveBackupProtocolLocal : public BackupProtocolLocal2 +{ +public: + int mNumKeepAlivesSent; + int mNumKeepAlivesReceived; + +public: + KeepAliveBackupProtocolLocal(int32_t AccountNumber, + const std::string& ConnectionDetails, + const std::string& AccountRootDir, int DiscSetNumber, + bool ReadOnly) + : BackupProtocolLocal2(AccountNumber, ConnectionDetails, AccountRootDir, + DiscSetNumber, ReadOnly), + mNumKeepAlivesSent(0), + mNumKeepAlivesReceived(0) + { } + + std::auto_ptr Query(const BackupProtocolGetIsAlive &rQuery) + { + mNumKeepAlivesSent++; + std::auto_ptr response = + BackupProtocolLocal::Query(rQuery); + mNumKeepAlivesReceived++; + return response; + } +}; + +bool test_ssl_keepalives() +{ + SETUP_TEST_BBACKUPD(); + + KeepAliveBackupProtocolLocal connection(0x01234567, "test", "backup/01234567/", + 0, false); + MockBackupDaemon bbackupd(connection); + TEST_THAT_OR(setup_test_bbackupd(bbackupd), FAIL); + + // Test that sending a keepalive actually works, when the timeout has expired, + // but doesn't send anything at the beginning: + { + MockClientContext context( + bbackupd, // rResolver + sTlsContext, // rTLSContext + "localhost", // rHostname + BOX_PORT_BBSTORED_TEST, + 0x01234567, // AccountNumber + false, // ExtendedLogging + false, // ExtendedLogToFile + "", // ExtendedLogFile + bbackupd, // rProgressNotifier + false, // TcpNiceMode + connection); // rClient + + // Set the timeout to 1 second + context.SetKeepAliveTime(1); + + // Check that DoKeepAlive() does nothing right now + context.DoKeepAlive(); + TEST_EQUAL(0, connection.mNumKeepAlivesSent); + + // Sleep until just before the timer expires, check that DoKeepAlive() + // still does nothing. + ShortSleep(MilliSecondsToBoxTime(900), true); + context.DoKeepAlive(); + TEST_EQUAL(0, connection.mNumKeepAlivesSent); + + // Sleep until just after the timer expires, check that DoKeepAlive() + // sends a GetIsAlive message now. + ShortSleep(MilliSecondsToBoxTime(200), true); + context.DoKeepAlive(); + TEST_EQUAL(1, connection.mNumKeepAlivesSent); + TEST_EQUAL(1, connection.mNumKeepAlivesReceived); + TEST_EQUAL(3, context.mNumKeepAlivesPolled); + } + + // Do the initial backup. There are no existing files to diff, so the only + // keepalives polled should be the ones for each directory entry while reading + // directories, and the one in UpdateItems(), which is also once per item (file + // or directory). test_base.tgz has 16 directory entries, so we expect 2 * 16 = 32 + // keepalives in total. Except on Windows where there are no symlinks, and when + // compiled with MSVC we exclude them from the tar extract operation as 7za + // complains about them, so there should be 3 files less, and thus only 26 + // keepalives. +#ifdef _MSC_VER + #define NUM_KEEPALIVES_BASE 26 +#else + #define NUM_KEEPALIVES_BASE 32 +#endif + + std::auto_ptr apContext = bbackupd.RunSyncNow(); + MockClientContext* pContext = (MockClientContext *)(apContext.get()); + TEST_EQUAL(NUM_KEEPALIVES_BASE, pContext->mNumKeepAlivesPolled); + TEST_EQUAL(1, pContext->mKeepAliveTime); + + // Calculate the number of blocks that will be in ./TestDir1/x1/dsfdsfs98.fd, + // which is 4269 bytes long. + int64_t NumBlocks; + int32_t BlockSize, LastBlockSize; + BackupStoreFileEncodeStream::CalculateBlockSizes(4269, NumBlocks, BlockSize, LastBlockSize); + TEST_EQUAL(4096, BlockSize); + TEST_EQUAL(173, LastBlockSize); + TEST_EQUAL(2, NumBlocks); + + // Now modify the file and run another backup. It's the only file that should be + // diffed, and DoKeepAlive() should be called for each block size in the original + // file, times the number of times that block size fits into the new file, + // i.e. 1 + (4269 / 256) = 18 times (plus the same 32 while scanning, as above). + + { + int fd = open("testfiles/TestDir1/x1/dsfdsfs98.fd", O_WRONLY); + TEST_THAT_OR(fd > 0, FAIL); + + char buffer[4000]; + memset(buffer, 0, sizeof(buffer)); + + TEST_EQUAL_LINE(sizeof(buffer), + write(fd, buffer, sizeof(buffer)), + "Buffer write"); + TEST_THAT(close(fd) == 0); + + wait_for_operation(5, "modified file to be old enough"); + } + + apContext = bbackupd.RunSyncNow(); + pContext = (MockClientContext *)(apContext.get()); + TEST_EQUAL(NUM_KEEPALIVES_BASE + (4269/4096) + (4269/173), + pContext->mNumKeepAlivesPolled); + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_backup_hardlinked_files() +{ + SETUP_WITH_BBSTORED(); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // Create some hard links. First in the same directory: + TEST_THAT(link("testfiles/TestDir1/x1/dsfdsfs98.fd", + "testfiles/TestDir1/x1/hardlink1") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // Now in a different directory + TEST_THAT(mkdir("testfiles/TestDir1/x2", 0755) == 0); + TEST_THAT(link("testfiles/TestDir1/x1/dsfdsfs98.fd", + "testfiles/TestDir1/x2/hardlink2") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // Now delete one of them + TEST_THAT(unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // And another. + TEST_THAT(unlink("testfiles/TestDir1/x1/hardlink1") == 0); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_backup_pauses_when_store_is_full() +{ + SETUP_WITHOUT_FILES(); + unpack_files("spacetest1", "testfiles/TestDir1"); + TEST_THAT_OR(StartClient(), FAIL); + + // TODO FIXME dedent + { + // wait for files to be uploaded + BOX_TRACE("Waiting for all outstanding files to be uploaded...") + wait_for_sync_end(); + BOX_TRACE("Done. Comparing to check that it worked...") + TEST_COMPARE(Compare_Same); + + // BLOCK + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(5, 0, 0, 9)); + TEST_THAT(check_num_blocks(*client, 10, 0, 0, 18, 28)); + client->QueryFinished(); + } + + // Set limit to something very small. + // 28 blocks are used at this point. + // set soft limit to 0 to ensure that all deleted files + // are deleted immediately by housekeeping + TEST_THAT(change_account_limits("0B", "20B")); + + // Unpack some more files + unpack_files("spacetest2", "testfiles/TestDir1"); + + // Delete a file and a directory + TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\spacetest\\d7") == 0); +#else + TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); +#endif + + // The following files should be in the backup directory: + // 00000001 -d---- 00002 (root) + // 00000002 -d---- 00002 Test1 + // 00000003 -d---- 00002 Test1/spacetest + // 00000004 f-X--- 00002 Test1/spacetest/f1 + // 00000005 f----- 00002 Test1/spacetest/f2 + // 00000006 -d---- 00002 Test1/spacetest/d1 + // 00000007 f----- 00002 Test1/spacetest/d1/f3 + // 00000008 f----- 00002 Test1/spacetest/d1/f4 + // 00000009 -d---- 00002 Test1/spacetest/d2 + // 00000009 -d---- 00002 Test1/spacetest/d6 + // 0000000a -d---- 00002 Test1/spacetest/d3 + // 0000000b -d---- 00002 Test1/spacetest/d3/d4 + // 0000000c f----- 00002 Test1/spacetest/d3/d4/f5 + // 0000000d -d---- 00002 Test1/spacetest/d6 + // 0000000d -d---- 00002 Test1/spacetest/d6/d8 + // 0000000d -d---- 00002 Test1/spacetest/d6/d8/f7 + // 0000000e -dX--- 00002 Test1/spacetest/d7 + // + // root + location + spacetest1 + spacetest2 = 17 files + // = 34 blocks with raidfile. Of which 2 in deleted files + // and 18 in directories. Note that f1 and d7 may or may + // not be deleted yet. + // + // The files and dirs from spacetest1 are already on the server + // (28 blocks). If we set the limit to 20 then the client will + // notice as soon as it tries to create the new files and dirs + // from spacetest2. It should still delete f1 and d7, but that + // won't bring it back under the hard limit, so no files from + // spacetest2 should be uploaded. + + BOX_TRACE("Waiting for sync for bbackupd to notice that the " + "store is full"); + wait_for_sync_end(); + BOX_TRACE("Sync finished."); + + BOX_TRACE("Compare to check that there are differences"); + TEST_COMPARE(Compare_Different); + + // Check that the notify script was run + TEST_THAT(TestFileExists("testfiles/notifyran.store-full.1")); + // But only once! + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); + + // We can't guarantee to get in before housekeeping runs, so it's safer + // (more reliable) to wait for it to finish. But we also need to stop the + // client first, otherwise it might be connected when housekeeping tries + // to run, and that would prevent it from running, causing test to fail. + TEST_THAT_OR(StopClient(), FAIL); + wait_for_operation(5, "housekeeping to run"); + + // BLOCK + { + std::auto_ptr client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + std::auto_ptr root_dir = + ReadDirectory(*client, BACKUPSTORE_ROOT_DIRECTORY_ID); + + int64_t test_dir_id = SearchDir(*root_dir, "Test1"); + TEST_THAT_OR(test_dir_id, FAIL); + std::auto_ptr test_dir = + ReadDirectory(*client, test_dir_id); + + int64_t spacetest_dir_id = SearchDir(*test_dir, "spacetest"); + TEST_THAT_OR(spacetest_dir_id, FAIL); + std::auto_ptr spacetest_dir = + ReadDirectory(*client, spacetest_dir_id); + + int64_t d2_id = SearchDir(*spacetest_dir, "d2"); + int64_t d6_id = SearchDir(*spacetest_dir, "d6"); + TEST_THAT_OR(d2_id != 0, FAIL); + TEST_THAT_OR(d6_id != 0, FAIL); + std::auto_ptr d2_dir = + ReadDirectory(*client, d2_id); + std::auto_ptr d6_dir = + ReadDirectory(*client, d6_id); + + // None of the new files should have been uploaded + TEST_EQUAL(SearchDir(*d2_dir, "f6"), 0); + TEST_EQUAL(SearchDir(*d6_dir, "d8"), 0); + + // But f1 and d7 should have been deleted. + TEST_EQUAL(SearchDir(*spacetest_dir, "f1"), 0); + TEST_EQUAL(SearchDir(*spacetest_dir, "d7"), 0); + + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); + client->QueryFinished(); + } + } + + // Increase the limit again, check that all files are backed up on the + // next run. + TEST_THAT(change_account_limits("0B", "34B")); + TEST_THAT_OR(StartClient(), FAIL); + wait_for_sync_end(); + TEST_COMPARE(Compare_Same); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_bbackupd_exclusions() +{ + SETUP_WITHOUT_FILES(); + + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir1")); + // Delete a file and a directory + TEST_THAT(::unlink("testfiles/TestDir1/spacetest/f1") == 0); + +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\spacetest\\d7") == 0); +#else + TEST_THAT(::system("rm -rf testfiles/TestDir1/spacetest/d7") == 0); +#endif + + // We need to be OVER the limit, i.e. >24 blocks, or + // BackupClientContext will mark us over limit immediately on + // connection. + TEST_THAT(change_account_limits("0B", "25B")); + + // Initial run to get the files backed up + { + bbackupd.RunSyncNow(); + TEST_THAT(!bbackupd.StorageLimitExceeded()); + + // BLOCK + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); + client->QueryFinished(); + } + } + + // Create a directory and then try to run a backup. This should try + // to create the directory on the server, fail, and catch the error. + // The directory that we create, spacetest/d6/d8, is included in + // spacetest2.tgz, so we can ignore this for counting files after we + // unpack spacetest2.tgz. + TEST_THAT(::mkdir("testfiles/TestDir1/spacetest/d6/d8", 0755) == 0); + bbackupd.RunSyncNow(); + TEST_THAT(bbackupd.StorageLimitExceeded()); + + // BLOCK + { + TEST_THAT(unpack_files("spacetest2", "testfiles/TestDir1")); + bbackupd.RunSyncNow(); + TEST_THAT(bbackupd.StorageLimitExceeded()); + + // BLOCK + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + TEST_THAT(check_num_files(4, 0, 0, 8)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 16, 24)); + client->QueryFinished(); + } + } + + // TODO FIXME dedent + { + // Start again with a new config that excludes d3 and f2, + // and hence also d3/d4 and d3/d4/f5. bbackupd should mark + // them as deleted and housekeeping should later clean up, + // making space to upload the new files. + // total required: (13-2-4+3)*2 = 20 blocks + + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-exclude.conf")); + // Should be marked as deleted by this run. Hold onto the + // BackupClientContext to stop housekeeping from running. + std::auto_ptr apClientContext = + bbackupd.RunSyncNow(); + // Housekeeping has not yet deleted the files, so there's not + // enough space to upload the new ones. + TEST_THAT(bbackupd.StorageLimitExceeded()); + + // Check that the notify script was run + // TEST_THAT(TestFileExists("testfiles/notifyran.store-full.2")); + // But only twice! + // TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.3")); + + // All these should be marked as deleted but not removed by + // housekeeping yet: + // f2 excluded + // d3 excluded + // d3/d4 excluded + // d3/d4/f5 excluded + // Careful with timing here, these files will be removed by + // housekeeping the next time it runs. We hold onto the client + // context (and hence an open connection) to stop it from + // running for now. + + // But we can't do that on Windows, because bbstored only + // support one simultaneous connection. So we have to hope that + // housekeeping has run recently enough that it doesn't need to + // run again when we disconnect. + + BOX_INFO("Finding out whether bbackupd marked files as deleted"); + + // TODO FIXME dedent + { +#ifdef WIN32 + apClientContext.reset(); +#endif + + std::auto_ptr client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + + std::auto_ptr rootDir = + ReadDirectory(*client); + + int64_t testDirId = SearchDir(*rootDir, "Test1"); + TEST_THAT(testDirId != 0); + + std::auto_ptr Test1_dir = + ReadDirectory(*client, testDirId); + + int64_t spacetestDirId = SearchDir(*Test1_dir, + "spacetest"); + TEST_THAT(spacetestDirId != 0); + + std::auto_ptr spacetest_dir = + ReadDirectory(*client, spacetestDirId); + + // these files were deleted before, they should be + // long gone by now + + TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); + + // these files have just been deleted, because + // they are excluded by the new configuration. + // but housekeeping should not have run yet + + TEST_THAT(test_entry_deleted(*spacetest_dir, "f2")); + TEST_THAT(test_entry_deleted(*spacetest_dir, "d3")); + + int64_t d3_id = SearchDir(*spacetest_dir, "d3"); + TEST_THAT_OR(d3_id != 0, FAIL); + + std::auto_ptr d3_dir = + ReadDirectory(*client, d3_id); + TEST_THAT(test_entry_deleted(*d3_dir, "d4")); + + int64_t d4_id = SearchDir(*d3_dir, "d4"); + TEST_THAT_OR(d4_id != 0, FAIL); + + std::auto_ptr d4_dir = + ReadDirectory(*client, d4_id); + TEST_THAT(test_entry_deleted(*d4_dir, "f5")); + + // d1/f3 and d1/f4 are the only two files on the + // server which are not deleted, they use 2 blocks + // each, the rest is directories and 2 deleted files + // (f2 and d3/d4/f5) + TEST_THAT(check_num_files(2, 0, 2, 8)); + TEST_THAT(check_num_blocks(*client, 4, 0, 4, 16, 24)); + + // Log out. + client->QueryFinished(); + } + + // Release our BackupClientContext and open connection, and + // force housekeeping to run now. + apClientContext.reset(); + TEST_THAT(run_housekeeping_and_check_account()); + + BOX_INFO("Checking that the files were removed"); + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + + std::auto_ptr rootDir = + ReadDirectory(*client); + + int64_t testDirId = SearchDir(*rootDir, "Test1"); + TEST_THAT(testDirId != 0); + + std::auto_ptr Test1_dir = + ReadDirectory(*client, testDirId); + + int64_t spacetestDirId = SearchDir(*Test1_dir, + "spacetest"); + TEST_THAT(spacetestDirId != 0); + + std::auto_ptr spacetest_dir = + ReadDirectory(*client, spacetestDirId); + + TEST_THAT(SearchDir(*spacetest_dir, "f1") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "f2") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d3") == 0); + TEST_THAT(SearchDir(*spacetest_dir, "d7") == 0); + + // f2, d3, d3/d4 and d3/d4/f5 have been removed. + // The files were counted as deleted files before, the + // deleted directories just as directories. + TEST_THAT(check_num_files(2, 0, 0, 6)); + TEST_THAT(check_num_blocks(*client, 4, 0, 0, 12, 16)); + + // Log out. + client->QueryFinished(); + } + + // Need 22 blocks free to upload everything + TEST_THAT_ABORTONFAIL(::system(BBSTOREACCOUNTS " -c " + "testfiles/bbstored.conf setlimit 01234567 0B 22B") + == 0); + TestRemoteProcessMemLeaks("bbstoreaccounts.memleaks"); + + // Run another backup, now there should be enough space + // for everything we want to upload. + bbackupd.RunSyncNow(); + TEST_THAT(!bbackupd.StorageLimitExceeded()); + + // Check that the contents of the store are the same + // as the contents of the disc + TEST_COMPARE(Compare_Same, "-c testfiles/bbackupd-exclude.conf"); + BOX_TRACE("done."); + + // BLOCK + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + + TEST_THAT(check_num_files(4, 0, 0, 7)); + TEST_THAT(check_num_blocks(*client, 8, 0, 0, 14, 22)); + + // d2/f6, d6/d8 and d6/d8/f7 are new + // i.e. 2 new files, 1 new directory + + client->QueryFinished(); + } + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_bbackupd_uploads_files() +{ + SETUP_WITH_BBSTORED(); + + // TODO FIXME dedent + { + // The files were all unpacked with timestamps in the past, + // so no delay should be needed to make them eligible to be + // backed up. + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_bbackupd_responds_to_connection_failure() +{ + SETUP_TEST_BBACKUPD(); + TEST_THAT_OR(unpack_files("test_base"), FAIL); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); // requires fork +#else // !WIN32 + // TODO FIXME dedent + { + // create a new file to force an upload + + const char* new_file = "testfiles/TestDir1/force-upload-2"; + int fd = open(new_file, O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT_OR(fd >= 0, + BOX_LOG_SYS_ERROR(BOX_FILE_MESSAGE(new_file, + "failed to create new file")); + FAIL); + + const char* control_string = "whee!\n"; + TEST_THAT(write(fd, control_string, + strlen(control_string)) == + (int)strlen(control_string)); + close(fd); + + wait_for_operation(5, "new file to be old enough"); + + // Start a bbstored with a test hook that makes it terminate + // on the first StoreFile command, breaking the connection to + // bbackupd. + class MyHook : public BackupStoreContext::TestHook + { + public: + int trigger_count; + MyHook() : trigger_count(0) { } + + virtual std::auto_ptr StartCommand( + const BackupProtocolMessage& rCommand) + { + if (rCommand.GetType() == + BackupProtocolStoreFile::TypeID) + { + // terminate badly + trigger_count++; + THROW_EXCEPTION(ConnectionException, + TLSReadFailed); + } + return std::auto_ptr(); + } + }; + + class MockBackupProtocolLocal : public BackupProtocolLocal2 + { + public: + MyHook hook; + MockBackupProtocolLocal(int32_t AccountNumber, + const std::string& ConnectionDetails, + const std::string& AccountRootDir, int DiscSetNumber, + bool ReadOnly) + : BackupProtocolLocal2(AccountNumber, ConnectionDetails, + AccountRootDir, DiscSetNumber, ReadOnly) + { + GetContext().SetTestHook(hook); + } + virtual ~MockBackupProtocolLocal() { } + }; + + MockBackupProtocolLocal client(0x01234567, "test", + "backup/01234567/", 0, false); + MockBackupDaemon bbackupd(client); + TEST_THAT_OR(setup_test_bbackupd(bbackupd, false, false), FAIL); + + TEST_THAT(::system("rm -f testfiles/notifyran.store-full.*") == 0); + std::auto_ptr apClientContext; + + { + Console& console(Logging::GetConsole()); + Logger::LevelGuard guard(console); + + if (console.GetLevel() < Log::TRACE) + { + console.Filter(Log::NOTHING); + } + + apClientContext = bbackupd.RunSyncNowWithExceptionHandling(); + } + + // Should only have been triggered once + TEST_EQUAL(1, client.hook.trigger_count); + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.1")); + } +#endif // !WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_absolute_symlinks_not_followed_during_restore() +{ + SETUP_WITH_BBSTORED(); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); // requires symlinks +#else + // TODO FIXME dedent + { + #define SYM_DIR "testfiles" DIRECTORY_SEPARATOR "TestDir1" \ + DIRECTORY_SEPARATOR "symlink_test" + + TEST_THAT(::mkdir(SYM_DIR, 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a", 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir", 0777) == 0); + TEST_THAT(::mkdir(SYM_DIR DIRECTORY_SEPARATOR "b", 0777) == 0); + + FILE* fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir" + DIRECTORY_SEPARATOR "content", "w"); + TEST_THAT(fp != NULL); + fputs("before\n", fp); + fclose(fp); + + char buf[PATH_MAX]; + TEST_THAT(getcwd(buf, sizeof(buf)) == buf); + std::string path = buf; + // testfiles/TestDir1/symlink_test/a/subdir -> + // testfiles/TestDir1/symlink_test/b/link + path += DIRECTORY_SEPARATOR SYM_DIR + DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir"; + TEST_THAT(symlink(path.c_str(), SYM_DIR + DIRECTORY_SEPARATOR "b" + DIRECTORY_SEPARATOR "link") == 0); + + // also test symlink-to-self loop does not break restore + TEST_THAT(symlink("self", SYM_DIR "/self") == 0); + + wait_for_operation(5, "symlinks to be old enough"); + bbackupd.RunSyncNow(); + + // Check that the backup was successful, i.e. no differences + TEST_COMPARE(Compare_Same); + + fp = fopen(SYM_DIR DIRECTORY_SEPARATOR "a" + DIRECTORY_SEPARATOR "subdir" + DIRECTORY_SEPARATOR "content", "w"); + TEST_THAT(fp != NULL); + fputs("after\n", fp); + fclose(fp); + + TEST_THAT(chmod(SYM_DIR, 0) == 0); + + // check that we can restore it + { + int returnValue = ::system(BBACKUPQUERY " " + "-c testfiles/bbackupd.conf " + "-Wwarning \"restore Test1 testfiles/restore-symlink\" " + "quit"); + TEST_RETURN(returnValue, + BackupQueries::ReturnCode::Command_OK); + } + + // make it accessible again + TEST_THAT(chmod(SYM_DIR, 0755) == 0); + + // check that the original file was not overwritten + FileStream fs(SYM_DIR "/a/subdir/content"); + IOStreamGetLine gl(fs); + std::string line; + TEST_THAT(gl.GetLine(line)); + TEST_THAT(line != "before"); + TEST_EQUAL("after", line); + + #undef SYM_DIR + } +#endif + + TEARDOWN_TEST_BBACKUPD(); +} + +// Testing that nonexistent locations are backed up if they are created later +bool test_initially_missing_locations_are_not_forgotten() +{ + SETUP_WITH_BBSTORED(); + + // ensure that the directory does not exist at the start + TEST_THAT(!FileExists("testfiles/TestDir2")); + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-temploc.conf")); + + // BLOCK + { + 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")); + + bbackupd.RunSyncNowWithExceptionHandling(); + 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! + TEST_THAT_OR(!search_for_file("Test2"), FAIL); + } + + // create the location directory and unpack some files into it + TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0); + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir2")); + + // check that the files are backed up now + bbackupd.RunSyncNowWithExceptionHandling(); + 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 + TEST_THAT_OR(search_for_file("Test2"), FAIL); + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_redundant_locations_deleted_on_time() +{ + SETUP_WITH_BBSTORED(); + + // create the location directory and unpack some files into it + TEST_THAT(::mkdir("testfiles/TestDir2", 0777) == 0); + TEST_THAT(unpack_files("spacetest1", "testfiles/TestDir2")); + + // Use a daemon with the TestDir2 location configured to back it up + // to the server. + { + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd-temploc.conf")); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + // Now use a daemon with no temporary location, which should delete + // it after 10 seconds + { + TEST_THAT(configure_bbackupd(bbackupd, "testfiles/bbackupd.conf")); + + // Initial run to start the countdown to destruction + bbackupd.RunSyncNow(); + + // Not deleted yet! + TEST_THAT(search_for_file("Test2")); + + wait_for_operation(9, "just before Test2 should be deleted"); + bbackupd.RunSyncNow(); + TEST_THAT(search_for_file("Test2")); + + // Now wait until after it should be deleted + wait_for_operation(2, "just after Test2 should be deleted"); + bbackupd.RunSyncNow(); + + TEST_THAT(search_for_file("Test2")); + std::auto_ptr client = connect_and_login( + sTlsContext, 0 /* read-write */); + std::auto_ptr root_dir = + ReadDirectory(*client, BACKUPSTORE_ROOT_DIRECTORY_ID); + TEST_THAT(test_entry_deleted(*root_dir, "Test2")); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check that read-only directories and their contents can be restored. +bool test_read_only_dirs_can_be_restored() +{ + SETUP_WITH_BBSTORED(); + + // TODO FIXME dedent + { + { + #ifdef WIN32 + // Cygwin chmod changes Windows file attributes + TEST_THAT(::system("chmod 0555 testfiles/" + "TestDir1/x1") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/x1", + 0555) == 0); + #endif + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 testfiles/TestDir1"); + + // check that we can restore it + TEST_THAT(restore("Test1", "testfiles/restore1")); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 testfiles/restore1"); + + // 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); + TEST_THAT(bbackupquery("\"lcd testfiles/restore-test\" " + "\"restore Test1\"")); + TEST_COMPARE(Compare_Same, "", "-cEQ Test1 " + "testfiles/restore-test/Test1"); + + // put the permissions back to sensible values + #ifdef WIN32 + TEST_THAT(::system("chmod 0755 testfiles/" + "TestDir1/x1") == 0); + TEST_THAT(::system("chmod 0755 testfiles/" + "restore1/x1") == 0); + TEST_THAT(::system("chmod 0755 testfiles/" + "restore-test/Test1/x1") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/x1", + 0755) == 0); + TEST_THAT(chmod("testfiles/restore1/x1", + 0755) == 0); + TEST_THAT(chmod("testfiles/restore-test/Test1/x1", + 0755) == 0); + #endif + } + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check that filenames in UTF-8 can be backed up +bool test_unicode_filenames_can_be_backed_up() +{ + SETUP_WITH_BBSTORED(); + +#ifndef WIN32 + BOX_NOTICE("skipping test on this platform"); + // requires ConvertConsoleToUtf8() +#else + // TODO FIXME dedent + { + // We have no guarantee that a random Unicode string can be + // represented in the user's character set, so we go the + // other way, taking three random characters from the + // character set and converting them to Unicode. Unless the + // console codepage is CP_UTF8, in which case our random + // characters are not valid, so we use the UTF8 version + // of them instead. + // + // We hope that these characters are valid in most + // character sets, but they probably are not in multibyte + // character sets such as Shift-JIS, GB2312, etc. This test + // will probably fail if your system locale is set to + // Chinese, Japanese, etc. where one of these character + // sets is used by default. You can check the character + // set for your system in Control Panel -> Regional + // Options -> General -> Language Settings -> Set Default + // (System Locale). Because bbackupquery converts from + // system locale to UTF-8 via the console code page + // (which you can check from the Command Prompt with "chcp") + // they must also be valid in your code page (850 for + // Western Europe). + // + // In ISO-8859-1 (Danish locale) they are three Danish + // accented characters, which are supported in code page + // 850. Depending on your locale, YYMV (your yak may vomit). + + std::string foreignCharsNative = (GetConsoleCP() == CP_UTF8) + ? "\xc3\xa6\xc3\xb8\xc3\xa5" : "\x91\x9b\x86"; + std::string foreignCharsUnicode; + TEST_THAT(ConvertConsoleToUtf8(foreignCharsNative.c_str(), + foreignCharsUnicode)); + + std::string basedir("testfiles/TestDir1"); + std::string dirname("test" + foreignCharsUnicode + "testdir"); + std::string dirpath(basedir + "/" + dirname); + TEST_THAT(mkdir(dirpath.c_str(), 0) == 0); + + std::string filename("test" + foreignCharsUnicode + "testfile"); + std::string filepath(dirpath + "/" + filename); + + char cwdbuf[1024]; + TEST_THAT(getcwd(cwdbuf, sizeof(cwdbuf)) == cwdbuf); + std::string cwd = cwdbuf; + + // Test that our emulated chdir() works properly + // with relative and absolute paths + TEST_THAT(::chdir(dirpath.c_str()) == 0); + TEST_THAT(::chdir("../../..") == 0); + TEST_THAT(::chdir(cwd.c_str()) == 0); + + // Check that it can be converted to the system encoding + // (which is what is needed on the command line) + std::string systemDirName; + TEST_THAT(ConvertEncoding(dirname.c_str(), CP_UTF8, + systemDirName, CP_ACP)); + + std::string systemFileName; + TEST_THAT(ConvertEncoding(filename.c_str(), CP_UTF8, + systemFileName, CP_ACP)); + + // Check that it can be converted to the console encoding + // (which is what we will see in the output) + std::string consoleDirName; + TEST_THAT(ConvertUtf8ToConsole(dirname.c_str(), + consoleDirName)); + + std::string consoleFileName; + TEST_THAT(ConvertUtf8ToConsole(filename.c_str(), + consoleFileName)); + + // test that bbackupd will let us lcd into the local + // directory using a relative path, and back out + TEST_THAT(bbackupquery("\"lcd testfiles/TestDir1/" + + systemDirName + "\" \"lcd ..\"")); + + // and using an absolute path + TEST_THAT(bbackupquery("\"lcd " + cwd + "/testfiles/" + + "TestDir1/" + systemDirName + "\" \"lcd ..\"")); + + { + FileStream fs(filepath.c_str(), O_CREAT | O_RDWR); + + std::string data("hello world\n"); + fs.Write(data.c_str(), data.size()); + TEST_EQUAL_LINE(12, fs.GetPosition(), + "FileStream position"); + fs.Close(); + + // Set modtime back in time to allow immediate backup + struct timeval times[2] = {}; + times[1].tv_sec = 1000000000; + TEST_THAT(emu_utimes(filepath.c_str(), times) == 0); + } + + bbackupd.RunSyncNow(); + + // Compare to check that the file was uploaded + TEST_COMPARE(Compare_Same); + + // Check that we can find it in directory listing + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0); + + std::auto_ptr dir = ReadDirectory( + *client); + + int64_t baseDirId = SearchDir(*dir, "Test1"); + TEST_THAT(baseDirId != 0); + dir = ReadDirectory(*client, baseDirId); + + int64_t testDirId = SearchDir(*dir, dirname.c_str()); + TEST_THAT(testDirId != 0); + dir = ReadDirectory(*client, testDirId); + + TEST_THAT(SearchDir(*dir, filename.c_str()) != 0); + // Log out + client->QueryFinished(); + } + + // Check that bbackupquery shows the dir in console encoding + std::string command = BBACKUPQUERY " -Wwarning " + "-c testfiles/bbackupd.conf " + "-q \"list Test1\" quit"; + pid_t bbackupquery_pid; + std::auto_ptr queryout; + queryout = LocalProcessStream(command.c_str(), + bbackupquery_pid); + TEST_THAT(queryout.get() != NULL); + TEST_THAT(bbackupquery_pid != -1); + + IOStreamGetLine reader(*queryout); + std::string line; + bool found = false; + while (!reader.IsEOF()) + { + TEST_THAT(reader.GetLine(line)); + if (line.find(consoleDirName) != std::string::npos) + { + found = true; + } + } + TEST_THAT(!(queryout->StreamDataLeft())); + TEST_THAT(reader.IsEOF()); + TEST_THAT(found); + queryout->Close(); + + // Check that bbackupquery can list the dir when given + // on the command line in system encoding, and shows + // the file in console encoding + command = BBACKUPQUERY " -c testfiles/bbackupd.conf " + "-Wwarning \"list Test1/" + systemDirName + "\" quit"; + queryout = LocalProcessStream(command.c_str(), + bbackupquery_pid); + TEST_THAT(queryout.get() != NULL); + TEST_THAT(bbackupquery_pid != -1); + + IOStreamGetLine reader2(*queryout); + found = false; + while (!reader2.IsEOF()) + { + TEST_THAT(reader2.GetLine(line)); + if (line.find(consoleFileName) != std::string::npos) + { + found = true; + } + } + TEST_THAT(!(queryout->StreamDataLeft())); + TEST_THAT(reader2.IsEOF()); + TEST_THAT(found); + queryout->Close(); + + // Check that bbackupquery can compare the dir when given + // on the command line in system encoding. + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/TestDir1/" + systemDirName); + + // Check that bbackupquery can restore the dir when given + // on the command line in system encoding. + TEST_THAT(restore("Test1/" + systemDirName, + "testfiles/restore-" + systemDirName)); + + // Compare to make sure it was restored properly. + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName); + + std::string fileToUnlink = "testfiles/restore-" + + dirname + "/" + filename; + TEST_THAT(::unlink(fileToUnlink.c_str()) == 0); + + // Check that bbackupquery can get the file when given + // on the command line in system encoding. + TEST_THAT(bbackupquery("\"get Test1/" + systemDirName + "/" + + systemFileName + " " + "testfiles/restore-" + + systemDirName + "/" + systemFileName + "\"")); + + // And after changing directory to a relative path + TEST_THAT(bbackupquery( + "\"lcd testfiles\" " + "\"cd Test1/" + systemDirName + "\" " + + "\"get " + systemFileName + "\"")); + + // cannot overwrite a file that exists, so delete it + std::string tmp = "testfiles/" + filename; + TEST_THAT(::unlink(tmp.c_str()) == 0); + + // And after changing directory to an absolute path + TEST_THAT(bbackupquery( + "\"lcd " + cwd + "/testfiles\" " + "\"cd Test1/" + systemDirName + "\" " + + "\"get " + systemFileName + "\"")); + + // Compare to make sure it was restored properly. The Get + // command does restore attributes, so we don't need to + // specify the -A option for this to succeed. + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/" + systemDirName + + " testfiles/restore-" + systemDirName); + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } +#endif // WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_sync_allow_script_can_pause_backup() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + + // TODO FIXME dedent + { + { + wait_for_sync_end(); + // we now have 3 seconds before bbackupd + // runs the SyncAllowScript again. + + const char* sync_control_file = "testfiles" + DIRECTORY_SEPARATOR "syncallowscript.control"; + int fd = open(sync_control_file, + O_CREAT | O_EXCL | O_WRONLY, 0700); + if (fd <= 0) + { + perror(sync_control_file); + } + TEST_THAT(fd > 0); + + const char* control_string = "10\n"; + TEST_THAT(write(fd, control_string, + strlen(control_string)) == + (int)strlen(control_string)); + close(fd); + + // this will pause backups, bbackupd will check + // every 10 seconds to see if they are allowed again. + + const char* new_test_file = "testfiles" + DIRECTORY_SEPARATOR "TestDir1" + DIRECTORY_SEPARATOR "Added_During_Pause"; + fd = open(new_test_file, + O_CREAT | O_EXCL | O_WRONLY, 0700); + if (fd <= 0) + { + perror(new_test_file); + } + TEST_THAT(fd > 0); + close(fd); + + struct stat st; + + // next poll should happen within the next + // 5 seconds (normally about 3 seconds) + + wait_for_operation(1, "2 seconds before next run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.1", &st) != 0); + wait_for_operation(4, "2 seconds after run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.1", &st) == 0); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) != 0); + + // next poll should happen within the next + // 10 seconds (normally about 8 seconds) + + wait_for_operation(6, "2 seconds before next run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) != 0); + wait_for_operation(4, "2 seconds after run"); + TEST_THAT(stat("testfiles" DIRECTORY_SEPARATOR + "syncallowscript.notifyran.2", &st) == 0); + + // bbackupquery compare might take a while + // on slow machines, so start the timer now + long start_time = time(NULL); + + // check that no backup has run (compare fails) + TEST_COMPARE(Compare_Different); + + TEST_THAT(unlink(sync_control_file) == 0); + wait_for_sync_start(); + long end_time = time(NULL); + long wait_time = end_time - start_time + 2; + + // should be about 10 seconds + if (wait_time < 8 || wait_time > 12) + { + printf("Waited for %ld seconds, should have " + "been %s", wait_time, control_string); + } + + TEST_THAT(wait_time >= 8); + TEST_THAT(wait_time <= 12); + + wait_for_sync_end(); + + // check that backup has run (compare succeeds) + TEST_COMPARE(Compare_Same); + } + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Delete file and update another, create symlink. +bool test_delete_update_and_symlink_files() +{ + SETUP_WITH_BBSTORED(); + + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Delete a file + TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + + #ifndef WIN32 + // New symlink + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/symlink-to-dir") == 0); + #endif + + // Update a file (will be uploaded as a diff) + { + // Check that the file is over the diffing + // threshold in the bbackupd.conf file + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + > 1024); + + // Add a bit to the end + FILE *f = ::fopen("testfiles/TestDir1/f45.df", "a"); + TEST_THAT(f != 0); + ::fprintf(f, "EXTRA STUFF"); + ::fclose(f); + TEST_THAT(TestGetFileSize("testfiles/TestDir1/f45.df") + > 1024); + } + + // wait long enough for new files to be old enough to backup + wait_for_operation(5, "new files to be old enough"); + + bbackupd.RunSyncNow(); + + // compare to make sure that it worked + TEST_COMPARE(Compare_Same); + + // Try a quick compare, just for fun + TEST_COMPARE(Compare_Same, "", "-acqQ"); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check that store errors are reported neatly. This test uses an independent +// daemon to check the daemon's backup loop delay, so it's easier to debug +// with the command: ./t -VTttest -e test_store_error_reporting +// --bbackupd-args=-kTtbbackupd +bool test_store_error_reporting() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + wait_for_sync_end(); + + // TODO FIXME dedent + { + // Check that store errors are reported neatly + TEST_THAT(system("rm -f testfiles/notifyran.backup-error.*") == 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 config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + TEST_EQUAL_LINE(0, errs.size(), "Loading configuration file " + "reported errors: " << errs); + TEST_THAT_OR(config.get(), return false); + std::auto_ptr 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); + } + + // Create a file to trigger an upload + { + int fd1 = open("testfiles/TestDir1/force-upload", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "just do it", 10) == 10); + TEST_THAT(close(fd1) == 0); + } + + wait_for_operation(4, "bbackupd to try to access the store"); + + // Check that an error was reported just once + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.1")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.2")); + // Now kill bbackupd and start one that's running in + // snapshot mode, check that it automatically syncs after + // an error, without waiting for another sync command. + TEST_THAT(StopClient()); + TEST_THAT(StartClient("testfiles/bbackupd-snapshot.conf")); + sync_and_wait(); + + // Check that the error was reported once more + TEST_THAT(TestFileExists("testfiles/notifyran.backup-error.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.backup-error.3")); + + // Fix the store (so that bbackupquery compare works) + TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", + "testfiles/0_0/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", + "testfiles/0_1/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", + "testfiles/0_2/backup/01234567/info.rf") == 0); + + int store_fixed_time = time(NULL); + + // Check that we DO get errors on compare (cannot do this + // until after we fix the store, which creates a race) + TEST_COMPARE(Compare_Different); + + // Test initial state + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Set a tag for the notify script to distinguish from + // previous runs. + { + int fd1 = open("testfiles/notifyscript.tag", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "wait-snapshot", 13) == 13); + TEST_THAT(close(fd1) == 0); + } + + // bbackupd should pause for BACKUP_ERROR_RETRY_SECONDS (plus + // a random delay of up to mUpdateStoreInterval/64 or 0.05 + // extra seconds) from store_fixed_time, so check that it + // hasn't run just before this time + wait_for_operation(BACKUP_ERROR_DELAY_SHORTENED + + (store_fixed_time - time(NULL)) - 1, + "just before bbackupd recovers"); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Should not have backed up, should still get errors + TEST_COMPARE(Compare_Different); + + // wait another 2 seconds, bbackup should have run + wait_for_operation(2, "bbackupd to recover"); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.backup-start.wait-snapshot.1")); + + // Check that it did get uploaded, and we have no more errors + TEST_COMPARE(Compare_Same); + + TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); + + // Stop the snapshot bbackupd + TEST_THAT(StopClient()); + + // Break the store again + 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); + + // Modify a file to trigger an upload + { + int fd1 = open("testfiles/TestDir1/force-upload", + O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "and again", 9) == 9); + TEST_THAT(close(fd1) == 0); + } + + // Restart bbackupd in automatic mode + TEST_THAT_OR(StartClient(), FAIL); + sync_and_wait(); + + // Fix the store again + TEST_THAT(::rename("testfiles/0_0/backup/01234567/info.rf.bak", + "testfiles/0_0/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_1/backup/01234567/info.rf.bak", + "testfiles/0_1/backup/01234567/info.rf") == 0); + TEST_THAT(::rename("testfiles/0_2/backup/01234567/info.rf.bak", + "testfiles/0_2/backup/01234567/info.rf") == 0); + + store_fixed_time = time(NULL); + + // Check that we DO get errors on compare (cannot do this + // until after we fix the store, which creates a race) + TEST_COMPARE(Compare_Different); + + // Test initial state + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Set a tag for the notify script to distinguish from + // previous runs. + { + int fd1 = open("testfiles/notifyscript.tag", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(write(fd1, "wait-automatic", 14) == 14); + TEST_THAT(close(fd1) == 0); + } + + // bbackupd should pause for BACKUP_ERROR_RETRY_SECONDS (plus + // a random delay of up to mUpdateStoreInterval/64 or 0.05 + // extra seconds) from store_fixed_time, so check that it + // hasn't run just before this time + wait_for_operation(BACKUP_ERROR_DELAY_SHORTENED + + (store_fixed_time - time(NULL)) - 1, + "just before bbackupd recovers"); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Should not have backed up, should still get errors + TEST_COMPARE(Compare_Different); + + // wait another 2 seconds, bbackup should have run + wait_for_operation(2, "bbackupd to recover"); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.backup-start.wait-automatic.1")); + + // Check that it did get uploaded, and we have no more errors + TEST_COMPARE(Compare_Same); + + TEST_THAT(::unlink("testfiles/notifyscript.tag") == 0); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_change_file_to_symlink_and_back() +{ + SETUP_WITH_BBSTORED(); + + #ifndef WIN32 + // New symlink + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/symlink-to-dir") == 0); + #endif + + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Bad case: delete a file/symlink, replace it with a directory. + // Replace symlink with directory, add new directory. + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/symlink-to-dir") + == 0); + #endif + + TEST_THAT(::mkdir("testfiles/TestDir1/symlink-to-dir", 0755) + == 0); + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", 0755) + == 0); + + // NOTE: create a file within the directory to + // avoid deletion by the housekeeping process later + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file/contents") + == 0); + #endif + + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // And the inverse, replace a directory with a file/symlink + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" + "/contents") == 0); + #endif + + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file") == 0); + #endif + + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // And then, put it back to how it was before. + BOX_INFO("Replace symlink with directory (which was a symlink)"); + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1" + "/dir-to-file") == 0); + #endif + + TEST_THAT(::mkdir("testfiles/TestDir1/x1/dir-to-file", + 0755) == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file/contents2") + == 0); + #endif + + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // And finally, put it back to how it was before + // it was put back to how it was before + // This gets lots of nasty things in the store with + // directories over other old directories. + + #ifndef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/x1/dir-to-file" + "/contents2") == 0); + #endif + + TEST_THAT(::rmdir("testfiles/TestDir1/x1/dir-to-file") == 0); + + #ifndef WIN32 + TEST_THAT(::symlink("does-not-exist", + "testfiles/TestDir1/x1/dir-to-file") == 0); + #endif + + wait_for_operation(5, "files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_file_rename_tracking() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // rename an untracked file over an existing untracked file + BOX_INFO("Rename over existing untracked file"); + int fd1 = open("testfiles/TestDir1/untracked-1", + O_CREAT | O_EXCL | O_WRONLY, 0700); + int fd2 = open("testfiles/TestDir1/untracked-2", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(fd2 > 0); + TEST_THAT(write(fd1, "hello", 5) == 5); + TEST_THAT(close(fd1) == 0); + safe_sleep(1); + TEST_THAT(write(fd2, "world", 5) == 5); + TEST_THAT(close(fd2) == 0); + TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-1")); + TEST_THAT(TestFileExists("testfiles/TestDir1/untracked-2")); + + // back up both files + wait_for_operation(5, "untracked files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + #ifdef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/untracked-2") + == 0); + #endif + + TEST_THAT(::rename("testfiles/TestDir1/untracked-1", + "testfiles/TestDir1/untracked-2") == 0); + TEST_THAT(!TestFileExists("testfiles/TestDir1/untracked-1")); + TEST_THAT( TestFileExists("testfiles/TestDir1/untracked-2")); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // case which went wrong: rename a tracked file over an + // existing tracked file + BOX_INFO("Rename over existing tracked file"); + fd1 = open("testfiles/TestDir1/tracked-1", + O_CREAT | O_EXCL | O_WRONLY, 0700); + fd2 = open("testfiles/TestDir1/tracked-2", + O_CREAT | O_EXCL | O_WRONLY, 0700); + TEST_THAT(fd1 > 0); + TEST_THAT(fd2 > 0); + char buffer[1024]; + TEST_THAT(write(fd1, "hello", 5) == 5); + TEST_THAT(write(fd1, buffer, sizeof(buffer)) == sizeof(buffer)); + TEST_THAT(close(fd1) == 0); + safe_sleep(1); + TEST_THAT(write(fd2, "world", 5) == 5); + TEST_THAT(write(fd2, buffer, sizeof(buffer)) == sizeof(buffer)); + TEST_THAT(close(fd2) == 0); + TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-1")); + TEST_THAT(TestFileExists("testfiles/TestDir1/tracked-2")); + + // wait for them to be old enough to back up + wait_for_operation(5, "tracked files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + #ifdef WIN32 + TEST_THAT(::unlink("testfiles/TestDir1/tracked-2") + == 0); + #endif + + TEST_THAT(::rename("testfiles/TestDir1/tracked-1", + "testfiles/TestDir1/tracked-2") == 0); + TEST_THAT(!TestFileExists("testfiles/TestDir1/tracked-1")); + TEST_THAT( TestFileExists("testfiles/TestDir1/tracked-2")); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // case which went wrong: rename a tracked file + // over a deleted file + BOX_INFO("Rename an existing file over a deleted file"); + TEST_THAT(::unlink("testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + TEST_THAT(::rename("testfiles/TestDir1/df9834.dsf", + "testfiles/TestDir1/x1/dsfdsfs98.fd") == 0); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Files that suddenly appear, with timestamps before the last sync window, +// and files whose size or timestamp change, should still be uploaded, even +// though they look old. +bool test_upload_very_old_files() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Add some more files + // Because the 'm' option is not used, these files will + // look very old to the daemon. + // Lucky it'll upload them then! + TEST_THAT(unpack_files("test2")); + + #ifndef WIN32 + ::chmod("testfiles/TestDir1/sub23/dhsfdss/blf.h", 0415); + #endif + + // Wait and test + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that modifying files with old timestamps + // still get added + BOX_INFO("Modify existing file, but change timestamp to rather old"); + + // Then modify an existing file + { + // in the archive, it's read only + #ifdef WIN32 + TEST_THAT(::system("chmod 0777 testfiles" + "/TestDir1/sub23/rand.h") == 0); + #else + TEST_THAT(chmod("testfiles/TestDir1/sub23" + "/rand.h", 0777) == 0); + #endif + + FILE *f = fopen("testfiles/TestDir1/sub23/rand.h", + "w+"); + + if (f == 0) + { + perror("Failed to open"); + } + + TEST_THAT(f != 0); + + if (f != 0) + { + fprintf(f, "MODIFIED!\n"); + fclose(f); + } + + // and then move the time backwards! + struct timeval times[2]; + BoxTimeToTimeval(SecondsToBoxTime( + (time_t)(365*24*60*60)), times[1]); + times[0] = times[1]; + TEST_THAT(::utimes("testfiles/TestDir1/sub23/rand.h", + times) == 0); + } + + // Wait and test + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); // files too new? + + // Check that no read error has been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_excluded_files_are_not_backed_up() +{ + // SETUP_WITH_BBSTORED(); + SETUP_TEST_BBACKUPD(); + + BackupProtocolLocal2 client(0x01234567, "test", "backup/01234567/", + 0, false); + MockBackupDaemon bbackupd(client); + + TEST_THAT_OR(setup_test_bbackupd(bbackupd, + true, // do_unpack_files + false // do_start_bbstored + ), FAIL); + + // TODO FIXME dedent + { + // Add some files and directories which are marked as excluded + TEST_THAT(unpack_files("testexclude")); + bbackupd.RunSyncNow(); + + // compare with exclusions, should not find differences + // TEST_COMPARE(Compare_Same); + TEST_COMPARE_LOCAL(Compare_Same, client); + + // compare without exclusions, should find differences + // TEST_COMPARE(Compare_Different, "", "-acEQ"); + TEST_COMPARE_LOCAL(Compare_Different, client, "acEQ"); + + // check that the excluded files did not make it + // into the store, and the included files did + { + /* + std::auto_ptr pClient = + connect_and_login(context, + BackupProtocolLogin::Flags_ReadOnly); + */ + BackupProtocolCallable* pClient = &client; + + std::auto_ptr dir = + ReadDirectory(*pClient); + + int64_t testDirId = SearchDir(*dir, "Test1"); + TEST_THAT(testDirId != 0); + dir = ReadDirectory(*pClient, testDirId); + + TEST_THAT(!SearchDir(*dir, "excluded_1")); + TEST_THAT(!SearchDir(*dir, "excluded_2")); + TEST_THAT(!SearchDir(*dir, "exclude_dir")); + TEST_THAT(!SearchDir(*dir, "exclude_dir_2")); + // xx_not_this_dir_22 should not be excluded by + // ExcludeDirsRegex, because it's a file + TEST_THAT(SearchDir (*dir, "xx_not_this_dir_22")); + TEST_THAT(!SearchDir(*dir, "zEXCLUDEu")); + TEST_THAT(SearchDir (*dir, "dont.excludethis")); + TEST_THAT(SearchDir (*dir, "xx_not_this_dir_ALWAYSINCLUDE")); + + int64_t sub23id = SearchDir(*dir, "sub23"); + TEST_THAT(sub23id != 0); + dir = ReadDirectory(*pClient, sub23id); + + TEST_THAT(!SearchDir(*dir, "xx_not_this_dir_22")); + TEST_THAT(!SearchDir(*dir, "somefile.excludethis")); + + // client->QueryFinished(); + } + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_read_error_reporting() +{ + SETUP_WITH_BBSTORED(); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); +#else + if(::getuid() == 0) + { + BOX_NOTICE("skipping test because we're running as root"); + // These tests only work as non-root users. + } + else + { + // TODO FIXME detent + { + // Check that the error has not been reported yet + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that read errors are reported neatly + BOX_INFO("Add unreadable files"); + + { + // Dir and file which can't be read + TEST_THAT(::mkdir("testfiles/TestDir1/sub23", + 0755) == 0); + TEST_THAT(::mkdir("testfiles/TestDir1/sub23" + "/read-fail-test-dir", 0000) == 0); + int fd = ::open("testfiles/TestDir1" + "/read-fail-test-file", + O_CREAT | O_WRONLY, 0000); + TEST_THAT(fd != -1); + ::close(fd); + } + + // Wait and test... with sysadmin notification + bbackupd.RunSyncNowWithExceptionHandling(); + + // should fail with an error due to unreadable file + TEST_COMPARE(Compare_Error); + + // Check that it was reported correctly + TEST_THAT(TestFileExists("testfiles/notifyran.read-error.1")); + + // Check that the error was only reported once + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + + // Set permissions on file and dir to stop + // errors in the future + TEST_THAT(::chmod("testfiles/TestDir1/sub23" + "/read-fail-test-dir", 0770) == 0); + TEST_THAT(::chmod("testfiles/TestDir1" + "/read-fail-test-file", 0770) == 0); + } + } +#endif + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_continuously_updated_file() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + + // TODO FIXME dedent + { + // Make sure everything happens at the same point in the + // sync cycle: wait until exactly the start of a sync + wait_for_sync_start(); + + // Then wait a second, to make sure the scan is complete + ::safe_sleep(1); + + { + BOX_INFO("Open a file, then save something to it " + "every second for 12 seconds"); + for(int l = 0; l < 12; ++l) + { + FILE *f = ::fopen("testfiles/TestDir1/continousupdate", "w+"); + TEST_THAT(f != 0); + fprintf(f, "Loop iteration %d\n", l); + fflush(f); + fclose(f); + safe_sleep(1); + } + + // Check there's a difference + int compareReturnValue = ::system("perl testfiles/" + "extcheck1.pl"); + + TEST_RETURN(compareReturnValue, 1); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + + BOX_INFO("Keep on continuously updating file for " + "28 seconds, check it is uploaded eventually"); + + for(int l = 0; l < 28; ++l) + { + FILE *f = ::fopen("testfiles/TestDir1/" + "continousupdate", "w+"); + TEST_THAT(f != 0); + fprintf(f, "Loop 2 iteration %d\n", l); + fflush(f); + fclose(f); + safe_sleep(1); + } + + compareReturnValue = ::system("perl testfiles/" + "extcheck2.pl"); + + TEST_RETURN(compareReturnValue, 1); + TestRemoteProcessMemLeaks("bbackupquery.memleaks"); + } + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_delete_dir_change_attribute() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Delete a directory +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\x1") == 0); +#else + TEST_THAT(::system("rm -r testfiles/TestDir1/x1") == 0); +#endif + // Change attributes on an existing file. +#ifdef WIN32 + TEST_EQUAL(0, system("chmod 0423 testfiles/TestDir1/df9834.dsf")); +#else + TEST_THAT(::chmod("testfiles/TestDir1/df9834.dsf", 0423) == 0); +#endif + + TEST_COMPARE(Compare_Different); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_restore_files_and_directories() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + int64_t deldirid = 0; + int64_t restoredirid = 0; + { + // connect and log in + std::auto_ptr client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + + // Find the ID of the Test1 directory + restoredirid = GetDirID(*client, "Test1", + BackupProtocolListDirectory::RootDirectory); + TEST_THAT_OR(restoredirid != 0, FAIL); + + // Test the restoration + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1" /* remote */, + "testfiles/restore-Test1" /* local */, + true /* print progress dots */, + false /* restore deleted */, + false /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_Complete); + + // On Win32 we can't open another connection + // to the server, so we'll compare later. + + // Make sure you can't restore a restored directory + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-Test1", + true /* print progress dots */, + false /* restore deleted */, + false /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_TargetExists); + + // Find ID of the deleted directory + deldirid = GetDirID(*client, "x1", restoredirid); + TEST_THAT(deldirid != 0); + + // Just check it doesn't bomb out -- will check this + // properly later (when bbackupd is stopped) + TEST_THAT(BackupClientRestore(*client, deldirid, + "Test1", "testfiles/restore-Test1-x1", + true /* print progress dots */, + true /* restore deleted */, + false /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_Complete); + + // Make sure you can't restore to a nonexistant path + printf("\n==== Try to restore to a path " + "that doesn't exist\n"); + fflush(stdout); + + { + Logger::LevelGuard(Logging::GetConsole(), + Log::FATAL); + TEST_THAT(BackupClientRestore(*client, + restoredirid, "Test1", + "testfiles/no-such-path/subdir", + true /* print progress dots */, + true /* restore deleted */, + false /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_TargetPathNotFound); + } + + // Log out + client->QueryFinished(); + } + + // Compare the restored files + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_compare_detects_attribute_changes() +{ + SETUP_WITH_BBSTORED(); + +#ifndef WIN32 + BOX_NOTICE("skipping test on this platform"); + // requires openfile(), GetFileTime() and attrib.exe +#else + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // TODO FIXME dedent + { + // make one of the files read-only, expect a compare failure + int exit_status = ::system("attrib +r " + "testfiles\\TestDir1\\f1.dat"); + TEST_RETURN(exit_status, 0); + + TEST_COMPARE(Compare_Different); + + // set it back, expect no failures + exit_status = ::system("attrib -r " + "testfiles\\TestDir1\\f1.dat"); + TEST_RETURN(exit_status, 0); + + TEST_COMPARE(Compare_Same); + + // change the timestamp on a file, expect a compare failure + const char* testfile = "testfiles\\TestDir1\\f1.dat"; + HANDLE handle = openfile(testfile, O_RDWR, 0); + TEST_THAT(handle != INVALID_HANDLE_VALUE); + + FILETIME creationTime, lastModTime, lastAccessTime; + TEST_THAT(GetFileTime(handle, &creationTime, &lastAccessTime, + &lastModTime) != 0); + TEST_THAT(CloseHandle(handle)); + + FILETIME dummyTime = lastModTime; + dummyTime.dwHighDateTime -= 100; + + // creation time is backed up, so changing it should cause + // a compare failure + TEST_THAT(set_file_time(testfile, dummyTime, lastModTime, + lastAccessTime)); + + TEST_COMPARE(Compare_Different); + + // last access time is not backed up, so it cannot be compared + TEST_THAT(set_file_time(testfile, creationTime, lastModTime, + dummyTime)); + TEST_COMPARE(Compare_Same); + + // last write time is backed up, so changing it should cause + // a compare failure + TEST_THAT(set_file_time(testfile, creationTime, dummyTime, + lastAccessTime)); + TEST_COMPARE(Compare_Different); + + // set back to original values, check that compare succeeds + TEST_THAT(set_file_time(testfile, creationTime, lastModTime, + lastAccessTime)); + TEST_COMPARE(Compare_Same); + } +#endif // WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_sync_new_files() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + // Add some more files and modify others. Use the m flag this + // time so they have a recent modification time. + TEST_THAT(unpack_files("test3", "testfiles", "m")); + + // OpenBSD's tar interprets the "-m" option quite differently: + // it sets the time to epoch zero (1 Jan 1970) instead of the + // current time, which doesn't help us. So reset the timestamp + // on a file with the touch command, so it won't be backed up. + TEST_RETURN(::system("touch testfiles/TestDir1/chsh"), 0); + + // At least one file is too new to be backed up on the first run. + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Different); + + wait_for_operation(5, "newly added files to be old enough"); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_rename_operations() +{ + SETUP_WITH_BBSTORED(); + + TEST_THAT(unpack_files("test2")); + TEST_THAT(unpack_files("test3")); + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // TODO FIXME dedent + { + BOX_INFO("Rename directory"); + TEST_THAT(rename("testfiles/TestDir1/sub23/dhsfdss", + "testfiles/TestDir1/renamed-dir") == 0); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + // and again, but with quick flag + TEST_COMPARE(Compare_Same, "", "-acqQ"); + + // Rename some files -- one under the threshold, others above + TEST_THAT(rename("testfiles/TestDir1/df324", + "testfiles/TestDir1/df324-ren") == 0); + TEST_THAT(rename("testfiles/TestDir1/sub23/find2perl", + "testfiles/TestDir1/find2perl-ren") == 0); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check that modifying files with madly in the future timestamps still get added +bool test_sync_files_with_timestamps_in_future() +{ + SETUP_WITH_BBSTORED(); + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + { + TEST_THAT(::mkdir("testfiles/TestDir1/sub23", + 0755) == 0); + FILE *f = fopen("testfiles/TestDir1/sub23/" + "in-the-future", "w"); + TEST_THAT_OR(f != 0, FAIL); + fprintf(f, "Back to the future!\n"); + fclose(f); + // and then move the time forwards! + struct timeval times[2]; + BoxTimeToTimeval(GetCurrentBoxTime() + + SecondsToBoxTime((time_t)(365*24*60*60)), + times[1]); + times[0] = times[1]; + TEST_THAT(::utimes("testfiles/TestDir1/sub23/" + "in-the-future", times) == 0); + } + + // Wait and test + bbackupd.RunSyncNow(); + wait_for_backup_operation("bbackup to sync future file"); + TEST_COMPARE(Compare_Same); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +// Check change of store marker pauses daemon +bool test_changing_client_store_marker_pauses_daemon() +{ + SETUP_WITH_BBSTORED(); + TEST_THAT(StartClient()); + + // Wait for the client to upload all current files. We also time + // approximately how long a sync takes. + box_time_t sync_start_time = GetCurrentBoxTime(); + sync_and_wait(); + box_time_t sync_time = GetCurrentBoxTime() - sync_start_time; + + // Time how long a compare takes. On NetBSD it's 3 seconds, and that + // interferes with test timing unless we account for it. + box_time_t compare_start_time = GetCurrentBoxTime(); + // There should be no differences right now (yet). + TEST_COMPARE(Compare_Same); + box_time_t compare_time = GetCurrentBoxTime() - compare_start_time; + BOX_TRACE("Compare takes " << BOX_FORMAT_MICROSECONDS(compare_time)); + + // Wait for the end of another sync, to give us ~3 seconds to change + // the client store marker. + wait_for_sync_end(); + + // TODO FIXME dedent + { + // Then... connect to the server, and change the + // client store marker. See what that does! + { + bool done = false; + int tries = 4; + while(!done && tries > 0) + { + try + { + std::auto_ptr + protocol = connect_to_bbstored(sTlsContext); + // Make sure the marker isn't zero, + // because that's the default, and + // it should have changed + std::auto_ptr loginConf(protocol->QueryLogin(0x01234567, 0)); + TEST_THAT(loginConf->GetClientStoreMarker() != 0); + + // Change it to something else + BOX_INFO("Changing client store marker " + "from " << loginConf->GetClientStoreMarker() << + " to 12"); + protocol->QuerySetClientStoreMarker(12); + + // Success! + done = true; + + // Log out + protocol->QueryFinished(); + } + catch(BoxException &e) + { + BOX_INFO("Failed to connect to bbstored, " + << tries << " retries remaining: " + << e.what()); + tries--; + } + catch(...) + { + tries--; + } + } + TEST_THAT(done); + } + + // Make a change to a file, to detect whether or not + // it's hanging around waiting to retry. + { + FILE *f = ::fopen("testfiles/TestDir1/fileaftermarker", "w"); + TEST_THAT(f != 0); + ::fprintf(f, "Lovely file you got there."); + ::fclose(f); + } + + // Wait for bbackupd to detect the problem. + wait_for_sync_end(); + + // Test that there *are* differences still, i.e. that bbackupd + // didn't successfully run a backup during that time. + BOX_TRACE("Compare starting, expecting differences"); + TEST_COMPARE(Compare_Different); + BOX_TRACE("Compare finished, expected differences"); + + // Wait out the expected delay in bbackupd. This is quite + // time-sensitive, so we use sub-second precision. + box_time_t wait = + SecondsToBoxTime(BACKUP_ERROR_DELAY_SHORTENED - 1) - + compare_time * 2; + BOX_TRACE("Waiting for " << BOX_FORMAT_MICROSECONDS(wait) << + " (plus another compare taking " << + BOX_FORMAT_MICROSECONDS(compare_time) << ") until " + "just before bbackupd recovers"); + ShortSleep(wait, true); + + // bbackupd should not have recovered yet, so there should + // still be differences. + BOX_TRACE("Compare starting, expecting differences"); + TEST_COMPARE(Compare_Different); + BOX_TRACE("Compare finished, expected differences"); + + // Now wait for it to recover and finish a sync, and check + // that the differences are gone (successful backup). + wait = sync_time + SecondsToBoxTime(2); + BOX_TRACE("Waiting for " << BOX_FORMAT_MICROSECONDS(wait) << + " until just after bbackupd recovers and finishes a sync"); + ShortSleep(wait, true); + + BOX_TRACE("Compare starting, expecting no differences"); + TEST_COMPARE(Compare_Same); + BOX_TRACE("Compare finished, expected no differences"); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_interrupted_restore_can_be_recovered() +{ + SETUP_WITH_BBSTORED(); + +#ifdef WIN32 + BOX_NOTICE("skipping test on this platform"); +#else + bbackupd.RunSyncNow(); + + // TODO FIXME dedent + { + { + std::auto_ptr client = + connect_and_login(sTlsContext, + BackupProtocolLogin::Flags_ReadOnly); + + // Find the ID of the Test1 directory + int64_t restoredirid = GetDirID(*client, "Test1", + BackupProtocolListDirectory::RootDirectory); + TEST_THAT_OR(restoredirid != 0, FAIL); + + do_interrupted_restore(sTlsContext, restoredirid); + int64_t resumesize = 0; + TEST_THAT(FileExists("testfiles/" + "restore-interrupt.boxbackupresume", + &resumesize)); + // make sure it has recorded something to resume + TEST_THAT(resumesize > 16); + + // Check that the restore fn returns resume possible, + // rather than doing anything + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", + true /* print progress dots */, + false /* restore deleted */, + false /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_ResumePossible); + + // Then resume it + TEST_THAT(BackupClientRestore(*client, restoredirid, + "Test1", "testfiles/restore-interrupt", + true /* print progress dots */, + false /* restore deleted */, + false /* undelete after */, + true /* resume */, + false /* keep going */) + == Restore_Complete); + + client->QueryFinished(); + client.reset(); + + // Then check it has restored the correct stuff + TEST_COMPARE(Compare_Same); + } + } +#endif // !WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} + +bool assert_x1_deleted_or_not(bool expected_deleted) +{ + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + + std::auto_ptr dir = ReadDirectory(*client); + int64_t testDirId = SearchDir(*dir, "Test1"); + TEST_THAT_OR(testDirId != 0, return false); + + dir = ReadDirectory(*client, testDirId); + BackupStoreDirectory::Iterator i(*dir); + BackupStoreFilenameClear child("x1"); + BackupStoreDirectory::Entry *en = i.FindMatchingClearName(child); + TEST_THAT_OR(en != 0, return false); + TEST_EQUAL_OR(expected_deleted, en->IsDeleted(), return false); + + return true; +} + +bool test_restore_deleted_files() +{ + SETUP_WITH_BBSTORED(); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + TEST_THAT(::unlink("testfiles/TestDir1/f1.dat") == 0); +#ifdef WIN32 + TEST_THAT(::system("rd /s/q testfiles\\TestDir1\\x1") == 0); +#else + TEST_THAT(::system("rm -r testfiles/TestDir1/x1") == 0); +#endif + TEST_COMPARE(Compare_Different); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + TEST_THAT(assert_x1_deleted_or_not(true)); + + // TODO FIXME dedent + { + { + std::auto_ptr client = + connect_and_login(sTlsContext, 0 /* read-write */); + + // Find the ID of the Test1 directory + int64_t restoredirid = GetDirID(*client, "Test1", + BackupProtocolListDirectory::RootDirectory); + TEST_THAT_OR(restoredirid != 0, FAIL); + + // Find ID of the deleted directory + int64_t deldirid = GetDirID(*client, "x1", restoredirid); + TEST_THAT_OR(deldirid != 0, FAIL); + + // Do restore and undelete + TEST_THAT(BackupClientRestore(*client, deldirid, + "Test1", "testfiles/restore-Test1-x1-2", + true /* print progress dots */, + true /* deleted files */, + true /* undelete after */, + false /* resume */, + false /* keep going */) + == Restore_Complete); + + client->QueryFinished(); + client.reset(); + + // Do a compare with the now undeleted files + TEST_COMPARE(Compare_Same, "", "-cEQ Test1/x1 " + "testfiles/restore-Test1-x1-2"); + } + + // Final check on notifications + TEST_THAT(!TestFileExists("testfiles/notifyran.store-full.2")); + TEST_THAT(!TestFileExists("testfiles/notifyran.read-error.2")); + } + + // should have been undeleted by restore + TEST_THAT(assert_x1_deleted_or_not(false)); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_locked_file_behaviour() +{ + SETUP_WITH_BBSTORED(); + +#ifndef WIN32 + // There are no tests for mandatory locks on non-Windows platforms yet. + BOX_NOTICE("skipping test on this platform"); +#else + // TODO FIXME dedent + { + // Test that locked files cannot be backed up, + // and the appropriate error is reported. + + HANDLE handle = openfile("testfiles/TestDir1/f1.dat", + BOX_OPEN_LOCK, 0); + TEST_THAT_OR(handle != INVALID_HANDLE_VALUE, FAIL); + + { + // this sync should try to back up the file, + // and fail, because it's locked + bbackupd.RunSyncNowWithExceptionHandling(); + TEST_THAT(TestFileExists("testfiles/" + "notifyran.read-error.1")); + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.read-error.2")); + } + + { + // now close the file and check that it is + // backed up on the next run. + CloseHandle(handle); + bbackupd.RunSyncNow(); + + // still no read errors? + TEST_THAT(!TestFileExists("testfiles/" + "notifyran.read-error.2")); + TEST_COMPARE(Compare_Same); + } + + { + // open the file again, compare and check that compare + // reports the correct error message (and finishes) + handle = openfile("testfiles/TestDir1/f1.dat", + BOX_OPEN_LOCK, 0); + TEST_THAT_OR(handle != INVALID_HANDLE_VALUE, FAIL); + + TEST_COMPARE(Compare_Error); + + // close the file again, check that compare + // works again + CloseHandle(handle); + TEST_COMPARE(Compare_Same); + } + } +#endif // WIN32 + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_backup_many_files() +{ + SETUP_WITH_BBSTORED(); + + unpack_files("test2"); + unpack_files("test3"); + unpack_files("testexclude"); + unpack_files("spacetest1", "testfiles/TestDir1"); + unpack_files("spacetest2", "testfiles/TestDir1"); + + bbackupd.RunSyncNow(); + TEST_COMPARE(Compare_Same); + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_parse_incomplete_command() +{ + SETUP_TEST_BBACKUPD(); + + { + // This is not a complete command, it should not parse! + BackupQueries::ParsedCommand cmd("-od", true); + TEST_THAT(cmd.mFailed); + TEST_EQUAL((void *)NULL, cmd.pSpec); + TEST_EQUAL(0, cmd.mCompleteArgCount); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +bool test_parse_syncallowscript_output() +{ + SETUP_TEST_BBACKUPD(); + + { + 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()); + } + + TEARDOWN_TEST_BBACKUPD(); +} + +int test(int argc, const char *argv[]) +{ + // SSL library + SSLLib::Initialise(); + + // Keys for subsystems + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + + { + std::string errs; + std::auto_ptr config( + Configuration::LoadAndVerify + ("testfiles/bbstored.conf", &BackupConfigFileVerify, errs)); + TEST_EQUAL_LINE(0, errs.size(), "Loading configuration file " + "reported errors: " << errs); + TEST_THAT_OR(config.get(), return 1); + // Initialise the raid file controller + RaidFileController &rcontroller(RaidFileController::GetController()); + rcontroller.Initialise(config->GetKeyValue("RaidFileConf").c_str()); + } + + sTlsContext.Initialise(false /* client */, + "testfiles/clientCerts.pem", + "testfiles/clientPrivKey.pem", + "testfiles/clientTrustedCAs.pem"); + + TEST_THAT(test_basics()); + TEST_THAT(test_readdirectory_on_nonexistent_dir()); + TEST_THAT(test_bbackupquery_parser_escape_slashes()); + TEST_THAT(test_getobject_on_nonexistent_file()); + // TEST_THAT(test_replace_zero_byte_file_with_nonzero_byte_file()); + TEST_THAT(test_backup_disappearing_directory()); + TEST_THAT(test_ssl_keepalives()); + TEST_THAT(test_backup_hardlinked_files()); + TEST_THAT(test_backup_pauses_when_store_is_full()); + TEST_THAT(test_bbackupd_exclusions()); + TEST_THAT(test_bbackupd_uploads_files()); + TEST_THAT(test_bbackupd_responds_to_connection_failure()); + TEST_THAT(test_absolute_symlinks_not_followed_during_restore()); + TEST_THAT(test_initially_missing_locations_are_not_forgotten()); + TEST_THAT(test_redundant_locations_deleted_on_time()); + TEST_THAT(test_read_only_dirs_can_be_restored()); + TEST_THAT(test_unicode_filenames_can_be_backed_up()); + TEST_THAT(test_sync_allow_script_can_pause_backup()); + TEST_THAT(test_delete_update_and_symlink_files()); + TEST_THAT(test_store_error_reporting()); + TEST_THAT(test_change_file_to_symlink_and_back()); + TEST_THAT(test_file_rename_tracking()); + TEST_THAT(test_upload_very_old_files()); + TEST_THAT(test_excluded_files_are_not_backed_up()); + TEST_THAT(test_read_error_reporting()); + TEST_THAT(test_continuously_updated_file()); + TEST_THAT(test_delete_dir_change_attribute()); + TEST_THAT(test_restore_files_and_directories()); + TEST_THAT(test_compare_detects_attribute_changes()); + TEST_THAT(test_sync_new_files()); + TEST_THAT(test_rename_operations()); + TEST_THAT(test_sync_files_with_timestamps_in_future()); + TEST_THAT(test_changing_client_store_marker_pauses_daemon()); + TEST_THAT(test_interrupted_restore_can_be_recovered()); + TEST_THAT(test_restore_deleted_files()); + TEST_THAT(test_locked_file_behaviour()); + TEST_THAT(test_backup_many_files()); + TEST_THAT(test_parse_incomplete_command()); + TEST_THAT(test_parse_syncallowscript_output()); + + TEST_THAT(kill_running_daemons()); + +#ifndef WIN32 + if(::getuid() == 0) + { + BOX_WARNING("This test was run as root. Some tests have been omitted."); + } +#endif + + return finish_test_suite(); +} diff --git a/test/bbackupd/testextra b/test/bbackupd/testextra new file mode 100644 index 00000000..798c8c67 --- /dev/null +++ b/test/bbackupd/testextra @@ -0,0 +1,4 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data diff --git a/test/bbackupd/testfiles/accounts.txt b/test/bbackupd/testfiles/accounts.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/bbackupd/testfiles/bbackupd-exclude.conf.in b/test/bbackupd/testfiles/bbackupd-exclude.conf.in new file mode 100644 index 00000000..4c08753f --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-exclude.conf.in @@ -0,0 +1,47 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +BackupLocations +{ + Test1 + { + Path = testfiles/TestDir1 + ExcludeDir = testfiles/TestDir1/spacetest/d3 + ExcludeFile = testfiles/TestDir1/spacetest/f2 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd-snapshot.conf.in b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in new file mode 100644 index 00000000..73b50c6e --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-snapshot.conf.in @@ -0,0 +1,57 @@ + +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 = 0 +BackupErrorDelay = 10 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.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 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd-symlink.conf.in b/test/bbackupd/testfiles/bbackupd-symlink.conf.in new file mode 100644 index 00000000..33bb6157 --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd-symlink.conf.in @@ -0,0 +1,55 @@ + +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 + +UpdateStoreInterval = 3 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.pl + +Server +{ + PidFile = testfiles/bbackupd.pid +} + +BackupLocations +{ + Test1 + { + Path = testfiles/symlink-to-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 + } +} + 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/bbackupd/testfiles/bbackupd.conf.in b/test/bbackupd/testfiles/bbackupd.conf.in new file mode 100644 index 00000000..f0080c4a --- /dev/null +++ b/test/bbackupd/testfiles/bbackupd.conf.in @@ -0,0 +1,56 @@ + +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 + +UpdateStoreInterval = 3 +BackupErrorDelay = 10 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = @TARGET_PERL@ testfiles/notifyscript.pl +SyncAllowScript = @TARGET_PERL@ testfiles/syncallowscript.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 + } +} + diff --git a/test/bbackupd/testfiles/bbackupd.keys b/test/bbackupd/testfiles/bbackupd.keys new file mode 100644 index 00000000..d9135b97 Binary files /dev/null and b/test/bbackupd/testfiles/bbackupd.keys differ diff --git a/test/bbackupd/testfiles/bbstored.conf b/test/bbackupd/testfiles/bbstored.conf new file mode 100644 index 00000000..87f4fe6b --- /dev/null +++ b/test/bbackupd/testfiles/bbstored.conf @@ -0,0 +1,17 @@ + +RaidFileConf = testfiles/raidfile.conf +AccountDatabase = testfiles/accounts.txt + +ExtendedLogging = no + +TimeBetweenHousekeeping = 5 + +Server +{ + PidFile = testfiles/bbstored.pid + ListenAddresses = inet:localhost:22011 + CertificateFile = testfiles/serverCerts.pem + PrivateKeyFile = testfiles/serverPrivKey.pem + TrustedCAsFile = testfiles/serverTrustedCAs.pem +} + diff --git a/test/bbackupd/testfiles/clientCerts.pem b/test/bbackupd/testfiles/clientCerts.pem new file mode 100644 index 00000000..c1f14fa7 --- /dev/null +++ b/test/bbackupd/testfiles/clientCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBmDCCAQECAQMwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMDRaFw0zMTAyMjIwOTAwMDRaMBoxGDAWBgNVBAMTD0JBQ0tVUC0w +MTIzNDU2NzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAvptM6A++ZdkxYN92 +OI6d0O32giRdybSdUVNJk09V1pdVJFhXr4owhtVv6d8yDnPaNOgS1LlxZ9CHcR5A +LtFwI9wmGHBc5a2uFCZGORTaSggntCythvRV3DGm/fU7mRME7Le1/tWWxjycnk2k +Rez6d7Ffj56SXDFoxY2dK8MwRasCAwEAATANBgkqhkiG9w0BAQUFAAOBgQB4D3LU +knCM4UZHMJhlbGnvc+N4O5SGrNKrHs94juMF8dPXJNgboBflkYJKNx1qDf47C/Cx +hxXjju2ucGHytNQ8kiWsz7vCzeS7Egkl0QhFcBcYVCeXNn7zc34aAUyVlLCuas2o +EGpfF4se7D3abg7J/3ioW0hx8bSal7kROleKCQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/clientPrivKey.pem b/test/bbackupd/testfiles/clientPrivKey.pem new file mode 100644 index 00000000..34b1af2a --- /dev/null +++ b/test/bbackupd/testfiles/clientPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQC+m0zoD75l2TFg33Y4jp3Q7faCJF3JtJ1RU0mTT1XWl1UkWFev +ijCG1W/p3zIOc9o06BLUuXFn0IdxHkAu0XAj3CYYcFzlra4UJkY5FNpKCCe0LK2G +9FXcMab99TuZEwTst7X+1ZbGPJyeTaRF7Pp3sV+PnpJcMWjFjZ0rwzBFqwIDAQAB +AoGAMW8Lqh/zLG0A/nPWMGLkkTw2M5iE7nw2VNI6AceQpqAHB+8VhsRbQ4z1gn1N +eSwYyqHpyFv0Co2touvKj5nn8CJfMmm571cvdOlD/n/mQsW+xZqd9WmvSE8Jh4Qq +iOQqwbwJlTYTV4BEo90qtfR+MDqffSCB8bHh4l3oO3fSp4kCQQDgbllQeq2kwlLp +81oDfrk+J7vpiq9hZ/HxFY1fZAOa6iylazZz0JSzvNAtQNLI1LeKAzBc8FuPPSG9 +qSHAKoDHAkEA2Wrziib5OgY/G86yAWVn2hPM7Ky6wGtsJxYnObXUiTwVM7lM1nZU +LpQaq//vzVDcWggqyEBTYkVcdEPYIJn3/QJBAL3e/bblowRx1p3Q4MV2L5gTG5pQ +V2HsA7c3yZv7TEWCenUUSEQhIb0SL3kpj2qS9BhR7FekjYGYcXQ4o7IlAz8CQD1B +BJxHnq/aUq1i7oO2Liwip/mGMJdFrJLWivaXY+nGI7MO4bcKX21ADMOot8cAoRQ8 +eNEyTkvBfurCsoF834ECQCPejz6x1bh/H7SeeANP17HKlwx1Lshw2JzxfF96MA26 +Eige4f0ttKHhMY/bnMcOzfPUSe/LvIN3AiMtphkl0pw= +-----END RSA PRIVATE KEY----- diff --git a/test/bbackupd/testfiles/clientTrustedCAs.pem b/test/bbackupd/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/bbackupd/testfiles/clientTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/extcheck1.pl.in b/test/bbackupd/testfiles/extcheck1.pl.in new file mode 100755 index 00000000..c80ddc66 --- /dev/null +++ b/test/bbackupd/testfiles/extcheck1.pl.in @@ -0,0 +1,61 @@ +#!@PERL@ +use strict; + +use File::Spec; + +my $flags = $ARGV[0] or ""; +my $bbackupquery = File::Spec->catfile('..', '..', 'bin', 'bbackupquery', 'bbackupquery'); + +unless(open IN, "$bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "-l testfiles/query4.log " . + "\"compare -ac$flags\" quit 2>&1 |") +{ + print "FAIL: opening compare utility\n"; + exit 2; +} + +my $ret = 1; +my $seen = 0; + +while() +{ + next unless m/\S/; + print "READ: $_"; + + if (m/continousupdate/) + { + unless (/exists/) + { + print "FAIL: continousupdate line does not match\n"; + $ret = 2; + } + $seen = 1; + } + elsif (m/^No entry for terminal type/ or + m/^using dumb terminal settings/) + { + # skip these lines, may happen in Debian buildd + # with no terminal. + } + else + { + unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ + or /probably due to file mod/) + { + print "FAIL: Summary line does not match\n"; + $ret = 2; + } + } +} + +close IN; + +unless ($seen) +{ + print "FAIL: missing line matching continousupdate\n"; + $ret = 2; +} + +exit $ret; + diff --git a/test/bbackupd/testfiles/extcheck2.pl.in b/test/bbackupd/testfiles/extcheck2.pl.in new file mode 100755 index 00000000..02c258f8 --- /dev/null +++ b/test/bbackupd/testfiles/extcheck2.pl.in @@ -0,0 +1,53 @@ +#!@PERL@ +use strict; + +use File::Spec; + +my $flags = $ARGV[0] or ""; +my $bbackupquery = File::Spec->catfile('..', '..', 'bin', 'bbackupquery', 'bbackupquery'); + +unless(open IN, "$bbackupquery -Wwarning " . + "-c testfiles/bbackupd.conf " . + "-l testfiles/query4.log " . + "\"compare -ac$flags\" quit 2>&1 |") +{ + print "Couldn't open compare utility\n"; + exit 2; +} + +my $ret = 1; + +while() +{ + next unless m/\S/; + print "READ: $_"; + + if (m/continousupdate/) + { + unless (m/contents/ or m/attributes/) + { + print "FAIL: continuousupdate line does not match\n"; + $ret = 2; + } + } + elsif (m/^No entry for terminal type/ or + m/^using dumb terminal settings/) + { + # skip these lines, may happen in Debian buildd + # with no terminal. + } + else + { + unless (/\AWARNING/ or /\ADifferences/ or /might be reason/ + or /probably due to file mod/) + { + print "FAIL: summary line does not match\n"; + $ret = 2; + } + } +} + +close IN; + +exit $ret; + diff --git a/test/bbackupd/testfiles/notifyscript.pl.in b/test/bbackupd/testfiles/notifyscript.pl.in new file mode 100755 index 00000000..d3e324e9 --- /dev/null +++ b/test/bbackupd/testfiles/notifyscript.pl.in @@ -0,0 +1,24 @@ +#!@TARGET_PERL@ + +my $f = 'testfiles/notifyran.'.$ARGV[0].'.'; + +if (-e 'testfiles/notifyscript.tag') +{ + open FILE, '< testfiles/notifyscript.tag' or die $!; + my $tag = ; + chomp $tag; + $f .= "$tag."; + close FILE; +} + +my $n = 1; + +while(-e $f.$n) +{ + $n ++; +} + +open FL,'>'.$f.$n; +print FL localtime(); +close FL; + diff --git a/test/bbackupd/testfiles/raidfile.conf b/test/bbackupd/testfiles/raidfile.conf new file mode 100644 index 00000000..641872b0 --- /dev/null +++ b/test/bbackupd/testfiles/raidfile.conf @@ -0,0 +1,10 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + diff --git a/test/bbackupd/testfiles/serverCerts.pem b/test/bbackupd/testfiles/serverCerts.pem new file mode 100644 index 00000000..92467618 --- /dev/null +++ b/test/bbackupd/testfiles/serverCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw +MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV +I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 +cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH +RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ +myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 +SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 +HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/serverPrivKey.pem b/test/bbackupd/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..fd87607d --- /dev/null +++ b/test/bbackupd/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP +cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 +bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB +AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX +P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT +LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l +4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN +8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t +Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX +I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl +XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 +q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE +xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn +-----END RSA PRIVATE KEY----- diff --git a/test/bbackupd/testfiles/serverTrustedCAs.pem b/test/bbackupd/testfiles/serverTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/bbackupd/testfiles/serverTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/bbackupd/testfiles/spacetest1.tgz b/test/bbackupd/testfiles/spacetest1.tgz new file mode 100644 index 00000000..c653c0ae Binary files /dev/null and b/test/bbackupd/testfiles/spacetest1.tgz differ diff --git a/test/bbackupd/testfiles/spacetest2.tgz b/test/bbackupd/testfiles/spacetest2.tgz new file mode 100644 index 00000000..aa47312d Binary files /dev/null and b/test/bbackupd/testfiles/spacetest2.tgz differ diff --git a/test/bbackupd/testfiles/syncallowscript.pl.in b/test/bbackupd/testfiles/syncallowscript.pl.in new file mode 100755 index 00000000..f2ef1171 --- /dev/null +++ b/test/bbackupd/testfiles/syncallowscript.pl.in @@ -0,0 +1,33 @@ +#!@TARGET_PERL@ + +use strict; +use warnings; + +my $control_file = 'testfiles/syncallowscript.control'; +if (! -r $control_file) +{ + print "now\n"; + exit 0; +} + +my $control_state; +open CONTROL, "< $control_file" or die "$control_file: $!"; +$control_state = ; +defined $control_state or die "$control_file: read failed: $!"; +close CONTROL; + +my $marker_file_root = 'testfiles/syncallowscript.notifyran.'; +my $n = 1; +my $marker_file; + +while($marker_file = $marker_file_root.$n and -e $marker_file) +{ + $n ++; +} + +open FL,'>'.$marker_file or die "$marker_file: $!"; +print FL localtime(); +close FL; + +print $control_state; +exit 0; diff --git a/test/bbackupd/testfiles/test2.tgz b/test/bbackupd/testfiles/test2.tgz new file mode 100644 index 00000000..ac7f18af Binary files /dev/null and b/test/bbackupd/testfiles/test2.tgz differ diff --git a/test/bbackupd/testfiles/test3.tgz b/test/bbackupd/testfiles/test3.tgz new file mode 100644 index 00000000..c7d60cd7 Binary files /dev/null and b/test/bbackupd/testfiles/test3.tgz differ diff --git a/test/bbackupd/testfiles/test_base.tgz b/test/bbackupd/testfiles/test_base.tgz new file mode 100644 index 00000000..9c8ddfc0 Binary files /dev/null and b/test/bbackupd/testfiles/test_base.tgz differ diff --git a/test/bbackupd/testfiles/testexclude.tgz b/test/bbackupd/testfiles/testexclude.tgz new file mode 100644 index 00000000..ac7329d8 Binary files /dev/null and b/test/bbackupd/testfiles/testexclude.tgz differ diff --git a/test/common/testcommon.cpp b/test/common/testcommon.cpp new file mode 100644 index 00000000..fbdf8d9c --- /dev/null +++ b/test/common/testcommon.cpp @@ -0,0 +1,924 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcommon.cpp +// Purpose: Tests for the code in lib/common +// Created: 2003/07/23 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#include "Test.h" +#include "Configuration.h" +#include "FdGetLine.h" +#include "Guards.h" +#include "FileStream.h" +#include "InvisibleTempFileStream.h" +#include "IOStreamGetLine.h" +#include "NamedLock.h" +#include "ReadGatherStream.h" +#include "MemBlockStream.h" +#include "ExcludeList.h" +#include "CommonException.h" +#include "Conversion.h" +#include "autogen_ConversionException.h" +#include "CollectInBufferStream.h" +#include "Archive.h" +#include "Timer.h" +#include "Logging.h" +#include "ZeroStream.h" +#include "PartialReadStream.h" + +#include "MemLeakFindOn.h" + +using namespace BoxConvert; + +void test_conversions() +{ + TEST_THAT((Convert(std::string("32"))) == 32); + TEST_THAT((Convert("42")) == 42); + TEST_THAT((Convert("-42")) == -42); + TEST_CHECK_THROWS((Convert("500")), ConversionException, IntOverflowInConvertFromString); + TEST_CHECK_THROWS((Convert("pants")), ConversionException, BadStringRepresentationOfInt); + TEST_CHECK_THROWS((Convert("")), ConversionException, CannotConvertEmptyStringToInt); + + std::string a(Convert(63)); + TEST_THAT(a == "63"); + std::string b(Convert(-3473463)); + TEST_THAT(b == "-3473463"); + std::string c(Convert(344)); + TEST_THAT(c == "344"); +} + +ConfigurationVerifyKey verifykeys1_1_1[] = +{ + ConfigurationVerifyKey("bing", ConfigTest_Exists), + ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("terrible", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerifyKey verifykeys1_1_2[] = +{ + ConfigurationVerifyKey("fish", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) +}; + + +ConfigurationVerify verifysub1_1[] = +{ + { + "*", + 0, + verifykeys1_1_1, + ConfigTest_Exists, + 0 + }, + { + "otherthing", + 0, + verifykeys1_1_2, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +ConfigurationVerifyKey verifykeys1_1[] = +{ + ConfigurationVerifyKey("value", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string1", ConfigTest_Exists), + ConfigurationVerifyKey("string2", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerifyKey verifykeys1_2[] = +{ + ConfigurationVerifyKey("carrots", ConfigTest_Exists | ConfigTest_IsInt), + ConfigurationVerifyKey("string", ConfigTest_Exists | ConfigTest_LastEntry) +}; + +ConfigurationVerify verifysub1[] = +{ + { + "test1", + verifysub1_1, + verifykeys1_1, + ConfigTest_Exists, + 0 + }, + { + "ping", + 0, + verifykeys1_2, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 + } +}; + +ConfigurationVerifyKey verifykeys1[] = +{ + ConfigurationVerifyKey("notExpected", 0), + ConfigurationVerifyKey("HasDefaultValue", 0, "Lovely default value"), + ConfigurationVerifyKey("MultiValue", ConfigTest_MultiValueAllowed), + ConfigurationVerifyKey("BoolTrue1", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolTrue2", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolFalse1", ConfigTest_IsBool), + ConfigurationVerifyKey("BoolFalse2", ConfigTest_IsBool), + ConfigurationVerifyKey("TOPlevel", + ConfigTest_LastEntry | ConfigTest_Exists) +}; + +ConfigurationVerify verify = +{ + "root", + verifysub1, + verifykeys1, + ConfigTest_Exists | ConfigTest_LastEntry, + 0 +}; + +class TestLogger : public Logger +{ + private: + bool mTriggered; + Log::Level mTargetLevel; + + public: + TestLogger(Log::Level targetLevel) + : mTriggered(false), mTargetLevel(targetLevel) + { + Logging::Add(this); + } + ~TestLogger() + { + Logging::Remove(this); + } + + bool IsTriggered() { return mTriggered; } + void Reset() { mTriggered = false; } + + virtual bool Log(Log::Level level, const std::string& file, int line, + const std::string& function, const Log::Category& category, + const std::string& message) + { + if (level == mTargetLevel) + { + mTriggered = true; + } + return true; + } + + virtual const char* GetType() { return "Test"; } + virtual void SetProgramName(const std::string& rProgramName) { } +}; + +int test(int argc, const char *argv[]) +{ + // Test PartialReadStream and ReadGatherStream handling of files + // over 2GB (refs #2) + { + char buffer[8]; + + ZeroStream zero(0x80000003); + zero.Seek(0x7ffffffe, IOStream::SeekType_Absolute); + TEST_THAT(zero.GetPosition() == 0x7ffffffe); + TEST_THAT(zero.Read(buffer, 8) == 5); + TEST_THAT(zero.GetPosition() == 0x80000003); + TEST_THAT(zero.Read(buffer, 8) == 0); + zero.Seek(0, IOStream::SeekType_Absolute); + TEST_THAT(zero.GetPosition() == 0); + + char* buffer2 = new char [0x1000000]; + TEST_THAT(buffer2 != NULL); + + PartialReadStream part(zero, 0x80000002); + for (int i = 0; i < 0x80; i++) + { + int read = part.Read(buffer2, 0x1000000); + TEST_THAT(read == 0x1000000); + } + TEST_THAT(part.Read(buffer, 8) == 2); + TEST_THAT(part.Read(buffer, 8) == 0); + + delete [] buffer2; + + ReadGatherStream gather(false); + zero.Seek(0, IOStream::SeekType_Absolute); + int component = gather.AddComponent(&zero); + gather.AddBlock(component, 0x80000002); + TEST_THAT(gather.Read(buffer, 8) == 8); + } + + // Test self-deleting temporary file streams + { + std::string tempfile("testfiles/tempfile"); + TEST_CHECK_THROWS(InvisibleTempFileStream fs(tempfile.c_str()), + CommonException, OSFileOpenError); + InvisibleTempFileStream fs(tempfile.c_str(), O_CREAT); + + #ifdef WIN32 + // file is still visible under Windows + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it again should work + InvisibleTempFileStream fs2(tempfile.c_str()); + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it to create should work + InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); + TEST_THAT(TestFileExists(tempfile.c_str())); + + // opening it to create exclusively should fail + TEST_CHECK_THROWS(InvisibleTempFileStream fs4(tempfile.c_str(), + O_CREAT | O_EXCL), CommonException, OSFileOpenError); + + fs2.Close(); + #else + // file is not visible under Unix + TEST_THAT(!TestFileExists(tempfile.c_str())); + + // opening it again should fail + TEST_CHECK_THROWS(InvisibleTempFileStream fs2(tempfile.c_str()), + CommonException, OSFileOpenError); + + // opening it to create should work + InvisibleTempFileStream fs3(tempfile.c_str(), O_CREAT); + TEST_THAT(!TestFileExists(tempfile.c_str())); + + // opening it to create exclusively should work + InvisibleTempFileStream fs4(tempfile.c_str(), O_CREAT | O_EXCL); + TEST_THAT(!TestFileExists(tempfile.c_str())); + + fs4.Close(); + #endif + + fs.Close(); + fs3.Close(); + + // now that it's closed, it should be invisible on all platforms + TEST_THAT(!TestFileExists(tempfile.c_str())); + } + + // Test that named locks work as expected + { + NamedLock lock1; + TEST_THAT(lock1.TryAndGetLock("testfiles/locktest")); + // With a lock held, we should not be able to acquire another. + TEST_THAT(!NamedLock().TryAndGetLock("testfiles/locktest")); + } + { + // But with the lock released, we should be able to. + TEST_THAT(NamedLock().TryAndGetLock("testfiles/locktest")); + } + + // Test that memory leak detection doesn't crash + { + char *test = new char[1024]; + delete [] test; + MemBlockStream *s = new MemBlockStream(test,12); + delete s; + } + +#ifdef BOX_MEMORY_LEAK_TESTING + { + Timers::Cleanup(); + + TEST_EQUAL(0, memleakfinder_numleaks()); + void *block = ::malloc(12); + TEST_EQUAL(1, memleakfinder_numleaks()); + void *b2 = ::realloc(block, 128*1024); + TEST_EQUAL(1, memleakfinder_numleaks()); + ::free(b2); + TEST_EQUAL(0, memleakfinder_numleaks()); + char *test = new char[1024]; + TEST_EQUAL(1, memleakfinder_numleaks()); + MemBlockStream *s = new MemBlockStream(test,12); + TEST_EQUAL(3, memleakfinder_numleaks()); + delete s; + TEST_EQUAL(1, memleakfinder_numleaks()); + delete [] test; + TEST_EQUAL(0, memleakfinder_numleaks()); + + Timers::Init(); + } +#endif // BOX_MEMORY_LEAK_TESTING + + // test main() initialises timers for us, so uninitialise them + Timers::Cleanup(); + + // Check that using timer methods without initialisation + // throws an assertion failure. Can only do this in debug mode + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Add(*(Timer*)NULL), + CommonException, AssertFailed); + TEST_CHECK_THROWS(Timers::Remove(*(Timer*)NULL), + CommonException, AssertFailed); + #endif + + // TEST_CHECK_THROWS(Timers::Signal(), CommonException, AssertFailed); + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, + AssertFailed); + #endif + + // Check that we can initialise the timers + Timers::Init(); + + // Check that double initialisation throws an exception + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Init(), CommonException, + AssertFailed); + #endif + + // Check that we can clean up the timers + Timers::Cleanup(); + + // Check that double cleanup throws an exception + #ifndef BOX_RELEASE_BUILD + TEST_CHECK_THROWS(Timers::Cleanup(), CommonException, + AssertFailed); + #endif + + Timers::Init(); + + Timer t0(0, "t0"); // should never expire + Timer t1(1000, "t1"); + Timer t2(2000, "t2"); + Timer t3(3000, "t3"); + + TEST_THAT(!t0.HasExpired()); + TEST_THAT(!t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(!t3.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(!t3.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(t2.HasExpired()); + TEST_THAT(!t3.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()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(!t2.HasExpired()); + TEST_THAT(t3.HasExpired()); + + safe_sleep(1); + TEST_THAT(!t0.HasExpired()); + TEST_THAT(t1.HasExpired()); + TEST_THAT(t2.HasExpired()); + TEST_THAT(t3.HasExpired()); + + // Leave timers initialised for rest of test. + // Test main() will cleanup after test finishes. + + static const char *testfilelines[] = + { + "First line", + "Second line", + "Third", + "", + "", + "", + "sdf hjjk", + "", + "test", + "test#not comment", + "test#not comment", + "", + "nice line", + "fish", + "", + "ping", + "", + "", + "Nothing", + "Nothing", + 0 + }; + + // First, test the FdGetLine class -- rather important this works! + { + FileHandleGuard file("testfiles" + DIRECTORY_SEPARATOR "fdgetlinetest.txt"); + FdGetLine getline(file); + + int l = 0; + while(testfilelines[l] != 0) + { + TEST_THAT(!getline.IsEOF()); + std::string line = getline.GetLine(true); + TEST_EQUAL(testfilelines[l], line); + l++; + } + TEST_THAT(getline.IsEOF()); + TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); + } + + // and again without pre-processing + { + FileHandleGuard file("testfiles" + DIRECTORY_SEPARATOR "fdgetlinetest.txt"); + FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", "r"); + TEST_THAT_ABORTONFAIL(file2 != 0); + FdGetLine getline(file); + char ll[512]; + + while(!feof(file2)) + { + fgets(ll, sizeof(ll), file2); + int e = strlen(ll); + while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) + { + e--; + } + ll[e] = '\0'; + + TEST_THAT(!getline.IsEOF()); + std::string line = getline.GetLine(false); + TEST_EQUAL(ll, line); + } + TEST_THAT(getline.IsEOF()); + TEST_CHECK_THROWS(getline.GetLine(true), CommonException, GetLineEOF); + + fclose(file2); + } + + // Then the IOStream version of get line, seeing as we're here... + { + FileStream file("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", O_RDONLY); + IOStreamGetLine getline(file); + + int l = 0; + while(testfilelines[l] != 0) + { + TEST_THAT(!getline.IsEOF()); + std::string line; + while(!getline.GetLine(line, true)) + { + // skip line + } + TEST_EQUAL(testfilelines[l], line); + l++; + } + TEST_THAT(getline.IsEOF()); + std::string dummy; + TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); + } + // and again without pre-processing + { + FileStream file("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", O_RDONLY); + IOStreamGetLine getline(file); + + FILE *file2 = fopen("testfiles" DIRECTORY_SEPARATOR + "fdgetlinetest.txt", "r"); + TEST_THAT_ABORTONFAIL(file2 != 0); + char ll[512]; + + while(!feof(file2)) + { + fgets(ll, sizeof(ll), file2); + int e = strlen(ll); + while(e > 0 && (ll[e-1] == '\n' || ll[e-1] == '\r')) + { + e--; + } + ll[e] = '\0'; + + TEST_THAT(!getline.IsEOF()); + std::string line; + while(!getline.GetLine(line, false)) + ; + TEST_EQUAL(ll, line); + } + TEST_THAT(getline.IsEOF()); + std::string dummy; + TEST_CHECK_THROWS(getline.GetLine(dummy, true), CommonException, GetLineEOF); + + fclose(file2); + } + + // Doesn't exist + { + std::string errMsg; + TEST_CHECK_THROWS(std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "DOESNTEXIST", + &verify, errMsg)), + CommonException, OSFileOpenError); + } + + // Basic configuration test + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config1.txt", + &verify, errMsg)); + if(!errMsg.empty()) + { + printf("UNEXPECTED error msg is:\n------\n%s------\n", errMsg.c_str()); + } + TEST_THAT_ABORTONFAIL(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->KeyExists("TOPlevel")); + TEST_THAT(pconfig->GetKeyValue("TOPlevel") == "value"); + TEST_THAT(pconfig->KeyExists("MultiValue")); + TEST_THAT(pconfig->GetKeyValue("MultiValue") == "single"); + TEST_THAT(!pconfig->KeyExists("not exist")); + TEST_THAT(pconfig->KeyExists("HasDefaultValue")); + TEST_THAT(pconfig->GetKeyValue("HasDefaultValue") == "Lovely default value"); + TEST_CHECK_THROWS(pconfig->GetKeyValue("not exist"), CommonException, ConfigNoKey); + // list of keys + std::vector keylist(pconfig->GetKeyNames()); + TEST_THAT(keylist.size() == 3); + // will be sorted alphanumerically + TEST_THAT(keylist[2] == "TOPlevel" && keylist[1] == "MultiValue" && keylist[0] == "HasDefaultValue"); + // list of sub configurations + std::vector sublist(pconfig->GetSubConfigurationNames()); + TEST_THAT(sublist.size() == 2); + TEST_THAT(sublist[0] == "test1"); + TEST_THAT(sublist[1] == "ping"); + TEST_THAT(pconfig->SubConfigurationExists("test1")); + TEST_THAT(pconfig->SubConfigurationExists("ping")); + TEST_CHECK_THROWS(pconfig->GetSubConfiguration("nosubconfig"), CommonException, ConfigNoSubConfig); + // Get a sub configuration + const Configuration &sub1 = pconfig->GetSubConfiguration("test1"); + TEST_THAT(sub1.GetKeyValueInt("value") == 12); + std::vector sublist2(sub1.GetSubConfigurationNames()); + TEST_THAT(sublist2.size() == 4); + // And the sub-sub configs + const Configuration &sub1_1 = sub1.GetSubConfiguration("subconfig"); + TEST_THAT(sub1_1.GetKeyValueInt("carrots") == 0x2356); + const Configuration &sub1_2 = sub1.GetSubConfiguration("subconfig2"); + TEST_THAT(sub1_2.GetKeyValueInt("carrots") == -243895); + const Configuration &sub1_3 = sub1.GetSubConfiguration("subconfig3"); + TEST_THAT(sub1_3.GetKeyValueInt("carrots") == 050); + TEST_THAT(sub1_3.GetKeyValue("terrible") == "absolutely"); + } + + static const char *file[][2] = + { + {"testfiles" DIRECTORY_SEPARATOR "config2.txt", + ".TOPlevel (key) is missing."}, + // Value missing from root + {"testfiles" DIRECTORY_SEPARATOR "config3.txt", + "Unexpected start block in test1"}, + // Unexpected { + {"testfiles" DIRECTORY_SEPARATOR "config4.txt", + "Root level has close block -- forgot to terminate subblock?"}, + // Missing } + {"testfiles" DIRECTORY_SEPARATOR "config5.txt", + "Block subconfig2 wasn't started correctly (no '{' on line of it's own)\n" + "Root level has close block -- forgot to terminate subblock?"}, + // { expected, but wasn't there + {"testfiles" DIRECTORY_SEPARATOR "config6.txt", + "test1.subconfig2.bing (key) multi value not allowed (duplicated key?)."}, + // Duplicate key + {"testfiles" DIRECTORY_SEPARATOR "config7.txt", + "Invalid configuration key: = invalid thing here!"}, + // Invalid key (no name) + {"testfiles" DIRECTORY_SEPARATOR "config8.txt", + "File ended without terminating all subblocks"}, + // Not all sub blocks terminated + {"testfiles" DIRECTORY_SEPARATOR "config9.txt", + "test1.subconfig3.carrots (key) is not a valid integer."}, + // Not valid integer + {"testfiles" DIRECTORY_SEPARATOR "config9b.txt", + "test1.subconfig2.carrots (key) is not a valid integer."}, + // Not valid integer + {"testfiles" DIRECTORY_SEPARATOR "config9c.txt", + "test1.subconfig2.carrots (key) is not a valid integer."}, + // Not valid integer + {"testfiles" DIRECTORY_SEPARATOR "config9d.txt", + "test1.subconfig3.carrots (key) is not a valid integer."}, + // Not valid integer + {"testfiles" DIRECTORY_SEPARATOR "config10.txt", + "test1.subconfig.carrots (key) is missing."}, + // Missing key (in subblock) + {"testfiles" DIRECTORY_SEPARATOR "config11.txt", + "test1.subconfig3.NOTEXPECTED (key) is not a known key. Check spelling and placement."}, + // Unknown key + {"testfiles" DIRECTORY_SEPARATOR "config12.txt", + ".test1.otherthing (block) is missing."}, + // Missing block + {"testfiles" DIRECTORY_SEPARATOR "config13.txt", + ".test1.* (block) is missing (a block must be present).\n" + ".test1.otherthing (block) is missing."}, + // Subconfig (wildcarded) should exist, but missing (ie nothing present) + {"testfiles" DIRECTORY_SEPARATOR "config16.txt", + ".BoolTrue1 (key) is not a valid boolean value."}, + // bad boolean value + {NULL, NULL}, + }; + + for(int l = 0; file[l][0] != 0; ++l) + { + HideCategoryGuard hide(ConfigurationVerify::VERIFY_ERROR); + std::string errMsg; + std::auto_ptr pconfig(Configuration::LoadAndVerify(file[l][0], &verify, errMsg)); + TEST_THAT(pconfig.get() == 0); + errMsg = errMsg.substr(0, errMsg.size() > 0 ? errMsg.size() - 1 : 0); + TEST_EQUAL_LINE(file[l][1], errMsg, file[l][0]); + } + + // Check that multivalues happen as expected + // (single value in a multivalue already checked) + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config14.txt", + &verify, errMsg)); + TEST_THAT(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->KeyExists("MultiValue")); + // values are separated by a specific character + std::string expectedvalue("value1"); + expectedvalue += Configuration::MultiValueSeparator; + expectedvalue += "secondvalue"; + TEST_THAT(pconfig->GetKeyValue("MultiValue") == expectedvalue); + } + + // Check boolean values + { + std::string errMsg; + std::auto_ptr pconfig( + Configuration::LoadAndVerify( + "testfiles" DIRECTORY_SEPARATOR "config15.txt", + &verify, errMsg)); + TEST_THAT(pconfig.get() != 0); + TEST_THAT(errMsg.empty()); + TEST_THAT(pconfig->GetKeyValueBool("BoolTrue1") == true); + TEST_THAT(pconfig->GetKeyValueBool("BoolTrue2") == true); + TEST_THAT(pconfig->GetKeyValueBool("BoolFalse1") == false); + TEST_THAT(pconfig->GetKeyValueBool("BoolFalse2") == false); + } + + // Test named locks + { + NamedLock lock1; + // Try and get a lock on a name in a directory which doesn't exist + TEST_CHECK_THROWS(lock1.TryAndGetLock( + "testfiles" + DIRECTORY_SEPARATOR "non-exist" + DIRECTORY_SEPARATOR "lock"), + CommonException, OSFileError); + + // And a more resonable request + TEST_THAT(lock1.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == true); + + // Try to lock something using the same lock + TEST_CHECK_THROWS( + lock1.TryAndGetLock( + "testfiles" + DIRECTORY_SEPARATOR "non-exist" + DIRECTORY_SEPARATOR "lock2"), + CommonException, NamedLockAlreadyLockingSomething); +#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK + // And again on that name + NamedLock lock2; + TEST_THAT(lock2.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == false); +#endif + } + { + // Check that it unlocked when it went out of scope + NamedLock lock3; + TEST_THAT(lock3.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock1") == true); + } + { + // And unlocking works + NamedLock lock4; + TEST_CHECK_THROWS(lock4.ReleaseLock(), CommonException, + NamedLockNotHeld); + TEST_THAT(lock4.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock4") == true); + lock4.ReleaseLock(); + NamedLock lock5; + TEST_THAT(lock5.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock4") == true); + // And can reuse it + TEST_THAT(lock4.TryAndGetLock( + "testfiles" DIRECTORY_SEPARATOR "lock5") == true); + } + + // Test the ReadGatherStream + { + #define GATHER_DATA1 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + #define GATHER_DATA2 "ZYZWVUTSRQPOMNOLKJIHGFEDCBA9876543210zyxwvutsrqpomno" + + // Make two streams + MemBlockStream s1(GATHER_DATA1, sizeof(GATHER_DATA1)); + MemBlockStream s2(GATHER_DATA2, sizeof(GATHER_DATA2)); + + // And a gather stream + ReadGatherStream gather(false /* no deletion */); + + // Add the streams + int s1_c = gather.AddComponent(&s1); + int s2_c = gather.AddComponent(&s2); + TEST_THAT(s1_c == 0); + TEST_THAT(s2_c == 1); + + // Set up some blocks + gather.AddBlock(s1_c, 11); + gather.AddBlock(s1_c, 2); + gather.AddBlock(s1_c, 8, true, 2); + gather.AddBlock(s2_c, 20); + gather.AddBlock(s1_c, 20); + gather.AddBlock(s2_c, 25); + gather.AddBlock(s1_c, 10, true, 0); + #define GATHER_RESULT "0123456789abc23456789ZYZWVUTSRQPOMNOLKJIHabcdefghijklmnopqrstGFEDCBA9876543210zyxwvuts0123456789" + + // Read them in... + char buffer[1024]; + unsigned int r = 0; + while(r < sizeof(GATHER_RESULT) - 1) + { + int s = gather.Read(buffer + r, 7); + r += s; + + TEST_THAT(gather.GetPosition() == r); + if(r < sizeof(GATHER_RESULT) - 1) + { + TEST_THAT(gather.StreamDataLeft()); + TEST_THAT(static_cast(gather.BytesLeftToRead()) == sizeof(GATHER_RESULT) - 1 - r); + } + else + { + TEST_THAT(!gather.StreamDataLeft()); + TEST_THAT(gather.BytesLeftToRead() == 0); + } + } + TEST_THAT(r == sizeof(GATHER_RESULT) - 1); + TEST_THAT(::memcmp(buffer, GATHER_RESULT, sizeof(GATHER_RESULT) - 1) == 0); + } + + // Test ExcludeList + { + ExcludeList elist; + // Check assumption + TEST_THAT(Configuration::MultiValueSeparator == '\x01'); + // Add definite entries + elist.AddDefiniteEntries(std::string("\x01")); + elist.AddDefiniteEntries(std::string("")); + elist.AddDefiniteEntries(std::string("Definite1\x01/dir/DefNumberTwo\x01\x01ThingDefThree")); + elist.AddDefiniteEntries(std::string("AnotherDef")); + TEST_THAT(elist.SizeOfDefiniteList() == 4); + + // Add regex entries + #ifdef HAVE_REGEX_SUPPORT + { + HideCategoryGuard hide(ConfigurationVerify::VERIFY_ERROR); + elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")); + elist.AddRegexEntries(std::string("")); + TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[:not_valid")), CommonException, BadRegularExpression); + TEST_THAT(elist.SizeOfRegexList() == 3); + } + #else + TEST_CHECK_THROWS(elist.AddRegexEntries(std::string("[a-d]+\\.reg$" "\x01" "EXCLUDE" "\x01" "^exclude$")), CommonException, RegexNotSupportedOnThisPlatform); + TEST_THAT(elist.SizeOfRegexList() == 0); + #endif + + #ifdef WIN32 + #define CASE_SENSITIVE false + #else + #define CASE_SENSITIVE true + #endif + + // Try some matches! + TEST_THAT(elist.IsExcluded(std::string("Definite1")) == true); + TEST_THAT(elist.IsExcluded(std::string("/dir/DefNumberTwo")) == true); + TEST_THAT(elist.IsExcluded(std::string("ThingDefThree")) == true); + TEST_THAT(elist.IsExcluded(std::string("AnotherDef")) == true); + TEST_THAT(elist.IsExcluded(std::string("dir/DefNumberTwo")) == false); + + // Try some case insensitive matches, + // that should pass on Win32 and fail elsewhere + TEST_THAT(elist.IsExcluded("DEFINITe1") + == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded("/Dir/DefNumberTwo") + == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded("thingdefthree") + == !CASE_SENSITIVE); + + #ifdef HAVE_REGEX_SUPPORT + TEST_THAT(elist.IsExcluded(std::string("b.reg")) == true); + TEST_THAT(elist.IsExcluded(std::string("B.reg")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("b.Reg")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("e.reg")) == false); + TEST_THAT(elist.IsExcluded(std::string("e.Reg")) == false); + TEST_THAT(elist.IsExcluded(std::string("DEfinite1")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("DEXCLUDEfinite1")) == true); + TEST_THAT(elist.IsExcluded(std::string("DEfinitexclude1")) == !CASE_SENSITIVE); + TEST_THAT(elist.IsExcluded(std::string("exclude")) == true); + TEST_THAT(elist.IsExcluded(std::string("ExcludE")) == !CASE_SENSITIVE); + #endif + + #undef CASE_SENSITIVE + + TestLogger logger(Log::WARNING); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo")); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo/")); + TEST_THAT(logger.IsTriggered()); + logger.Reset(); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR)); + TEST_THAT(logger.IsTriggered()); + logger.Reset(); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR "bar\x01/foo")); + TEST_THAT(!logger.IsTriggered()); + elist.AddDefiniteEntries(std::string("/foo" + DIRECTORY_SEPARATOR "bar\x01/foo" + DIRECTORY_SEPARATOR)); + TEST_THAT(logger.IsTriggered()); + } + + test_conversions(); + + // test that we can use Archive and CollectInBufferStream + // to read and write arbitrary types to a memory buffer + + { + CollectInBufferStream buffer; + ASSERT(buffer.GetPosition() == 0); + + { + Archive archive(buffer, 0); + ASSERT(buffer.GetPosition() == 0); + + archive.Write((bool) true); + archive.Write((bool) false); + archive.Write((int) 0x12345678); + archive.Write((int) 0x87654321); + archive.Write((int64_t) 0x0badfeedcafebabeLL); + archive.Write((uint64_t) 0xfeedfacedeadf00dLL); + archive.Write((uint8_t) 0x01); + archive.Write((uint8_t) 0xfe); + archive.Write(std::string("hello world!")); + archive.Write(std::string("goodbye cruel world!")); + } + + CollectInBufferStream buf2; + buf2.Write(buffer.GetBuffer(), buffer.GetSize()); + TEST_THAT(buf2.GetPosition() == buffer.GetSize()); + + buf2.SetForReading(); + TEST_THAT(buf2.GetPosition() == 0); + + { + Archive archive(buf2, 0); + TEST_THAT(buf2.GetPosition() == 0); + + bool b; + archive.Read(b); TEST_THAT(b == true); + archive.Read(b); TEST_THAT(b == false); + + int i; + archive.Read(i); TEST_THAT(i == 0x12345678); + archive.Read(i); TEST_THAT((unsigned int)i == 0x87654321); + + uint64_t i64; + archive.Read(i64); TEST_THAT(i64 == 0x0badfeedcafebabeLL); + archive.Read(i64); TEST_THAT(i64 == 0xfeedfacedeadf00dLL); + + uint8_t i8; + archive.Read(i8); TEST_THAT(i8 == 0x01); + archive.Read(i8); TEST_THAT(i8 == 0xfe); + + std::string s; + archive.Read(s); TEST_THAT(s == "hello world!"); + archive.Read(s); TEST_THAT(s == "goodbye cruel world!"); + + TEST_THAT(!buf2.StreamDataLeft()); + } + } + + return 0; +} diff --git a/test/common/testfiles/config1.txt b/test/common/testfiles/config1.txt new file mode 100644 index 00000000..d000f759 --- /dev/null +++ b/test/common/testfiles/config1.txt @@ -0,0 +1,40 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config10.txt b/test/common/testfiles/config10.txt new file mode 100644 index 00000000..02aeec74 --- /dev/null +++ b/test/common/testfiles/config10.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config11.txt b/test/common/testfiles/config11.txt new file mode 100644 index 00000000..cafabe74 --- /dev/null +++ b/test/common/testfiles/config11.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + NOTEXPECTED= 34234 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config12.txt b/test/common/testfiles/config12.txt new file mode 100644 index 00000000..17ed34f1 --- /dev/null +++ b/test/common/testfiles/config12.txt @@ -0,0 +1,33 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config13.txt b/test/common/testfiles/config13.txt new file mode 100644 index 00000000..8de8ea5b --- /dev/null +++ b/test/common/testfiles/config13.txt @@ -0,0 +1,15 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config14.txt b/test/common/testfiles/config14.txt new file mode 100644 index 00000000..d409beaa --- /dev/null +++ b/test/common/testfiles/config14.txt @@ -0,0 +1,41 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = value1 +MultiValue = secondvalue + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config15.txt b/test/common/testfiles/config15.txt new file mode 100644 index 00000000..bfa7b022 --- /dev/null +++ b/test/common/testfiles/config15.txt @@ -0,0 +1,45 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + +BoolTrue1 = true +BoolTrue2 = yes +BoolFalse1 = fAlse +BoolFalse2 = nO + diff --git a/test/common/testfiles/config16.txt b/test/common/testfiles/config16.txt new file mode 100644 index 00000000..566070f2 --- /dev/null +++ b/test/common/testfiles/config16.txt @@ -0,0 +1,42 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +MultiValue = single + +ping +{ + carrots=324 + string = casrts +} + +BoolTrue1 = not a valid value + diff --git a/test/common/testfiles/config2.txt b/test/common/testfiles/config2.txt new file mode 100644 index 00000000..724c911a --- /dev/null +++ b/test/common/testfiles/config2.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +# make this value missing +# TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config3.txt b/test/common/testfiles/config3.txt new file mode 100644 index 00000000..688675a4 --- /dev/null +++ b/test/common/testfiles/config3.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + { + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config4.txt b/test/common/testfiles/config4.txt new file mode 100644 index 00000000..1563d8aa --- /dev/null +++ b/test/common/testfiles/config4.txt @@ -0,0 +1,40 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} +} + + diff --git a/test/common/testfiles/config5.txt b/test/common/testfiles/config5.txt new file mode 100644 index 00000000..d1821ecc --- /dev/null +++ b/test/common/testfiles/config5.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config6.txt b/test/common/testfiles/config6.txt new file mode 100644 index 00000000..d7381738 --- /dev/null +++ b/test/common/testfiles/config6.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + bing= something else + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config7.txt b/test/common/testfiles/config7.txt new file mode 100644 index 00000000..6a24d036 --- /dev/null +++ b/test/common/testfiles/config7.txt @@ -0,0 +1,39 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + = invalid thing here! + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config8.txt b/test/common/testfiles/config8.txt new file mode 100644 index 00000000..3a66bbb3 --- /dev/null +++ b/test/common/testfiles/config8.txt @@ -0,0 +1,37 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts + diff --git a/test/common/testfiles/config9.txt b/test/common/testfiles/config9.txt new file mode 100644 index 00000000..936ad6ce --- /dev/null +++ b/test/common/testfiles/config9.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050X + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9b.txt b/test/common/testfiles/config9b.txt new file mode 100644 index 00000000..65c44a19 --- /dev/null +++ b/test/common/testfiles/config9b.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=C-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9c.txt b/test/common/testfiles/config9c.txt new file mode 100644 index 00000000..d9be55ad --- /dev/null +++ b/test/common/testfiles/config9c.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=2430-895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =050 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/config9d.txt b/test/common/testfiles/config9d.txt new file mode 100644 index 00000000..28ea300e --- /dev/null +++ b/test/common/testfiles/config9d.txt @@ -0,0 +1,38 @@ +test1 +{ + value=12 + string1 = carrots in may + string2 =on the string + subconfig + { + bing= nothing really + carrots =0x2356 + terrible=lovely + } + subconfig2 + { + bing= something + carrots=-243895 + terrible=pgin! + } + subconfig3 + { + bing= 435 + carrots =090 + terrible=absolutely + } + otherthing + { + string= ping + fish =0 + } +} + +TOPlevel= value + +ping +{ + carrots=324 + string = casrts +} + diff --git a/test/common/testfiles/fdgetlinetest.txt b/test/common/testfiles/fdgetlinetest.txt new file mode 100644 index 00000000..f7b2c829 --- /dev/null +++ b/test/common/testfiles/fdgetlinetest.txt @@ -0,0 +1,20 @@ +First line + Second line + Third +# comment + # comment + + sdf hjjk + + test #coment + test#not comment + test#not comment #comment + +nice line +fish + +ping +#comment + + Nothing + Nothing \ No newline at end of file diff --git a/test/compress/testcompress.cpp b/test/compress/testcompress.cpp new file mode 100644 index 00000000..76512e6a --- /dev/null +++ b/test/compress/testcompress.cpp @@ -0,0 +1,262 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcompress.cpp +// Purpose: Test lib/compress +// Created: 5/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "Compress.h" +#include "CompressStream.h" +#include "CollectInBufferStream.h" + +#include "MemLeakFindOn.h" + +#define DATA_SIZE (1024*128+103) +#define CHUNK_SIZE 2561 +#define DECOMP_CHUNK_SIZE 3 + +// Stream for testing +class CopyInToOutStream : public IOStream +{ +public: + CopyInToOutStream() : currentBuffer(0) {buffers[currentBuffer].SetForReading();} + ~CopyInToOutStream() {} + int Read(void *pBuffer, int NBytes, int Timeout = IOStream::TimeOutInfinite) + { + if(buffers[currentBuffer].StreamDataLeft()) + { + return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); + } + + // Swap buffers? + if(buffers[(currentBuffer + 1) & 1].GetSize() > 0) + { + buffers[currentBuffer].Reset(); + currentBuffer = (currentBuffer + 1) & 1; + buffers[currentBuffer].SetForReading(); + return buffers[currentBuffer].Read(pBuffer, NBytes, Timeout); + } + + return 0; + } + void Write(const void *pBuffer, int NBytes, + int Timeout = IOStream::TimeOutInfinite) + { + buffers[(currentBuffer + 1) & 1].Write(pBuffer, NBytes, Timeout); + } + bool StreamDataLeft() + { + return buffers[currentBuffer].StreamDataLeft() || buffers[(currentBuffer + 1) % 1].GetSize() > 0; + } + bool StreamClosed() + { + return false; + } + int currentBuffer; + CollectInBufferStream buffers[2]; +}; + +// Test stream based interface +int test_stream() +{ + // Make a load of compressible data to compress + CollectInBufferStream source; + uint16_t data[1024]; + for(int x = 0; x < 1024; ++x) + { + data[x] = x; + } + for(int x = 0; x < (32*1024); ++x) + { + source.Write(data, (x % 1024) * 2); + } + source.SetForReading(); + + // Straight compress from one stream to another + { + CollectInBufferStream *poutput = new CollectInBufferStream; + CompressStream compress(poutput, true /* take ownership */, false /* read */, true /* write */); + + source.CopyStreamTo(compress); + compress.Close(); + poutput->SetForReading(); + + // Check sizes + TEST_THAT(poutput->GetSize() < source.GetSize()); + BOX_TRACE("compressed size = " << poutput->GetSize() << + ", source size = " << source.GetSize()); + + // Decompress the data + { + CollectInBufferStream decompressed; + CompressStream decompress(poutput, false /* don't take ownership */, true /* read */, false /* write */); + decompress.CopyStreamTo(decompressed); + decompress.Close(); + + TEST_THAT(decompressed.GetSize() == source.GetSize()); + TEST_THAT(::memcmp(decompressed.GetBuffer(), source.GetBuffer(), decompressed.GetSize()) == 0); + } + + // Don't delete poutput, let mem leak testing ensure it's deleted. + } + + // Set source to the beginning + source.Seek(0, IOStream::SeekType_Absolute); + + // Test where the same stream compresses and decompresses, should be fun! + { + CollectInBufferStream output; + CopyInToOutStream copyer; + CompressStream compress(©er, false /* no ownership */, true, true); + + bool done = false; + int count = 0; + int written = 0; + while(!done) + { + ++count; + bool do_sync = (count % 256) == 0; + uint8_t buffer[4096]; + int r = source.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); + if(r == 0) + { + done = true; + compress.Close(); + } + else + { + compress.Write(buffer, r); + written += r; + if(do_sync) + { + compress.WriteAllBuffered(); + } + } + + int r2 = 0; + do + { + r2 = compress.Read(buffer, sizeof(buffer), IOStream::TimeOutInfinite); + if(r2 > 0) + { + output.Write(buffer, r2); + } + } while(r2 > 0); + if(do_sync && r != 0) + { + // Check that everything is synced + TEST_THAT(output.GetSize() == written); + TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); + } + } + output.SetForReading(); + + // Test that it's the same + TEST_THAT(output.GetSize() == source.GetSize()); + TEST_THAT(::memcmp(output.GetBuffer(), source.GetBuffer(), output.GetSize()) == 0); + } + + return 0; +} + +// Test basic interface +int test(int argc, const char *argv[]) +{ + // Bad data to compress! + char *data = (char *)malloc(DATA_SIZE); + for(int l = 0; l < DATA_SIZE; ++l) + { + data[l] = l*23; + } + + // parameters about compression + int maxOutput = Compress_MaxSizeForCompressedData(DATA_SIZE); + TEST_THAT(maxOutput >= DATA_SIZE); + + char *compressed = (char *)malloc(maxOutput); + int compressedSize = 0; + + // Do compression, in small chunks + { + Compress compress; + + int in_loc = 0; + while(!compress.OutputHasFinished()) + { + int ins = DATA_SIZE - in_loc; + if(ins > CHUNK_SIZE) ins = CHUNK_SIZE; + + if(ins == 0) + { + compress.FinishInput(); + } + else + { + compress.Input(data + in_loc, ins); + } + in_loc += ins; + + // Get output data + int s = 0; + do + { + TEST_THAT(compressedSize < maxOutput); + s = compress.Output(compressed + compressedSize, maxOutput - compressedSize); + compressedSize += s; + } while(s > 0); + } + } + + // a reasonable test, especially given the compressability of the input data. + TEST_THAT(compressedSize < DATA_SIZE); + + // decompression + char *decompressed = (char*)malloc(DATA_SIZE * 2); + int decomp_size = 0; + { + Compress decompress; + + int in_loc = 0; + while(!decompress.OutputHasFinished()) + { + int ins = compressedSize - in_loc; + if(ins > DECOMP_CHUNK_SIZE) ins = DECOMP_CHUNK_SIZE; + + if(ins == 0) + { + decompress.FinishInput(); + } + else + { + decompress.Input(compressed + in_loc, ins); + } + in_loc += ins; + + // Get output data + int s = 0; + do + { + TEST_THAT(decomp_size <= DATA_SIZE); + s = decompress.Output(decompressed + decomp_size, (DATA_SIZE*2) - decomp_size); + decomp_size += s; + } while(s > 0); + } + } + + TEST_THAT(decomp_size == DATA_SIZE); + TEST_THAT(::memcmp(data, decompressed, DATA_SIZE) == 0); + + ::free(data); + ::free(compressed); + ::free(decompressed); + + return test_stream(); +} diff --git a/test/crypto/testcrypto.cpp b/test/crypto/testcrypto.cpp new file mode 100644 index 00000000..4e623cc2 --- /dev/null +++ b/test/crypto/testcrypto.cpp @@ -0,0 +1,313 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testcrypto.cpp +// Purpose: test lib/crypto +// Created: 1/12/03 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include + +#include "Test.h" +#include "CipherContext.h" +#include "CipherBlowfish.h" +#include "CipherAES.h" +#include "CipherException.h" +#include "RollingChecksum.h" +#include "Random.h" + +#include "MemLeakFindOn.h" + +#define STRING1 "Mary had a little lamb" +#define STRING2 "Skjdf sdjf sjksd fjkhsdfjk hsdfuiohcverfg sdfnj sdfgkljh sdfjb jlhdfvghsdip vjsdfv bsdfhjvg yuiosdvgpvj kvbn m,sdvb sdfuiovg sdfuivhsdfjkv" + +#define KEY "0123456701234567012345670123456" +#define KEY2 "1234567012345670123456A" + +#define CHECKSUM_DATA_SIZE (128*1024) +#define CHECKSUM_BLOCK_SIZE_BASE (65*1024) +#define CHECKSUM_BLOCK_SIZE_LAST (CHECKSUM_BLOCK_SIZE_BASE + 64) +#define CHECKSUM_ROLLS 16 + +void check_random_int(uint32_t max) +{ + for(int c = 0; c < 1024; ++c) + { + uint32_t v = Random::RandomInt(max); + TEST_THAT(v >= 0 && v <= max); + } +} + +#define ZERO_BUFFER(x) ::memset(x, 0, sizeof(x)); + +template +void test_cipher() +{ + { + // Make a couple of cipher contexts + CipherContext encrypt1; + encrypt1.Reset(); + encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + TEST_CHECK_THROWS(encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))), + CipherException, AlreadyInitialised); + // Encrpt something + char buf1[256]; + unsigned int buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); + TEST_THAT(buf1_used >= sizeof(STRING1)); + // Decrypt it + CipherContext decrypt1; + decrypt1.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + char buf1_de[256]; + unsigned int buf1_de_used = decrypt1.TransformBlock(buf1_de, sizeof(buf1_de), buf1, buf1_used); + TEST_THAT(buf1_de_used == sizeof(STRING1)); + TEST_THAT(memcmp(STRING1, buf1_de, sizeof(STRING1)) == 0); + + // Use them again... + char buf1_de2[256]; + unsigned int buf1_de2_used = decrypt1.TransformBlock(buf1_de2, sizeof(buf1_de2), buf1, buf1_used); + TEST_THAT(buf1_de2_used == sizeof(STRING1)); + TEST_THAT(memcmp(STRING1, buf1_de2, sizeof(STRING1)) == 0); + + // Test the interface + char buf2[256]; + TEST_CHECK_THROWS(encrypt1.Transform(buf2, sizeof(buf2), STRING1, sizeof(STRING1)), + CipherException, BeginNotCalled); + TEST_CHECK_THROWS(encrypt1.Final(buf2, sizeof(buf2)), + CipherException, BeginNotCalled); + encrypt1.Begin(); + int e = 0; + e = encrypt1.Transform(buf2, sizeof(buf2), STRING2, sizeof(STRING2) - 16); + e += encrypt1.Transform(buf2 + e, sizeof(buf2) - e, STRING2 + sizeof(STRING2) - 16, 16); + e += encrypt1.Final(buf2 + e, sizeof(buf2) - e); + TEST_THAT(e >= (int)sizeof(STRING2)); + + // Then decrypt + char buf2_de[256]; + decrypt1.Begin(); + TEST_CHECK_THROWS(decrypt1.Transform(buf2_de, 2, buf2, e), CipherException, OutputBufferTooSmall); + TEST_CHECK_THROWS(decrypt1.Final(buf2_de, 2), CipherException, OutputBufferTooSmall); + int d = decrypt1.Transform(buf2_de, sizeof(buf2_de), buf2, e - 48); + d += decrypt1.Transform(buf2_de + d, sizeof(buf2_de) - d, buf2 + e - 48, 48); + d += decrypt1.Final(buf2_de + d, sizeof(buf2_de) - d); + TEST_THAT(d == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf2_de, sizeof(STRING2)) == 0); + + // Try a reset and rekey + encrypt1.Reset(); + encrypt1.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY2, sizeof(KEY2))); + buf1_used = encrypt1.TransformBlock(buf1, sizeof(buf1), STRING1, sizeof(STRING1)); + } + + // Test initialisation vectors + { + // Init with random IV + CipherContext encrypt2; + encrypt2.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + int ivLen; + char iv2[BLOCKSIZE]; + const void *ivGen = encrypt2.SetRandomIV(ivLen); + TEST_THAT(ivLen == BLOCKSIZE); // block size + TEST_THAT(ivGen != 0); + memcpy(iv2, ivGen, ivLen); + + char buf3[256]; + unsigned int buf3_used = encrypt2.TransformBlock(buf3, sizeof(buf3), STRING2, sizeof(STRING2)); + + // Encrypt again with different IV + char iv3[BLOCKSIZE]; + int ivLen3; + const void *ivGen3 = encrypt2.SetRandomIV(ivLen3); + TEST_THAT(ivLen3 == BLOCKSIZE); // block size + TEST_THAT(ivGen3 != 0); + memcpy(iv3, ivGen3, ivLen3); + // Check the two generated IVs are different + TEST_THAT(memcmp(iv2, iv3, BLOCKSIZE) != 0); + + char buf4[256]; + unsigned int buf4_used = encrypt2.TransformBlock(buf4, sizeof(buf4), STRING2, sizeof(STRING2)); + + // check encryptions are different + TEST_THAT(buf3_used == buf4_used); + TEST_THAT(memcmp(buf3, buf4, buf3_used) != 0); + + // Test that decryption with the right IV works + CipherContext decrypt2; + decrypt2.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY), iv2)); + char buf3_de[256]; + unsigned int buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); + TEST_THAT(buf3_de_used == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) == 0); + + // And that using the wrong one doesn't + decrypt2.SetIV(iv3); + buf3_de_used = decrypt2.TransformBlock(buf3_de, sizeof(buf3_de), buf3, buf3_used); + TEST_THAT(buf3_de_used == sizeof(STRING2)); + TEST_THAT(memcmp(STRING2, buf3_de, sizeof(STRING2)) != 0); + } + + // Test with padding off. + { + CipherContext encrypt3; + encrypt3.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt3.UsePadding(false); + + // Should fail because the encrypted size is not a multiple of the block size + char buf4[256]; + encrypt3.Begin(); + ZERO_BUFFER(buf4); + int buf4_used = encrypt3.Transform(buf4, sizeof(buf4), STRING2, 6); + TEST_CHECK_THROWS(encrypt3.Final(buf4, sizeof(buf4)), CipherException, EVPFinalFailure); + + // Check a nice encryption with the correct block size + CipherContext encrypt4; + encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4.UsePadding(false); + encrypt4.Begin(); + ZERO_BUFFER(buf4); + buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, 16); + buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); + TEST_THAT(buf4_used == 16); + + // Check it's encrypted to the same thing as when there's padding on + CipherContext encrypt4b; + encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4b.Begin(); + char buf4b[256]; + ZERO_BUFFER(buf4b); + int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, 16); + buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); + TEST_THAT(buf4b_used == 16+BLOCKSIZE); + TEST_THAT(::memcmp(buf4, buf4b, 16) == 0); + + // Decrypt + char buf4_de[256]; + CipherContext decrypt4; + decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + decrypt4.UsePadding(false); + decrypt4.Begin(); + ZERO_BUFFER(buf4_de); + int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, 16); + buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); + TEST_THAT(buf4_de_used == 16); + TEST_THAT(::memcmp(buf4_de, STRING2, 16) == 0); + + // Test that the TransformBlock thing works as expected too with blocks the same size as the input + TEST_THAT(encrypt4.TransformBlock(buf4, 16, STRING2, 16) == 16); + // But that it exceptions if we try the trick with padding on + encrypt4.UsePadding(true); + TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, 16, STRING2, 16), CipherException, OutputBufferTooSmall); + } + + // And again, but with different string size + { + char buf4[256]; + int buf4_used; + + // Check a nice encryption with the correct block size + CipherContext encrypt4; + encrypt4.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4.UsePadding(false); + encrypt4.Begin(); + ZERO_BUFFER(buf4); + buf4_used = encrypt4.Transform(buf4, sizeof(buf4), STRING2, (BLOCKSIZE*3)); // do three blocks worth + buf4_used += encrypt4.Final(buf4+buf4_used, sizeof(buf4)); + TEST_THAT(buf4_used == (BLOCKSIZE*3)); + + // Check it's encrypted to the same thing as when there's padding on + CipherContext encrypt4b; + encrypt4b.Init(CipherContext::Encrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + encrypt4b.Begin(); + char buf4b[256]; + ZERO_BUFFER(buf4b); + int buf4b_used = encrypt4b.Transform(buf4b, sizeof(buf4b), STRING2, (BLOCKSIZE*3)); + buf4b_used += encrypt4b.Final(buf4b + buf4b_used, sizeof(buf4b)); + TEST_THAT(buf4b_used == (BLOCKSIZE*4)); + TEST_THAT(::memcmp(buf4, buf4b, (BLOCKSIZE*3)) == 0); + + // Decrypt + char buf4_de[256]; + CipherContext decrypt4; + decrypt4.Init(CipherContext::Decrypt, CipherType(CipherDescription::Mode_CBC, KEY, sizeof(KEY))); + decrypt4.UsePadding(false); + decrypt4.Begin(); + ZERO_BUFFER(buf4_de); + int buf4_de_used = decrypt4.Transform(buf4_de, sizeof(buf4_de), buf4, (BLOCKSIZE*3)); + buf4_de_used += decrypt4.Final(buf4_de+buf4_de_used, sizeof(buf4_de)); + TEST_THAT(buf4_de_used == (BLOCKSIZE*3)); + TEST_THAT(::memcmp(buf4_de, STRING2, (BLOCKSIZE*3)) == 0); + + // Test that the TransformBlock thing works as expected too with blocks the same size as the input + TEST_THAT(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)) == (BLOCKSIZE*3)); + // But that it exceptions if we try the trick with padding on + encrypt4.UsePadding(true); + TEST_CHECK_THROWS(encrypt4.TransformBlock(buf4, (BLOCKSIZE*3), STRING2, (BLOCKSIZE*3)), CipherException, OutputBufferTooSmall); + } +} + +int test(int argc, const char *argv[]) +{ + Random::Initialise(); + + // Cipher type + ::printf("Blowfish...\n"); + test_cipher(); +#ifndef HAVE_OLD_SSL + ::printf("AES...\n"); + test_cipher(); +#else + ::printf("Skipping AES -- not supported by version of OpenSSL in use.\n"); +#endif + + ::printf("Misc...\n"); + // Check rolling checksums + uint8_t *checkdata_blk = (uint8_t *)malloc(CHECKSUM_DATA_SIZE); + uint8_t *checkdata = checkdata_blk; + RAND_pseudo_bytes(checkdata, CHECKSUM_DATA_SIZE); + for(int size = CHECKSUM_BLOCK_SIZE_BASE; size <= CHECKSUM_BLOCK_SIZE_LAST; ++size) + { + // Test skip-roll code + RollingChecksum rollFast(checkdata, size); + rollFast.RollForwardSeveral(checkdata, checkdata+size, size, CHECKSUM_ROLLS/2); + RollingChecksum calc(checkdata + (CHECKSUM_ROLLS/2), size); + TEST_THAT(calc.GetChecksum() == rollFast.GetChecksum()); + + //printf("size = %d\n", size); + // Checksum to roll + RollingChecksum roll(checkdata, size); + + // Roll forward + for(int l = 0; l < CHECKSUM_ROLLS; ++l) + { + // Calculate new one + RollingChecksum calc(checkdata, size); + + //printf("%08X %08X %d %d\n", roll.GetChecksum(), calc.GetChecksum(), checkdata[0], checkdata[size]); + + // Compare them! + TEST_THAT(calc.GetChecksum() == roll.GetChecksum()); + + // Roll it onwards + roll.RollForward(checkdata[0], checkdata[size], size); + + // increment + ++checkdata; + } + } + ::free(checkdata_blk); + + // Random integers + check_random_int(0); + check_random_int(1); + check_random_int(5); + check_random_int(15); // all 1's + check_random_int(1022); + + return 0; +} + + + diff --git a/test/httpserver/testfiles/httpserver.conf b/test/httpserver/testfiles/httpserver.conf new file mode 100644 index 00000000..1a1c4644 --- /dev/null +++ b/test/httpserver/testfiles/httpserver.conf @@ -0,0 +1,8 @@ + +AddressPrefix = http://localhost:1080 + +Server +{ + PidFile = testfiles/httpserver.pid + ListenAddresses = inet:localhost:1080 +} diff --git a/test/httpserver/testfiles/photos/puppy.jpg b/test/httpserver/testfiles/photos/puppy.jpg new file mode 100644 index 00000000..a326a6a7 --- /dev/null +++ b/test/httpserver/testfiles/photos/puppy.jpg @@ -0,0 +1 @@ +omgpuppies! diff --git a/test/httpserver/testfiles/s3simulator.conf b/test/httpserver/testfiles/s3simulator.conf new file mode 100644 index 00000000..07921560 --- /dev/null +++ b/test/httpserver/testfiles/s3simulator.conf @@ -0,0 +1,10 @@ +AccessKey = 0PN5J17HBGZHT7JJ3X82 +SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o +StoreDirectory = testfiles +AddressPrefix = http://localhost:1080 + +Server +{ + PidFile = testfiles/s3simulator.pid + ListenAddresses = inet:localhost:1080 +} diff --git a/test/httpserver/testfiles/testrequests.pl b/test/httpserver/testfiles/testrequests.pl new file mode 100755 index 00000000..4dd3549f --- /dev/null +++ b/test/httpserver/testfiles/testrequests.pl @@ -0,0 +1,145 @@ +#!/usr/bin/perl +use strict; +use LWP::UserAgent; + +# Use 127.0.0.1 instead of localhost to force use of IPv4, as that is what the server +# binds to. Windows tends to use IPv6 instead if possible, breaking the test. +my $url_base = 'http://127.0.0.1:1080'; + +my $ua = LWP::UserAgent->new(env_proxy => 0, keep_alive => 1, timeout => 30); + +print "GET request...\n"; + +my $response1 = $ua->get("$url_base/test-one/34/341s/234?p1=vOne&p2=vTwo"); +die $response1->content unless $response1->is_success(); + +my $content = $response1->content(); + +check_url($content, '/test-one/34/341s/234'); +check_params($content, 'p1'=>'vOne','p2'=>'vTwo'); + +print "POST request...\n"; + +my %post = ('sdfgksjhdfsd'=>'dfvsiufy2e3434','sxciuhwf8723e4'=>'238947829334', + '&sfsfsfskfhs'=>'?hdkfjhsjfds','fdsf=sdf2342'=>'3984sajhksda'); + +my $response2 = $ua->post("$url_base/tdskjhfsjdkhf2943734?p1=vOne&p2=vTwo", \%post); + +my $content2 = $response2->content(); + +check_url($content2, '/tdskjhfsjdkhf2943734'); +check_params($content2, %post); + +print "HEAD request...\n"; + +my $response3 = $ua->head("$url_base/tdskjhfsdfkjhs"); + +if($response3->content() ne '') +{ + print "Content not zero length\n"; + exit(1); +} + +if($response3->code() != 200) +{ + print "Wrong response code\n"; + exit(1); +} + +print "Redirected GET request...\n"; + +my $response4 = $ua->get("$url_base/redirect?key=value"); +exit 4 unless $response4->is_success(); + +my $content4 = $response4->content(); + +check_url($content4, '/redirected'); +check_params($content4); + +print "Cookie tests...\n"; + +# from examples in specs +test_cookies('CUSTOMER=WILE_E_COYOTE', 'CUSTOMER=WILE_E_COYOTE'); +test_cookies('CUSTOMER="WILE_E_COYOTE"; C2="pants"', 'CUSTOMER=WILE_E_COYOTE', 'C2=pants'); +test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001'); +test_cookies('CUSTOMER=WILE_E_COYOTE; PART_NUMBER=ROCKET_LAUNCHER_0001; SHIPPING=FEDEX', 'CUSTOMER=WILE_E_COYOTE', 'PART_NUMBER=ROCKET_LAUNCHER_0001', 'SHIPPING=FEDEX'); +test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"', 'Customer=WILE_E_COYOTE'); +test_cookies('$Version="1"; Customer="WILE_E_COYOTE"; $Path="/acme"; Part_Number="Rocket_Launcher_0001"; $Path="/acme" ', + 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001'); +test_cookies(qq!\$Version="1"; Customer="WILE_E_COYOTE"; \$Path="/acme"; Part_Number="Rocket_Launcher_0001"; \$Path="/acme"; Shipping="FedEx"; \t \$Path="/acme"!, + 'Customer=WILE_E_COYOTE', 'Part_Number=Rocket_Launcher_0001', 'Shipping=FedEx'); + +# test the server setting cookies in the UA +require HTTP::Cookies; +$ua->cookie_jar(HTTP::Cookies->new()); +$ua->get("$url_base/set-cookie"); +test_cookies('', 'SetByServer=Value1'); + +sub test_cookies +{ + my ($c_str, @cookies) = @_; + test_cookies2($c_str, @cookies); + $c_str =~ s/;/,/g; + test_cookies2($c_str, @cookies); +} + +sub test_cookies2 +{ + my ($c_str, @cookies) = @_; + my $r; + if($c_str ne '') + { + $r = $ua->get("$url_base/cookie", 'Cookie' => $c_str); + } + else + { + $r = $ua->get("$url_base/cookie"); + } + my $c = $r->content(); + for(@cookies) + { + unless($c =~ m/COOKIE:$_
    /) + { + print "Cookie $_ not found\n"; + exit(1); + } + } +} + + +sub check_url +{ + my ($c,$url) = @_; + unless($c =~ m~URI: (.+?)

    ~) + { + print "URI not found\n"; + exit(1); + } + if($url ne $1) + { + print "Wrong URI in content\n"; + exit(1); + } +} + +sub check_params +{ + my ($c,%params) = @_; + + while($c =~ m/^PARAM:(.+)=(.+?)
    /mg) + { + if($params{$1} ne $2) + { + print "$1=$2 not found in response\n"; + exit(1); + } + delete $params{$1} + } + + my @k = keys %params; + if($#k != -1) + { + print "Didn't find all params\n"; + exit(1); + } +} diff --git a/test/httpserver/testhttpserver.cpp b/test/httpserver/testhttpserver.cpp new file mode 100644 index 00000000..469fa383 --- /dev/null +++ b/test/httpserver/testhttpserver.cpp @@ -0,0 +1,493 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: testhttpserver.cpp +// Purpose: Test code for HTTP server class +// Created: 26/3/04 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include +#include + +#ifdef HAVE_SIGNAL_H + #include +#endif + +#include + +#include "autogen_HTTPException.h" +#include "HTTPRequest.h" +#include "HTTPResponse.h" +#include "HTTPServer.h" +#include "IOStreamGetLine.h" +#include "S3Client.h" +#include "S3Simulator.h" +#include "ServerControl.h" +#include "Test.h" +#include "decode.h" +#include "encode.h" + +#include "MemLeakFindOn.h" + +#define SHORT_TIMEOUT 5000 + +class TestWebServer : public HTTPServer +{ +public: + TestWebServer(); + ~TestWebServer(); + + virtual void Handle(HTTPRequest &rRequest, HTTPResponse &rResponse); + +}; + +// Build a nice HTML response, so this can also be tested neatly in a browser +void TestWebServer::Handle(HTTPRequest &rRequest, HTTPResponse &rResponse) +{ + // Test redirection mechanism + if(rRequest.GetRequestURI() == "/redirect") + { + rResponse.SetAsRedirect("/redirected"); + return; + } + + // Set a cookie? + if(rRequest.GetRequestURI() == "/set-cookie") + { + rResponse.SetCookie("SetByServer", "Value1"); + } + + #define DEFAULT_RESPONSE_1 "\nTEST SERVER RESPONSE\n

    Test response

    \n

    URI: " + #define DEFAULT_RESPONSE_3 "

    \n

    Query string: " + #define DEFAULT_RESPONSE_4 "

    \n

    Method: " + #define DEFAULT_RESPONSE_5 "

    \n

    Decoded query:
    " + #define DEFAULT_RESPONSE_6 "

    \n

    Content type: " + #define DEFAULT_RESPONSE_7 "

    \n

    Content length: " + #define DEFAULT_RESPONSE_8 "

    \n

    Cookies:
    \n" + #define DEFAULT_RESPONSE_2 "

    \n\n\n" + + rResponse.SetResponseCode(HTTPResponse::Code_OK); + rResponse.SetContentType("text/html"); + rResponse.Write(DEFAULT_RESPONSE_1, sizeof(DEFAULT_RESPONSE_1) - 1); + const std::string &ruri(rRequest.GetRequestURI()); + rResponse.Write(ruri.c_str(), ruri.size()); + rResponse.Write(DEFAULT_RESPONSE_3, sizeof(DEFAULT_RESPONSE_3) - 1); + const std::string &rquery(rRequest.GetQueryString()); + rResponse.Write(rquery.c_str(), rquery.size()); + rResponse.Write(DEFAULT_RESPONSE_4, sizeof(DEFAULT_RESPONSE_4) - 1); + { + const char *m = "????"; + switch(rRequest.GetMethod()) + { + case HTTPRequest::Method_GET: m = "GET "; break; + case HTTPRequest::Method_HEAD: m = "HEAD"; break; + case HTTPRequest::Method_POST: m = "POST"; break; + default: m = "UNKNOWN"; + } + rResponse.Write(m, 4); + } + rResponse.Write(DEFAULT_RESPONSE_5, sizeof(DEFAULT_RESPONSE_5) - 1); + { + const HTTPRequest::Query_t &rquery(rRequest.GetQuery()); + for(HTTPRequest::Query_t::const_iterator i(rquery.begin()); i != rquery.end(); ++i) + { + rResponse.Write("\nPARAM:", 7); + rResponse.Write(i->first.c_str(), i->first.size()); + rResponse.Write("=", 1); + rResponse.Write(i->second.c_str(), i->second.size()); + rResponse.Write("
    \n", 4); + } + } + rResponse.Write(DEFAULT_RESPONSE_6, sizeof(DEFAULT_RESPONSE_6) - 1); + const std::string &rctype(rRequest.GetContentType()); + rResponse.Write(rctype.c_str(), rctype.size()); + rResponse.Write(DEFAULT_RESPONSE_7, sizeof(DEFAULT_RESPONSE_7) - 1); + { + char l[32]; + rResponse.Write(l, ::sprintf(l, "%d", rRequest.GetContentLength())); + } + rResponse.Write(DEFAULT_RESPONSE_8, sizeof(DEFAULT_RESPONSE_8) - 1); + const HTTPRequest::CookieJar_t *pcookies = rRequest.GetCookies(); + if(pcookies != 0) + { + HTTPRequest::CookieJar_t::const_iterator i(pcookies->begin()); + for(; i != pcookies->end(); ++i) + { + char t[512]; + rResponse.Write(t, ::sprintf(t, "COOKIE:%s=%s
    \n", i->first.c_str(), i->second.c_str())); + } + } + rResponse.Write(DEFAULT_RESPONSE_2, sizeof(DEFAULT_RESPONSE_2) - 1); +} + +TestWebServer::TestWebServer() {} +TestWebServer::~TestWebServer() {} + +int test(int argc, const char *argv[]) +{ + if(argc >= 2 && ::strcmp(argv[1], "server") == 0) + { + // Run a server + TestWebServer server; + return server.Main("doesnotexist", argc - 1, argv + 1); + } + + if(argc >= 2 && ::strcmp(argv[1], "s3server") == 0) + { + // Run a server + S3Simulator server; + return server.Main("doesnotexist", argc - 1, argv + 1); + } + +#ifndef WIN32 + TEST_THAT(system("rm -rf *.memleaks") == 0); +#endif + + // Start the server + int pid = StartDaemon(0, TEST_EXECUTABLE " server testfiles/httpserver.conf", + "testfiles/httpserver.pid"); + TEST_THAT_OR(pid > 0, return 1); + + // Run the request script + TEST_THAT(::system("perl testfiles/testrequests.pl") == 0); + +#ifdef ENABLE_KEEPALIVE_SUPPORT // incomplete, need chunked encoding support + #ifndef WIN32 + signal(SIGPIPE, SIG_IGN); + #endif + + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + for (int i = 0; i < 4; i++) + { + HTTPRequest request(HTTPRequest::Method_GET, + "/test-one/34/341s/234?p1=vOne&p2=vTwo"); + + if (i < 2) + { + // first set of passes has keepalive off by default, + // so when i == 1 the socket has already been closed + // by the server, and we'll get -EPIPE when we try + // to send the request. + request.SetClientKeepAliveRequested(false); + } + else + { + request.SetClientKeepAliveRequested(true); + } + + if (i == 1) + { + sleep(1); // need time for our process to realise + // that the peer has died, otherwise no SIGPIPE :( + TEST_CHECK_THROWS( + request.Send(sock, SHORT_TIMEOUT), + ConnectionException, SocketWriteError); + sock.Close(); + sock.Open(Socket::TypeINET, "localhost", 1080); + continue; + } + else + { + request.Send(sock, SHORT_TIMEOUT); + } + + HTTPResponse response; + response.Receive(sock, SHORT_TIMEOUT); + + TEST_THAT(response.GetResponseCode() == HTTPResponse::Code_OK); + TEST_THAT(response.GetContentType() == "text/html"); + + IOStreamGetLine getline(response); + std::string line; + + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("TEST SERVER RESPONSE", + line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Test response

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    URI: /test-one/34/341s/234

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Query string: p1=vOne&p2=vTwo

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Method: GET

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Decoded query:
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("PARAM:p1=vOne
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("PARAM:p2=vTwo

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Content type:

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Content length: -1

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    Cookies:
    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("

    ", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + TEST_THAT(getline.GetLine(line)); + TEST_EQUAL("", line); + + if(!response.IsKeepAlive()) + { + BOX_TRACE("Server will close the connection, closing our end too."); + sock.Close(); + sock.Open(Socket::TypeINET, "localhost", 1080); + } + else + { + BOX_TRACE("Server will keep the connection open for more requests."); + } + } + + sock.Close(); +#endif // ENABLE_KEEPALIVE_SUPPORT + + // Kill it + TEST_THAT(StopDaemon(pid, "testfiles/httpserver.pid", + "generic-httpserver.memleaks", true)); + + // correct, official signature should succeed, with lower-case header + { + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html + HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); + request.SetHostName("johnsmith.s3.amazonaws.com"); + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", + "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + simulator.Handle(request, response); + TEST_EQUAL(200, response.GetResponseCode()); + + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + } + + // modified signature should fail + { + // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html + HTTPRequest request(HTTPRequest::Method_GET, "/photos/puppy.jpg"); + request.SetHostName("johnsmith.s3.amazonaws.com"); + request.AddHeader("date", "Tue, 27 Mar 2007 19:36:42 +0000"); + request.AddHeader("authorization", + "AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbB="); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + simulator.Handle(request, response); + TEST_EQUAL(401, response.GetResponseCode()); + + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("" + "Internal Server Error\n" + "

    Internal Server Error

    \n" + "

    An error, type Authentication Failed occured " + "when processing the request.

    " + "

    Please try again later.

    \n" + "\n", response_data); + } + + // S3Client tests with S3Simulator in-process server for debugging + { + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + S3Client client(&simulator, "johnsmith.s3.amazonaws.com", + "0PN5J17HBGZHT7JJ3X82", + "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o"); + + HTTPResponse response = client.GetObject("/photos/puppy.jpg"); + TEST_EQUAL(200, response.GetResponseCode()); + std::string response_data((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + + // make sure that assigning to HTTPResponse does clear stream + response = client.GetObject("/photos/puppy.jpg"); + TEST_EQUAL(200, response.GetResponseCode()); + response_data = std::string((const char *)response.GetBuffer(), + response.GetSize()); + TEST_EQUAL("omgpuppies!\n", response_data); + + response = client.GetObject("/nonexist"); + TEST_EQUAL(404, response.GetResponseCode()); + + FileStream fs("testfiles/testrequests.pl"); + response = client.PutObject("/newfile", fs); + TEST_EQUAL(200, response.GetResponseCode()); + + response = client.GetObject("/newfile"); + TEST_EQUAL(200, response.GetResponseCode()); + TEST_THAT(fs.CompareWith(response)); + TEST_EQUAL(0, ::unlink("testfiles/newfile")); + } + + { + HTTPRequest request(HTTPRequest::Method_PUT, "/newfile"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("authorization", + "AWS 0PN5J17HBGZHT7JJ3X82:XtMYZf0hdOo4TdPYQknZk0Lz7rw="); + request.AddHeader("Content-Type", "text/plain"); + + FileStream fs("testfiles/testrequests.pl"); + fs.CopyStreamTo(request); + request.SetForReading(); + + CollectInBufferStream response_buffer; + HTTPResponse response(&response_buffer); + + S3Simulator simulator; + simulator.Configure("testfiles/s3simulator.conf"); + simulator.Handle(request, response); + + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", + response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + TEST_EQUAL(0, response.GetSize()); + + FileStream f1("testfiles/testrequests.pl"); + FileStream f2("testfiles/newfile"); + TEST_THAT(f1.CompareWith(f2)); + TEST_EQUAL(0, ::unlink("testfiles/newfile")); + } + + // Start the S3Simulator server + pid = StartDaemon(0, TEST_EXECUTABLE " s3server testfiles/s3simulator.conf", + "testfiles/s3simulator.pid"); + TEST_THAT_OR(pid > 0, return 1); + + { + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + HTTPRequest request(HTTPRequest::Method_GET, "/nonexist"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:0cSX/YPdtXua1aFFpYmH1tc0ajA="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, SHORT_TIMEOUT); + + HTTPResponse response; + response.Receive(sock, SHORT_TIMEOUT); + std::string value; + TEST_EQUAL(404, response.GetResponseCode()); + } + + #ifndef WIN32 // much harder to make files inaccessible on WIN32 + // Make file inaccessible, should cause server to return a 403 error, + // unless of course the test is run as root :) + { + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + TEST_THAT(chmod("testfiles/testrequests.pl", 0) == 0); + HTTPRequest request(HTTPRequest::Method_GET, + "/testrequests.pl"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, SHORT_TIMEOUT); + + HTTPResponse response; + response.Receive(sock, SHORT_TIMEOUT); + std::string value; + TEST_EQUAL(403, response.GetResponseCode()); + TEST_THAT(chmod("testfiles/testrequests.pl", 0755) == 0); + } + #endif + + { + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + HTTPRequest request(HTTPRequest::Method_GET, + "/testrequests.pl"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:qc1e8u8TVl2BpIxwZwsursIb8U8="); + request.SetClientKeepAliveRequested(true); + request.Send(sock, SHORT_TIMEOUT); + + HTTPResponse response; + response.Receive(sock, SHORT_TIMEOUT); + std::string value; + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("qBmKRcEWBBhH6XAqsKU/eg24V3jf/kWKN9dJip1L/FpbYr9FDy7wWFurfdQOEMcY", response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("text/plain", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + + FileStream file("testfiles/testrequests.pl"); + TEST_THAT(file.CompareWith(response)); + } + + { + SocketStream sock; + sock.Open(Socket::TypeINET, "localhost", 1080); + + HTTPRequest request(HTTPRequest::Method_PUT, + "/newfile"); + request.SetHostName("quotes.s3.amazonaws.com"); + request.AddHeader("Date", "Wed, 01 Mar 2006 12:00:00 GMT"); + request.AddHeader("Authorization", "AWS 0PN5J17HBGZHT7JJ3X82:kfY1m6V3zTufRy2kj92FpQGKz4M="); + request.AddHeader("Content-Type", "text/plain"); + FileStream fs("testfiles/testrequests.pl"); + HTTPResponse response; + request.SendWithStream(sock, SHORT_TIMEOUT, &fs, response); + std::string value; + TEST_EQUAL(200, response.GetResponseCode()); + TEST_EQUAL("LriYPLdmOdAiIfgSm/F1YsViT1LW94/xUQxMsF7xiEb1a0wiIOIxl+zbwZ163pt7", response.GetHeaderValue("x-amz-id-2")); + TEST_EQUAL("F2A8CCCA26B4B26D", response.GetHeaderValue("x-amz-request-id")); + TEST_EQUAL("Wed, 01 Mar 2006 12:00:00 GMT", response.GetHeaderValue("Date")); + TEST_EQUAL("Sun, 1 Jan 2006 12:00:00 GMT", response.GetHeaderValue("Last-Modified")); + TEST_EQUAL("\"828ef3fdfa96f00ad9f27c383fc9ac7f\"", response.GetHeaderValue("ETag")); + TEST_EQUAL("", response.GetContentType()); + TEST_EQUAL("AmazonS3", response.GetHeaderValue("Server")); + TEST_EQUAL(0, response.GetSize()); + + FileStream f1("testfiles/testrequests.pl"); + FileStream f2("testfiles/newfile"); + TEST_THAT(f1.CompareWith(f2)); + } + + + // Kill it + TEST_THAT(StopDaemon(pid, "testfiles/s3simulator.pid", + "s3simulator.memleaks", true)); + + return 0; +} + diff --git a/test/raidfile/testextra b/test/raidfile/testextra new file mode 100644 index 00000000..1f4fbcb2 --- /dev/null +++ b/test/raidfile/testextra @@ -0,0 +1,7 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/1_0 +mkdir testfiles/1_1 +mkdir testfiles/1_2 +mkdir testfiles/2 diff --git a/test/raidfile/testfiles/raidfile.conf b/test/raidfile/testfiles/raidfile.conf new file mode 100644 index 00000000..6c6d02f9 --- /dev/null +++ b/test/raidfile/testfiles/raidfile.conf @@ -0,0 +1,30 @@ + +disc0 +{ + SetNumber = 0 + BlockSize = 2048 + Dir0 = testfiles/0_0 + Dir1 = testfiles/0_1 + Dir2 = testfiles/0_2 +} + +disc1 +{ + SetNumber = 1 + BlockSize = 2048 + Dir0 = testfiles/1_0 + Dir1 = testfiles/1_1 + Dir2 = testfiles/1_2 +} + +disc2 +{ + SetNumber = 2 + BlockSize = 2048 + Dir0 = testfiles/2 + Dir1 = testfiles/2 + Dir2 = testfiles/2 +} + + + diff --git a/test/raidfile/testraidfile.cpp b/test/raidfile/testraidfile.cpp new file mode 100644 index 00000000..c8150387 --- /dev/null +++ b/test/raidfile/testraidfile.cpp @@ -0,0 +1,985 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: test/raidfile/test.cpp +// Purpose: Test RaidFile system +// Created: 2003/07/08 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#include +#include +#include + +#ifdef HAVE_SYSCALL +#include +#endif + +#include + +#include "Test.h" +#include "RaidFileController.h" +#include "RaidFileWrite.h" +#include "RaidFileException.h" +#include "RaidFileRead.h" +#include "Guards.h" + +#include "MemLeakFindOn.h" + +#define RAID_BLOCK_SIZE 2048 +#define RAID_NUMBER_DISCS 3 + +#define TEST_DATA_SIZE (8*1024 + 173) + +#ifndef PLATFORM_CLIB_FNS_INTERCEPTION_IMPOSSIBLE + #define TRF_CAN_INTERCEPT +#endif + + +#ifdef TRF_CAN_INTERCEPT +// function in intercept.cpp for setting up errors +void intercept_setup_error(const char *filename, unsigned int errorafter, int errortoreturn, int syscalltoerror); +bool intercept_triggered(); +void intercept_clear_setup(); +#endif + +// Nice random data for testing written files +class R250 { +public: + // Set up internal state table with 32-bit random numbers. + // The bizarre bit-twiddling is because rand() returns 16 bits of which + // the bottom bit is always zero! Hence, I use only some of the bits. + // You might want to do something better than this.... + + R250(int seed) : posn1(0), posn2(103) + { + // populate the state and incr tables + srand(seed); + + for (int i = 0; i != stateLen; ++i) { + state[i] = ((rand() >> 2) << 19) ^ ((rand() >> 2) << 11) ^ (rand() >> 2); + incrTable[i] = i == stateLen - 1 ? 0 : i + 1; + } + + // stir up the numbers to ensure they're random + + for (int j = 0; j != stateLen * 4; ++j) + (void) next(); + } + + // Returns the next random number. Xor together two elements separated + // by 103 mod 250, replacing the first element with the result. Then + // increment the two indices mod 250. + inline int next() + { + int ret = (state[posn1] ^= state[posn2]); // xor and replace element + + posn1 = incrTable[posn1]; // increment indices using lookup table + posn2 = incrTable[posn2]; + + return ret; + } +private: + enum { stateLen = 250 }; // length of the state table + int state[stateLen]; // holds the random number state + int incrTable[stateLen]; // lookup table: maps i to (i+1) % stateLen + int posn1, posn2; // indices into the state table +}; + +void testReadingFileContents(int set, const char *filename, void *data, int datasize, bool TestRAIDProperties, int UsageInBlocks = -1) +{ + // Work out which disc is the "start" disc. + int h = 0; + int n = 0; + while(filename[n] != 0) + { + h += filename[n]; + n++; + } + int startDisc = h % RAID_NUMBER_DISCS; + +//printf("UsageInBlocks = %d\n", UsageInBlocks); + + // sizes of data to read + static int readsizes[] = {2047, 1, 1, 2047, 12, 1, 1, RAID_BLOCK_SIZE - (12+1+1), RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 246, (RAID_BLOCK_SIZE * 3) + 3, 243}; + + // read the data in to test it + char testbuff[(RAID_BLOCK_SIZE * 3) + 128]; // bigger than the max request above! + std::auto_ptr pread = RaidFileRead::Open(set, filename); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + //printf("%d, %d\n", pread->GetFileSize(), datasize); + TEST_THAT(pread->GetFileSize() == datasize); + IOStream &readstream1 = *(pread.get()); + int dataread = 0; + int r; + int readsize = readsizes[0]; + int bsc = 1; + while((r = readstream1.Read(testbuff, readsize)) > 0) + { + //printf("== read, asked: %d actual: %d\n", readsize, r); + TEST_THAT(((dataread+r) == datasize) || r == readsize); + TEST_THAT(r > 0); + TEST_THAT(readstream1.StreamDataLeft()); // check IOStream interface is correct + for(int z = 0; z < r; ++z) + { + TEST_THAT(((char*)data)[dataread+z] == testbuff[z]); + /*if(((char*)data)[dataread+z] != testbuff[z]) + { + printf("z = %d\n", z); + }*/ + } + // Next size... + if(bsc <= (int)((sizeof(readsizes) / sizeof(readsizes[0])) - 1)) + { + readsize = readsizes[bsc++]; + } + dataread += r; + } + TEST_THAT(dataread == datasize); + pread->Close(); + + // open and close it... + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream2 = *(pread.get()); + + // positions to try seeking too.. + static int seekpos[] = {0, 1, 2, 887, 887+256 /* no seek required */, RAID_BLOCK_SIZE, RAID_BLOCK_SIZE + 1, RAID_BLOCK_SIZE - 1, RAID_BLOCK_SIZE*3, RAID_BLOCK_SIZE + 23, RAID_BLOCK_SIZE * 4, RAID_BLOCK_SIZE * 4 + 1}; + + for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) + { + //printf("== seekpos = %d\n", seekpos[p]); + // only try if test file size is big enough + if(seekpos[p]+256 > datasize) continue; + + readstream2.Seek(seekpos[p], IOStream::SeekType_Absolute); + TEST_THAT(readstream2.Read(testbuff, 256) == 256); + TEST_THAT(readstream2.GetPosition() == seekpos[p] + 256); + TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); + } + + // open and close it... + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream3 = *(pread.get()); + + int pos = 0; + for(unsigned int p = 0; p < (sizeof(seekpos) / sizeof(seekpos[0])); ++p) + { + // only try if test file size is big enough + if(seekpos[p]+256 > datasize) continue; + + //printf("pos %d, seekpos %d, p %d\n", pos, seekpos[p], p); + + readstream3.Seek(seekpos[p] - pos, IOStream::SeekType_Relative); + TEST_THAT(readstream3.Read(testbuff, 256) == 256); + pos = seekpos[p] + 256; + TEST_THAT(readstream3.GetPosition() == pos); + TEST_THAT(::memcmp(((char*)data) + seekpos[p], testbuff, 256) == 0); + } + + // Straight read of file + pread.reset(RaidFileRead::Open(set, filename).release()); + if(UsageInBlocks != -1) + { + TEST_THAT(UsageInBlocks == pread->GetDiscUsageInBlocks()); + } + IOStream &readstream4 = *(pread.get()); + pos = 0; + int bytesread = 0; + while((r = readstream4.Read(testbuff, 988)) != 0) + { + TEST_THAT(readstream4.StreamDataLeft()); // check IOStream interface is behaving as expected + + // check contents + TEST_THAT(::memcmp(((char*)data) + pos, testbuff, r) == 0); + + // move on + pos += r; + bytesread += r; + } + TEST_THAT(!readstream4.StreamDataLeft()); // check IOStream interface is correct + pread.reset(); + + // Be nasty, and create some errors for the RAID stuff to recover from... + if(TestRAIDProperties) + { + HideCategoryGuard hide(RaidFileRead::OPEN_IN_RECOVERY); + hide.Add(RaidFileRead::IO_ERROR); + hide.Add(RaidFileRead::RECOVERING_IO_ERROR); + HideSpecificExceptionGuard hex(RaidFileException::ExceptionType, + RaidFileException::ErrorOpeningFileForRead); + + char stripe1fn[256], stripe1fnRename[256]; + sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); + sprintf(stripe1fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, startDisc, + filename); + char stripe2fn[256], stripe2fnRename[256]; + sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + sprintf(stripe2fnRename, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf-REMOVED", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + + // Read with stripe1 + parity + TEST_THAT(::rename(stripe2fn, stripe2fnRename) == 0); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(::rename(stripe2fnRename, stripe2fn) == 0); + + // Read with stripe2 + parity + TEST_THAT(::rename(stripe1fn, stripe1fnRename) == 0); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(::rename(stripe1fnRename, stripe1fn) == 0); + + // Munged filename for avoidance + char mungefilename[256]; + char filenamepart[256]; + sprintf(filenamepart, "%s.rf", filename); + int m = 0, s = 0; + while(filenamepart[s] != '\0') + { + if(filenamepart[s] == '/') + { + mungefilename[m++] = '_'; + } + else if(filenamepart[s] == '_') + { + mungefilename[m++] = '_'; + mungefilename[m++] = '_'; + } + else + { + mungefilename[m++] = filenamepart[s]; + } + s++; + } + mungefilename[m++] = '\0'; + char stripe1munge[256]; + sprintf(stripe1munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR ".raidfile-unreadable" + DIRECTORY_SEPARATOR "%s", set, startDisc, + mungefilename); + char stripe2munge[256]; + sprintf(stripe2munge, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR ".raidfile-unreadable" + DIRECTORY_SEPARATOR "%s", set, + (startDisc + 1) % RAID_NUMBER_DISCS, mungefilename); + +#ifdef TRF_CAN_INTERCEPT + // Test I/O errors on opening + // stripe 1 + intercept_setup_error(stripe1fn, 0, EIO, SYS_open); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + + // Test error in reading stripe 2 + intercept_setup_error(stripe2fn, 0, EIO, SYS_open); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + + // Test I/O errors on seeking + // stripe 1, if the file is bigger than the minimum thing that it'll get seeked for + if(datasize > 257) + { + intercept_setup_error(stripe1fn, 1, EIO, SYS_lseek); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + } + + // Stripe 2, only if the file is big enough to merit this + if(datasize > (RAID_BLOCK_SIZE + 4)) + { + intercept_setup_error(stripe2fn, 1, EIO, SYS_lseek); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + } + + // Test I/O errors on read, but only if the file is size greater than 0 + if(datasize > 0) + { + // Where shall we error after? + int errafter = datasize / 4; + + // Test error in reading stripe 1 + intercept_setup_error(stripe1fn, errafter, EIO, SYS_readv); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe1munge)); + TEST_THAT(::rename(stripe1munge, stripe1fn) == 0); + + // Can only test error if file size > RAID_BLOCK_SIZE, as otherwise stripe2 has nothing in it + if(datasize > RAID_BLOCK_SIZE) + { + // Test error in reading stripe 2 + intercept_setup_error(stripe2fn, errafter, EIO, SYS_readv); + testReadingFileContents(set, filename, data, datasize, false /* avoid recursion! */, UsageInBlocks); + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + + // Check that the file was moved correctly. + TEST_THAT(TestFileExists(stripe2munge)); + TEST_THAT(::rename(stripe2munge, stripe2fn) == 0); + } + } +#endif // TRF_CAN_INTERCEPT + } +} + + +void testReadWriteFileDo(int set, const char *filename, void *data, int datasize, bool DoTransform) +{ + // Work out which disc is the "start" disc. + int h = 0; + int n = 0; + while(filename[n] != 0) + { + h += filename[n]; + n++; + } + int startDisc = h % RAID_NUMBER_DISCS; + + // Another to test the transform works OK... + RaidFileWrite write4(set, filename); + write4.Open(); + write4.Write(data, datasize); + // This time, don't discard and transform it to a RAID File + char writefnPre[256]; + sprintf(writefnPre, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rfwX", set, startDisc, filename); + TEST_THAT(TestFileExists(writefnPre)); + char writefn[256]; + sprintf(writefn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rfw", set, startDisc, filename); + int usageInBlocks = write4.GetDiscUsageInBlocks(); + write4.Commit(DoTransform); + // Check that files are nicely done... + if(!DoTransform) + { + TEST_THAT(TestFileExists(writefn)); + TEST_THAT(!TestFileExists(writefnPre)); + } + else + { + TEST_THAT(!TestFileExists(writefn)); + TEST_THAT(!TestFileExists(writefnPre)); + // Stripe file sizes + int fullblocks = datasize / RAID_BLOCK_SIZE; + int leftover = datasize - (fullblocks * RAID_BLOCK_SIZE); + int fs1 = -2; + if((fullblocks & 1) == 0) + { + // last block of data will be on the first stripe + fs1 = ((fullblocks / 2) * RAID_BLOCK_SIZE) + leftover; + } + else + { + // last block is on second stripe + fs1 = ((fullblocks / 2)+1) * RAID_BLOCK_SIZE; + } + char stripe1fn[256]; + sprintf(stripe1fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, startDisc, filename); + TEST_THAT(TestGetFileSize(stripe1fn) == fs1); + char stripe2fn[256]; + sprintf(stripe2fn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 1) % RAID_NUMBER_DISCS, filename); + TEST_THAT(TestGetFileSize(stripe2fn) == (int)(datasize - fs1)); + // Parity file size + char parityfn[256]; + sprintf(parityfn, "testfiles" DIRECTORY_SEPARATOR "%d_%d" + DIRECTORY_SEPARATOR "%s.rf", set, + (startDisc + 2) % RAID_NUMBER_DISCS, filename); + // Mildly complex calculation + unsigned int blocks = datasize / RAID_BLOCK_SIZE; + unsigned int bytesOver = datasize % RAID_BLOCK_SIZE; + int paritysize = (blocks / 2) * RAID_BLOCK_SIZE; + // Then add in stuff for the last couple of blocks + if((blocks & 1) == 0) + { + if(bytesOver == 0) + { + paritysize += sizeof(RaidFileRead::FileSizeType); + } + else + { + paritysize += (bytesOver == sizeof(RaidFileRead::FileSizeType))?(RAID_BLOCK_SIZE+sizeof(RaidFileRead::FileSizeType)):bytesOver; + } + } + else + { + paritysize += RAID_BLOCK_SIZE; + if(bytesOver == 0 || bytesOver >= (RAID_BLOCK_SIZE-sizeof(RaidFileRead::FileSizeType))) + { + paritysize += sizeof(RaidFileRead::FileSizeType); + } + } + //printf("datasize = %d, calc paritysize = %d, actual size of file = %d\n", datasize, paritysize, TestGetFileSize(parityfn)); + TEST_THAT(TestGetFileSize(parityfn) == paritysize); + //printf("stripe1 size = %d, stripe2 size = %d, parity size = %d\n", TestGetFileSize(stripe1fn), TestGetFileSize(stripe2fn), TestGetFileSize(parityfn)); + + // Check that block calculation is correct + //printf("filesize = %d\n", datasize); + #define TO_BLOCKS_ROUND_UP(x) (((x) + (RAID_BLOCK_SIZE-1)) / RAID_BLOCK_SIZE) + TEST_THAT(usageInBlocks == (TO_BLOCKS_ROUND_UP(paritysize) + TO_BLOCKS_ROUND_UP(fs1) + TO_BLOCKS_ROUND_UP(datasize - fs1))); + + // See about whether or not the files look correct + char testblock[1024]; // compiler bug? This can't go in the block below without corrupting stripe2fn... + if(datasize > (3*1024)) + { + int f; + TEST_THAT((f = ::open(stripe1fn, O_RDONLY | O_BINARY, + 0)) != -1); + TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); + for(unsigned int q = 0; q < sizeof(testblock); ++q) + { + TEST_THAT(testblock[q] == ((char*)data)[q]); + } + ::close(f); + TEST_THAT((f = ::open(stripe2fn, O_RDONLY | O_BINARY, + 0)) != -1); + TEST_THAT(sizeof(testblock) == ::read(f, testblock, sizeof(testblock))); + for(unsigned int q = 0; q < sizeof(testblock); ++q) + { + TEST_THAT(testblock[q] == ((char*)data)[q+RAID_BLOCK_SIZE]); + } + ::close(f); + } + } + + // See if the contents look right + testReadingFileContents(set, filename, data, datasize, DoTransform /* only test RAID stuff if it has been transformed to RAID */, usageInBlocks); +} + +void testReadWriteFile(int set, const char *filename, void *data, int datasize) +{ + // Test once, transforming it... + testReadWriteFileDo(set, filename, data, datasize, true); + + // And then again, not transforming it + std::string fn(filename); + fn += "NT"; + testReadWriteFileDo(set, fn.c_str(), data, datasize, false); +} + +bool list_matches(const std::vector &rList, const char *compareto[]) +{ + // count in compare to + int count = 0; + while(compareto[count] != 0) + count++; + + if((int)rList.size() != count) + { + return false; + } + + // Space for bools + bool *found = new bool[count]; + + for(int c = 0; c < count; ++c) + { + found[c] = false; + } + + for(int c = 0; c < count; ++c) + { + bool f = false; + for(int l = 0; l < (int)rList.size(); ++l) + { + if(rList[l] == compareto[c]) + { + f = true; + break; + } + } + found[c] = f; + } + + bool ret = true; + for(int c = 0; c < count; ++c) + { + if(found[c] == false) + { + ret = false; + } + } + + delete [] found; + + return ret; +} + +void test_overwrites() +{ + // Opening twice is bad + { + RaidFileWrite writeA(0, "overwrite_A"); + writeA.Open(); + writeA.Write("TESTTEST", 8); + + { +#if defined(HAVE_FLOCK) || HAVE_DECL_O_EXLOCK + RaidFileWrite writeA2(0, "overwrite_A"); + TEST_CHECK_THROWS(writeA2.Open(), RaidFileException, FileIsCurrentlyOpenForWriting); +#endif + } + } + + // But opening a file which has previously been open, but isn't now, is OK. + + // Generate a random pre-existing write file (and ensure that it doesn't exist already) + int f; + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "overwrite_B.rfwX", + O_WRONLY | O_CREAT | O_EXCL | O_BINARY, 0755)) != -1); + TEST_THAT(::write(f, "TESTTEST", 8) == 8); + ::close(f); + + // Attempt to overwrite it, which should work nicely. + RaidFileWrite writeB(0, "overwrite_B"); + writeB.Open(); + writeB.Write("TEST", 4); + TEST_THAT(writeB.GetFileSize() == 4); + writeB.Commit(); +} + + +int test(int argc, const char *argv[]) +{ + #ifndef TRF_CAN_INTERCEPT + printf("NOTE: Skipping intercept based tests on this platform.\n\n"); + #endif + + // Initialise the controller + RaidFileController &rcontroller = RaidFileController::GetController(); + rcontroller.Initialise("testfiles" DIRECTORY_SEPARATOR "raidfile.conf"); + + // some data + char data[TEST_DATA_SIZE]; + R250 random(619); + for(unsigned int l = 0; l < sizeof(data); ++l) + { + data[l] = random.next() & 0xff; + } + char data2[57]; + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + data2[l] = l; + } + + // Try creating a directory + RaidFileWrite::CreateDirectory(0, "test-dir"); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_1" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(TestDirExists("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test-dir")); + TEST_THAT(RaidFileRead::DirectoryExists(0, "test-dir")); + TEST_THAT(!RaidFileRead::DirectoryExists(0, "test-dir-not")); + + + // Test converting to disc set names + { + std::string n1(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 0)); + std::string n2(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 1)); + std::string n3(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 2)); + std::string n4(RaidFileController::DiscSetPathToFileSystemPath(0, "testX", 3)); + TEST_THAT(n1 != n2); + TEST_THAT(n2 != n3); + TEST_THAT(n1 != n3); + TEST_THAT(n1 == n4); // ie wraps around + BOX_TRACE("Gen paths = '" << n1 << "', '" << n2 << + "', '" << n3); + } + + // Test that creating and deleting a RaidFile with the wrong + // reference counts throws the expected errors. + { + RaidFileWrite write1(0, "write1", 1); + write1.Open(); + write1.Commit(); + TEST_CHECK_THROWS(write1.Delete(), RaidFileException, + RequestedDeleteReferencedFile); + } + + { + RaidFileWrite write1(0, "write1", 0); + write1.Open(true); + TEST_CHECK_THROWS(write1.Commit(), RaidFileException, + RequestedModifyUnreferencedFile); + write1.Delete(); + } + + { + TEST_CHECK_THROWS(RaidFileWrite write1(0, "write1", 2), + RaidFileException, + RequestedModifyMultiplyReferencedFile); + } + + // Create a RaidFile + RaidFileWrite write1(0, "test1"); + IOStream &write1stream = write1; // use the stream interface where possible + write1.Open(); + write1stream.Write(data, sizeof(data)); + write1stream.Seek(1024, IOStream::SeekType_Absolute); + write1stream.Write(data2, sizeof(data2)); + write1stream.Seek(1024, IOStream::SeekType_Relative); + write1stream.Write(data2, sizeof(data2)); + write1stream.Seek(0, IOStream::SeekType_End); + write1stream.Write(data, sizeof(data)); + + // Before it's deleted, check to see the contents are as expected + int f; + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfwX", O_RDONLY | O_BINARY, 0)) + >= 0); + char buffer[sizeof(data)]; + int bytes_read = ::read(f, buffer, sizeof(buffer)); + TEST_THAT(bytes_read == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+1024] == data2[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); + } + TEST_THAT(::lseek(f, sizeof(data), SEEK_SET) == sizeof(buffer)); + bytes_read = ::read(f, buffer, sizeof(buffer)); + TEST_THAT(bytes_read == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + // make sure that's the end of the file + TEST_THAT(::read(f, buffer, sizeof(buffer)) == 0); + ::close(f); + + // Commit the data + write1.Commit(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + >= 0); + ::close(f); + + // Now try and read it + { + std::auto_ptr pread = RaidFileRead::Open(0, "test1"); + TEST_THAT(pread->GetFileSize() == sizeof(buffer)*2); + + char buffer[sizeof(data)]; + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+1024] == data2[l]); + } + for(unsigned int l = 0; l < sizeof(data2); ++l) + { + TEST_THAT(buffer[l+2048+sizeof(data2)] == data2[l]); + } + pread->Seek(sizeof(data), IOStream::SeekType_Absolute); + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == sizeof(buffer)); + for(unsigned int l = 0; l < 1024; ++l) + { + TEST_THAT(buffer[l] == data[l]); + } + // make sure that's the end of the file + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + // Seek backwards a bit + pread->Seek(-1024, IOStream::SeekType_Relative); + TEST_THAT(pread->Read(buffer, 1024) == 1024); + // make sure that's the end of the file + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + // Test seeking to end works + pread->Seek(-1024, IOStream::SeekType_Relative); + TEST_THAT(pread->Read(buffer, 512) == 512); + pread->Seek(0, IOStream::SeekType_End); + TEST_THAT(pread->Read(buffer, sizeof(buffer)) == 0); + } + + // Delete it + RaidFileWrite writeDel(0, "test1"); + writeDel.Delete(); + + // And again... + RaidFileWrite write2(0, "test1"); + write2.Open(); + write2.Write(data, sizeof(data)); + // This time, discard it + write2.Discard(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + == -1); + + // And leaving it there... + RaidFileWrite writeLeave(0, "test1"); + writeLeave.Open(); + writeLeave.Write(data, sizeof(data)); + // This time, commit it + writeLeave.Commit(); + TEST_THAT((f = ::open("testfiles" DIRECTORY_SEPARATOR "0_2" + DIRECTORY_SEPARATOR "test1.rfw", O_RDONLY | O_BINARY, 0)) + != -1); + ::close(f); + + // Then check that the thing will refuse to open it again. + RaidFileWrite write3(0, "test1"); + TEST_CHECK_THROWS(write3.Open(), RaidFileException, CannotOverwriteExistingFile); + + // Test overwrite behaviour + test_overwrites(); + + // Then... open it again allowing overwrites + RaidFileWrite write3b(0, "test1"); + write3b.Open(true); + // Write something + write3b.Write(data + 3, sizeof(data) - 3); + write3b.Commit(); + // Test it + testReadingFileContents(0, "test1", data+3, sizeof(data) - 3, false + /* TestRAIDProperties */); + + // And once again, but this time making it a raid file + RaidFileWrite write3c(0, "test1"); + write3c.Open(true); + // Write something + write3c.Write(data + 7, sizeof(data) - 7); + write3c.Commit(true); // make RAID + // Test it + testReadingFileContents(0, "test1", data+7, sizeof(data) - 7, false + /*TestRAIDProperties*/); + + // Test opening a file which doesn't exist + TEST_CHECK_THROWS( + std::auto_ptr preadnotexist = RaidFileRead::Open(1, "doesnt-exist"), + RaidFileException, RaidFileDoesntExist); + + { + // Test unrecoverable damage + RaidFileWrite w(0, "damage"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + + // Try removing the parity file + TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "damage.rf", + "testfiles" DIRECTORY_SEPARATOR "0_0" + DIRECTORY_SEPARATOR "damage.rf-NT") == 0); + { + std::auto_ptr pr0 = RaidFileRead::Open(0, "damage"); + pr0->Read(buffer, sizeof(data)); + } + TEST_THAT(::rename("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf-NT", "testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); + + // Delete one of the files + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "damage.rf") == 0); // stripe 1 + +#ifdef TRF_CAN_INTERCEPT + // Open it and read... + { + intercept_setup_error("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "damage.rf", 0, EIO, SYS_read); // stripe 2 + std::auto_ptr pr1 = RaidFileRead::Open(0, "damage"); + TEST_CHECK_THROWS( + pr1->Read(buffer, sizeof(data)), + RaidFileException, OSError); + + TEST_THAT(intercept_triggered()); + intercept_clear_setup(); + } +#endif //TRF_CAN_INTERCEPT + + // Delete another + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "damage.rf") == 0); // parity + + TEST_CHECK_THROWS( + std::auto_ptr pread2 = RaidFileRead::Open(0, "damage"), + RaidFileException, FileIsDamagedNotRecoverable); + } + + // Test reading a directory + { + RaidFileWrite::CreateDirectory(0, "dirread"); + // Make some contents + RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "dfsdf1"); + RaidFileWrite::CreateDirectory(0, "dirread" DIRECTORY_SEPARATOR "ponwq2"); + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "sdf9873241"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + } + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "fsdcxjni3242"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(true); + } + { + RaidFileWrite w(0, "dirread" DIRECTORY_SEPARATOR "cskjnds3"); + w.Open(); + w.Write(data, sizeof(data)); + w.Commit(false); + } + + const static char *dir_list1[] = {"dfsdf1", "ponwq2", 0}; + const static char *file_list1[] = {"sdf9873241", "fsdcxjni3242", "cskjnds3", 0}; + const static char *file_list2[] = {"fsdcxjni3242", "cskjnds3", 0}; + + std::vector names; + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_DirsOnly, names)); + TEST_THAT(list_matches(names, dir_list1)); + // Delete things + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_0" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + // Delete something else so that it's not recoverable + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_1" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(false == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list1)); + // And finally... + TEST_THAT(::unlink("testfiles" DIRECTORY_SEPARATOR "0_2" DIRECTORY_SEPARATOR "dirread" DIRECTORY_SEPARATOR "sdf9873241.rf") == 0); + TEST_THAT(true == RaidFileRead::ReadDirectoryContents(0, std::string("dirread"), RaidFileRead::DirReadType_FilesOnly, names)); + TEST_THAT(list_matches(names, file_list2)); + } + + // Check that sizes are reported correctly for non-raid discs + { + int sizeInBlocks = (sizeof(data) + RAID_BLOCK_SIZE - 1) / RAID_BLOCK_SIZE; + // for writing + { + RaidFileWrite write(2, "testS"); + write.Open(); + write.Write(data, sizeof(data)); + TEST_THAT(write.GetDiscUsageInBlocks() == sizeInBlocks); + write.Commit(); + } + // for reading + { + std::auto_ptr pread(RaidFileRead::Open(2, "testS")); + TEST_THAT(pread->GetDiscUsageInBlocks() == sizeInBlocks); + } + } + +//printf("SKIPPING tests ------------------\n"); +//return 0; + + // Test a load of transformed things + #define BIG_BLOCK_SIZE (25*1024 + 19) + MemoryBlockGuard bigblock(BIG_BLOCK_SIZE); + R250 randomX2(2165); + for(unsigned int l = 0; l < BIG_BLOCK_SIZE; ++l) + { + ((char*)(void*)bigblock)[l] = randomX2.next() & 0xff; + } + + // First on one size of data, on different discs + testReadWriteFile(0, "testdd", data, sizeof(data)); + testReadWriteFile(0, "test2", bigblock, BIG_BLOCK_SIZE); + testReadWriteFile(1, "testThree", bigblock, BIG_BLOCK_SIZE - 2048); + testReadWriteFile(1, "testX", bigblock, BIG_BLOCK_SIZE - 2289); + testReadWriteFile(1, "testSmall0", data, 0); + testReadWriteFile(1, "testSmall1", data, 1); + testReadWriteFile(1, "testSmall2", data, 2); + testReadWriteFile(1, "testSmall3", data, 3); + testReadWriteFile(1, "testSmall4", data, 4); + testReadWriteFile(0, "testSmall5", data, 5); + testReadWriteFile(0, "testSmall6", data, 6); + testReadWriteFile(1, "testSmall7", data, 7); + testReadWriteFile(1, "testSmall8", data, 8); + testReadWriteFile(1, "testSmall9", data, 9); + testReadWriteFile(1, "testSmall10", data, 10); + // See about a file which is one block bigger than the previous tests + { + char dataonemoreblock[TEST_DATA_SIZE + RAID_BLOCK_SIZE]; + R250 random(715); + for(unsigned int l = 0; l < sizeof(dataonemoreblock); ++l) + { + dataonemoreblock[l] = random.next() & 0xff; + } + testReadWriteFile(0, "testfour", dataonemoreblock, sizeof(dataonemoreblock)); + } + + // Some more nasty sizes + static int nastysize[] = {0, 1, 2, 7, 8, 9, (RAID_BLOCK_SIZE/2)+3, + RAID_BLOCK_SIZE-9, RAID_BLOCK_SIZE-8, RAID_BLOCK_SIZE-7, RAID_BLOCK_SIZE-6, RAID_BLOCK_SIZE-5, + RAID_BLOCK_SIZE-4, RAID_BLOCK_SIZE-3, RAID_BLOCK_SIZE-2, RAID_BLOCK_SIZE-1}; + for(int o = 0; o <= 5; ++o) + { + for(unsigned int n = 0; n < (sizeof(nastysize)/sizeof(nastysize[0])); ++n) + { + int s = (o*RAID_BLOCK_SIZE)+nastysize[n]; + char fn[64]; + sprintf(fn, "testN%d", s); + testReadWriteFile(n&1, fn, bigblock, s); + } + } + + // Finally, a mega test (not necessary for every run, I would have thought) +/* unsigned int megamax = (1024*128) + 9; + MemoryBlockGuard megablock(megamax); + R250 randomX3(183); + for(unsigned int l = 0; l < megamax; ++l) + { + ((char*)(void*)megablock)[l] = randomX3.next() & 0xff; + } + for(unsigned int s = 0; s < megamax; ++s) + { + testReadWriteFile(s & 1, "mega", megablock, s); + RaidFileWrite deleter(s & 1, "mega"); + deleter.Delete(); + RaidFileWrite deleter2(s & 1, "megaNT"); + deleter2.Delete(); + }*/ + + return 0; +} diff --git a/test/s3store/testextra b/test/s3store/testextra new file mode 100644 index 00000000..798c8c67 --- /dev/null +++ b/test/s3store/testextra @@ -0,0 +1,4 @@ +mkdir testfiles/0_0 +mkdir testfiles/0_1 +mkdir testfiles/0_2 +mkdir testfiles/bbackupd-data diff --git a/test/s3store/testfiles/bbackupd.conf b/test/s3store/testfiles/bbackupd.conf new file mode 100644 index 00000000..77640e5e --- /dev/null +++ b/test/s3store/testfiles/bbackupd.conf @@ -0,0 +1,61 @@ + +CertificateFile = testfiles/clientCerts.pem +PrivateKeyFile = testfiles/clientPrivKey.pem +TrustedCAsFile = testfiles/clientTrustedCAs.pem + +KeysFile = testfiles/bbackupd.keys + +DataDirectory = testfiles/bbackupd-data + +S3Store +{ + HostName = localhost + Port = 22080 + BasePath = /subdir/ + AccessKey = 0PN5J17HBGZHT7JJ3X82 + SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o +} + +UpdateStoreInterval = 3 +BackupErrorDelay = 10 +MinimumFileAge = 4 +MaxUploadWait = 24 +DeleteRedundantLocationsAfter = 10 + +FileTrackingSizeThreshold = 1024 +DiffingUploadSizeThreshold = 1024 + +MaximumDiffingTime = 3 +KeepAliveTime = 1 + +ExtendedLogging = no +ExtendedLogFile = testfiles/bbackupd.log + +CommandSocket = testfiles/bbackupd.sock + +NotifyScript = /usr/bin/perl testfiles/notifyscript.pl +SyncAllowScript = /usr/bin/perl testfiles/syncallowscript.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 + } +} + diff --git a/test/s3store/testfiles/bbackupd.keys b/test/s3store/testfiles/bbackupd.keys new file mode 100644 index 00000000..d9135b97 Binary files /dev/null and b/test/s3store/testfiles/bbackupd.keys differ diff --git a/test/s3store/testfiles/clientTrustedCAs.pem b/test/s3store/testfiles/clientTrustedCAs.pem new file mode 100644 index 00000000..2a065879 --- /dev/null +++ b/test/s3store/testfiles/clientTrustedCAs.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjDCB9gIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRST09UMB4XDTAz +MTAwNzA4NTkzMloXDTMxMDIyMjA4NTkzMlowDzENMAsGA1UEAxMEUk9PVDCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAtZypR/5m5fuNMPNrSLdzwmXKqhdVZj/e +cZHUZvVuXQZboosAznDrbh8HgpuTw5vaZDEz8VfPwgIaROZDT3ztFIedLapJ7Ot9 +I4JNqSv/y3V9MKb7trTSPVvyYLqk9isLmw8wmEidJiLbWbIc2cHFXDvWNqTr2jF6 +u4Q8DvdVfAECAwEAATANBgkqhkiG9w0BAQUFAAOBgQAL1lyJ/5y44yjk2BK+tnrZ +hbK7Ghtqrq/uZ8RQq5sAme919TnPijh2tRBqSaUaD2K+Sgo3RNgUGbKhfHRU1pfM +USllHskTKiJu74ix/T3UOnjpQ946OLSl5zNsOdOgbjBDnozfPSrKeEGN0huBbmmt +SlL3iQzVXlF6NAhkzS54fQ== +-----END CERTIFICATE----- diff --git a/test/s3store/testfiles/s3simulator.conf b/test/s3store/testfiles/s3simulator.conf new file mode 100644 index 00000000..c9895e9f --- /dev/null +++ b/test/s3store/testfiles/s3simulator.conf @@ -0,0 +1,10 @@ +AccessKey = 0PN5J17HBGZHT7JJ3X82 +SecretKey = uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o +StoreDirectory = testfiles/store +AddressPrefix = http://localhost:22080 + +Server +{ + PidFile = testfiles/s3simulator.pid + ListenAddresses = inet:localhost:22080 +} diff --git a/test/s3store/testfiles/serverCerts.pem b/test/s3store/testfiles/serverCerts.pem new file mode 100644 index 00000000..92467618 --- /dev/null +++ b/test/s3store/testfiles/serverCerts.pem @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBlzCCAQACAQQwDQYJKoZIhvcNAQEFBQAwDzENMAsGA1UEAxMEUk9PVDAeFw0w +MzEwMDcwOTAwMTFaFw0zMTAyMjIwOTAwMTFaMBkxFzAVBgNVBAMTDlNUT1JFLTAw +MDAwMDA4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNj1fGSCaSl/1w1lRV +I8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCPcBq/gxZOYevp+QnwMc+nUSS7Px/n+q92 +cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlH +RJZNiS9Asme+5Zvjfz3Phy0YWwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBABhmdun/ +myn3l4SbH+PxSUaW/mSvBubFhbbl9wolwhzvGCrtY968jn464JUP1UwUnnvePUU2 +SSVPZOVCvobCfM6s20aOdlKvnn+7GZkjoFONuCw3O+1hIFTSyXFcJWBaYLuczVk1 +HfdIKKcVZ1CpAfnMhMxuu+nA7fjor4p1/K0t +-----END CERTIFICATE----- diff --git a/test/s3store/testfiles/serverPrivKey.pem b/test/s3store/testfiles/serverPrivKey.pem new file mode 100644 index 00000000..fd87607d --- /dev/null +++ b/test/s3store/testfiles/serverPrivKey.pem @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQDNj1fGSCaSl/1w1lRVI8qE6BqjvT6R0XXGdIV+dk/mHmE3NOCP +cBq/gxZOYevp+QnwMc+nUSS7Px/n+q92cl3a8ttInfZjLqg9o/wpd6dBfH4gLTG4 +bEujhMt1x4bEUJk/uWfnk5FhsJXDBrlHRJZNiS9Asme+5Zvjfz3Phy0YWwIDAQAB +AoGBAI88mjo1noM528Wb4+nr5bvVDHMadJYhccMXAMqNYMGGW9GfS/dHc6wNiSaX +P0+rVIyF+R+rAEBmDTKV0Vxk9xZQuAaDKjLluDkxSxSR869D2YOWYUfvjDo3OFlT +LMZf0eE7u/3Pm0MtxPctXszqvNnmb+IvPXzttGRgUfU5G+tJAkEA+IphkGMI4A3l +4KfxotZZU+HiJbRDFpm81RzCc2709KCMkXMEz/+xkvnqlo28jqOf7PRBeq/ecsZN +8BGvtyoqVQJBANO6uj6sPI66GaRqxV83VyUUdMmL9uFOccIMqW5q0rx5UDi0mG7t +Pjjz+ul1D247+dvVxnEBeW4C85TSNbbKR+8CQQChpV7PCZo8Hs3jz1bZEZAHfmIX +I6Z+jH7EHHBbo06ty72g263FmgdkECcCxCxemQzqj/IGWVvUSiVmfhpKhqIBAkAl +XbjswpzVW4aW+7jlevDIPHn379mcHan54x4rvHKAjLBZsZWNThVDG9vWQ7B7dd48 +q9efrfDuN1shko+kOMLFAkAGIc5w0bJNC4eu91Wr6AFgTm2DntyVQ9keVhYbrwrE +xY37dgVhAWVeLDOk6eVOVSYqEI1okXPVqvfOIoRJUYkn +-----END RSA PRIVATE KEY----- diff --git a/test/s3store/testfiles/serverReq.pem b/test/s3store/testfiles/serverReq.pem new file mode 100644 index 00000000..7475d406 --- /dev/null +++ b/test/s3store/testfiles/serverReq.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBWDCBwgIBADAZMRcwFQYDVQQDEw5TVE9SRS0wMDAwMDAwODCBnzANBgkqhkiG +9w0BAQEFAAOBjQAwgYkCgYEAzY9Xxkgmkpf9cNZUVSPKhOgao70+kdF1xnSFfnZP +5h5hNzTgj3Aav4MWTmHr6fkJ8DHPp1Ekuz8f5/qvdnJd2vLbSJ32Yy6oPaP8KXen +QXx+IC0xuGxLo4TLdceGxFCZP7ln55ORYbCVwwa5R0SWTYkvQLJnvuWb4389z4ct +GFsCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIdlFo8gbik1K/+4Ra87cQDZzn0L +wE9bZrxRMPXqGjCQ8HBCfvQMFa1Oc6fEczCJ/nmmd76j0HIXW7uYOELIT8L/Zvf5 +jw/z9/OvEOQal7H2JN2d6W4ZmYpQko5+e/bJmlrOxyBpcXk34BvyQen9pTmI6J4E +pkBN/5XUUvVJSM67 +-----END CERTIFICATE REQUEST----- diff --git a/test/s3store/testfiles/store/subdir/dirs/create-me.txt b/test/s3store/testfiles/store/subdir/dirs/create-me.txt new file mode 100644 index 00000000..e69de29b diff --git a/test/s3store/tests3store.cpp b/test/s3store/tests3store.cpp new file mode 100644 index 00000000..50bd2bfd --- /dev/null +++ b/test/s3store/tests3store.cpp @@ -0,0 +1,128 @@ +// -------------------------------------------------------------------------- +// +// File +// Name: tests3store.cpp +// Purpose: Test Amazon S3 storage VFS API and utilities +// Created: 2015/06/28 +// +// -------------------------------------------------------------------------- + +#include "Box.h" + +#ifndef WIN32 +# include +#endif + +#include "BackupAccountControl.h" +#include "BackupClientCryptoKeys.h" +#include "BackupDaemonConfigVerify.h" +#include "BackupStoreDirectory.h" +#include "BackupStoreInfo.h" +#include "Configuration.h" +#include "RaidFileController.h" +#include "ServerControl.h" +#include "SSLLib.h" +#include "Test.h" +#include "Utils.h" + +#include "MemLeakFindOn.h" + +#define DEFAULT_BBACKUPD_CONFIG_FILE "testfiles/bbackupd.conf" + +int s3simulator_pid = 0; + +bool StartSimulator() +{ + s3simulator_pid = StartDaemon(s3simulator_pid, + "../../bin/s3simulator/s3simulator " + bbstored_args + + " testfiles/s3simulator.conf", "testfiles/s3simulator.pid"); + return s3simulator_pid != 0; +} + +bool StopSimulator() +{ + bool result = StopDaemon(s3simulator_pid, "testfiles/s3simulator.pid", + "s3simulator.memleaks", true); + s3simulator_pid = 0; + return result; +} + +bool kill_running_daemons() +{ + if(FileExists("testfiles/s3simulator.pid")) + { + return KillServer("testfiles/s3simulator.pid", true); + } + else + { + return true; + } +} + +//! Simplifies calling setUp() with the current function name in each test. +#define SETUP_TEST_S3SIMULATOR() \ + SETUP(); \ + TEST_THAT(kill_running_daemons()); \ + TEST_THAT(StartSimulator()); \ + +#define TEARDOWN_TEST_S3SIMULATOR() \ + TEST_THAT(s3simulator_pid == 0 || StopSimulator()); \ + TEST_THAT(kill_running_daemons()); \ + TEARDOWN(); + +bool test_create_account_with_account_control() +{ + SETUP_TEST_S3SIMULATOR(); + + std::auto_ptr config = load_config_file(DEFAULT_BBACKUPD_CONFIG_FILE, + BackupDaemonConfigVerify); + S3BackupAccountControl control(*config); + control.CreateAccount("test", 1000, 2000); + + FileStream fs("testfiles/store/subdir/" S3_INFO_FILE_NAME); + std::auto_ptr info = BackupStoreInfo::Load(fs, fs.GetFileName(), + true); // ReadOnly + TEST_EQUAL(0, info->GetAccountID()); + TEST_EQUAL(1, info->GetLastObjectIDUsed()); + TEST_EQUAL(1, info->GetBlocksUsed()); + TEST_EQUAL(0, info->GetBlocksInCurrentFiles()); + TEST_EQUAL(0, info->GetBlocksInOldFiles()); + TEST_EQUAL(0, info->GetBlocksInDeletedFiles()); + TEST_EQUAL(1, info->GetBlocksInDirectories()); + TEST_EQUAL(0, info->GetDeletedDirectories().size()); + TEST_EQUAL(1000, info->GetBlocksSoftLimit()); + TEST_EQUAL(2000, info->GetBlocksHardLimit()); + TEST_EQUAL(0, info->GetNumCurrentFiles()); + TEST_EQUAL(0, info->GetNumOldFiles()); + TEST_EQUAL(0, info->GetNumDeletedFiles()); + TEST_EQUAL(1, info->GetNumDirectories()); + TEST_EQUAL(true, info->IsAccountEnabled()); + TEST_EQUAL(true, info->IsReadOnly()); + TEST_EQUAL(0, info->GetClientStoreMarker()); + TEST_EQUAL("test", info->GetAccountName()); + + FileStream root_stream("testfiles/store/subdir/dirs/0x1.dir"); + BackupStoreDirectory root_dir(root_stream); + TEST_EQUAL(0, root_dir.GetNumberOfEntries()); + + TEARDOWN_TEST_S3SIMULATOR(); +} + +int test(int argc, const char *argv[]) +{ + // SSL library + SSLLib::Initialise(); + + // Use the setup crypto command to set up all these keys, so that the bbackupquery command can be used + // for seeing what's going on. + BackupClientCryptoKeys_Setup("testfiles/bbackupd.keys"); + +#ifndef WIN32 + signal(SIGPIPE, SIG_IGN); +#endif + + TEST_THAT(test_create_account_with_account_control()); + + return finish_test_suite(); +} + diff --git a/test/win32/Makefile b/test/win32/Makefile new file mode 100644 index 00000000..1212bc6f --- /dev/null +++ b/test/win32/Makefile @@ -0,0 +1,5 @@ +timezone.exe: timezone.cpp Makefile + g++ -g -O0 -mno-cygwin -I../../lib/win32 -o timezone.exe timezone.cpp + +clean: + rm timezone.exe diff --git a/test/win32/testlibwin32.cpp b/test/win32/testlibwin32.cpp new file mode 100644 index 00000000..fb735c56 --- /dev/null +++ b/test/win32/testlibwin32.cpp @@ -0,0 +1,345 @@ +// win32test.cpp : Defines the entry point for the console application. +// + +//#include +#include "Box.h" + +#ifdef WIN32 + +#include +#include +#include + +#include "../../bin/bbackupd/BackupDaemon.h" +#include "BoxPortsAndFiles.h" +#include "emu.h" + +int main(int argc, char* argv[]) +{ + // ACL tests + char* exename = getenv("WINDIR"); + + PSID psidOwner; + PSID psidGroup; + PACL pDacl; + PSECURITY_DESCRIPTOR pSecurityDesc; + + DWORD result = GetNamedSecurityInfo( + exename, // pObjectName + SE_FILE_OBJECT, // ObjectType + DACL_SECURITY_INFORMATION | // SecurityInfo + GROUP_SECURITY_INFORMATION | + OWNER_SECURITY_INFORMATION, + &psidOwner, // ppsidOwner, + &psidGroup, // ppsidGroup, + &pDacl, // ppDacl, + NULL, // ppSacl, + &pSecurityDesc // ppSecurityDescriptor + ); + if (result != ERROR_SUCCESS) + { + printf("Error getting security info for '%s': error %d", + exename, result); + } + assert(result == ERROR_SUCCESS); + + char namebuf[1024]; + char domainbuf[1024]; + SID_NAME_USE nametype; + DWORD namelen = sizeof(namebuf); + DWORD domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, psidOwner, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("Owner:\n"); + printf("User name: %s\n", namebuf); + printf("Domain name: %s\n", domainbuf); + printf("Name type: %d\n", nametype); + printf("\n"); + + namelen = sizeof(namebuf); + domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, psidGroup, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("Group:\n"); + printf("User name: %s\n", namebuf); + printf("Domain name: %s\n", domainbuf); + printf("Name type: %d\n", nametype); + printf("\n"); + + ULONG numEntries; + PEXPLICIT_ACCESS pEntries; + result = GetExplicitEntriesFromAcl + ( + pDacl, // pAcl + &numEntries, // pcCountOfExplicitEntries, + &pEntries // pListOfExplicitEntries + ); + assert(result == ERROR_SUCCESS); + + printf("Found %lu explicit DACL entries for '%s'\n\n", + (unsigned long)numEntries, exename); + + for (ULONG i = 0; i < numEntries; i++) + { + EXPLICIT_ACCESS* pEntry = &(pEntries[i]); + printf("DACL entry %lu:\n", (unsigned long)i); + + DWORD perms = pEntry->grfAccessPermissions; + printf(" Access permissions: ", perms); + + #define PRINT_PERM(name) \ + if (perms & name) \ + { \ + printf(#name " "); \ + perms &= ~name; \ + } + + PRINT_PERM(FILE_ADD_FILE); + PRINT_PERM(FILE_ADD_SUBDIRECTORY); + PRINT_PERM(FILE_ALL_ACCESS); + PRINT_PERM(FILE_APPEND_DATA); + PRINT_PERM(FILE_CREATE_PIPE_INSTANCE); + PRINT_PERM(FILE_DELETE_CHILD); + PRINT_PERM(FILE_EXECUTE); + PRINT_PERM(FILE_LIST_DIRECTORY); + PRINT_PERM(FILE_READ_ATTRIBUTES); + PRINT_PERM(FILE_READ_DATA); + PRINT_PERM(FILE_READ_EA); + PRINT_PERM(FILE_TRAVERSE); + PRINT_PERM(FILE_WRITE_ATTRIBUTES); + PRINT_PERM(FILE_WRITE_DATA); + PRINT_PERM(FILE_WRITE_EA); + PRINT_PERM(STANDARD_RIGHTS_READ); + PRINT_PERM(STANDARD_RIGHTS_WRITE); + PRINT_PERM(SYNCHRONIZE); + PRINT_PERM(DELETE); + PRINT_PERM(READ_CONTROL); + PRINT_PERM(WRITE_DAC); + PRINT_PERM(WRITE_OWNER); + PRINT_PERM(MAXIMUM_ALLOWED); + PRINT_PERM(GENERIC_ALL); + PRINT_PERM(GENERIC_EXECUTE); + PRINT_PERM(GENERIC_WRITE); + PRINT_PERM(GENERIC_READ); + printf("\n"); + + if (perms) + { + printf(" Bits left over: %08x\n", perms); + } + assert(!perms); + + printf(" Access mode: "); + switch(pEntry->grfAccessMode) + { + case NOT_USED_ACCESS: + printf("NOT_USED_ACCESS\n"); break; + case GRANT_ACCESS: + printf("GRANT_ACCESS\n"); break; + case DENY_ACCESS: + printf("DENY_ACCESS\n"); break; + case REVOKE_ACCESS: + printf("REVOKE_ACCESS\n"); break; + case SET_AUDIT_SUCCESS: + printf("SET_AUDIT_SUCCESS\n"); break; + case SET_AUDIT_FAILURE: + printf("SET_AUDIT_FAILURE\n"); break; + default: + printf("Unknown (%08x)\n", pEntry->grfAccessMode); + } + + printf(" Trustee: "); + assert(pEntry->Trustee.pMultipleTrustee == NULL); + assert(pEntry->Trustee.MultipleTrusteeOperation == NO_MULTIPLE_TRUSTEE); + switch(pEntry->Trustee.TrusteeForm) + { + case TRUSTEE_IS_SID: + { + PSID trusteeSid = (PSID)(pEntry->Trustee.ptstrName); + + namelen = sizeof(namebuf); + domainlen = sizeof(domainbuf); + + assert(LookupAccountSid(NULL, trusteeSid, namebuf, &namelen, + domainbuf, &domainlen, &nametype)); + + printf("SID of %s\\%s (%d)\n", domainbuf, namebuf, nametype); + } + break; + case TRUSTEE_IS_NAME: + printf("Name\n"); break; + case TRUSTEE_BAD_FORM: + printf("Bad form\n"); assert(0); + case TRUSTEE_IS_OBJECTS_AND_SID: + printf("Objects and SID\n"); break; + case TRUSTEE_IS_OBJECTS_AND_NAME: + printf("Objects and name\n"); break; + default: + printf("Unknown form\n"); assert(0); + } + + printf(" Trustee type: "); + switch(pEntry->Trustee.TrusteeType) + { + case TRUSTEE_IS_UNKNOWN: + printf("Unknown type.\n"); break; + case TRUSTEE_IS_USER: + printf("User\n"); break; + case TRUSTEE_IS_GROUP: + printf("Group\n"); break; + case TRUSTEE_IS_DOMAIN: + printf("Domain\n"); break; + case TRUSTEE_IS_ALIAS: + printf("Alias\n"); break; + case TRUSTEE_IS_WELL_KNOWN_GROUP: + printf("Well-known group\n"); break; + case TRUSTEE_IS_DELETED: + printf("Deleted account\n"); break; + case TRUSTEE_IS_INVALID: + printf("Invalid trustee type\n"); break; + case TRUSTEE_IS_COMPUTER: + printf("Computer\n"); break; + default: + printf("Unknown type %d\n", pEntry->Trustee.TrusteeType); + assert(0); + } + + printf("\n"); + } + + assert(LocalFree((HLOCAL)pEntries) == 0); + assert(LocalFree((HLOCAL)pSecurityDesc) == 0); + + chdir("c:\\tmp"); + openfile("test", O_CREAT, 0); + struct stat ourfs; + //test our opendir, readdir and closedir + //functions + DIR *ourDir = opendir("C:"); + + if ( ourDir != NULL ) + { + struct dirent *info; + do + { + info = readdir(ourDir); + if (info) printf("File/Dir name is : %s\r\n", info->d_name); + } + while (info != NULL); + + closedir(ourDir); + } + + std::string diry("C:\\Projects\\boxbuild\\testfiles\\"); + ourDir = opendir(diry.c_str()); + if ( ourDir != NULL ) + { + struct dirent *info; + do + { + info = readdir(ourDir); + if (info == NULL) break; + std::string file(diry + info->d_name); + stat(file.c_str(), &ourfs); + if (info) printf("File/Dir name is : %s\r\n", info->d_name); + } + while ( info != NULL ); + + closedir(ourDir); + + } + + stat("c:\\windows", &ourfs); + stat("c:\\autoexec.bat", &ourfs); + printf("Finished dir read\n"); + + //test our getopt function + char * test_argv[] = + { + "foobar.exe", + "-qwc", + "-", + "-c", + "fgfgfg", + "-f", + "-l", + "hello", + "-", + "force-sync", + NULL + }; + int test_argc; + for (test_argc = 0; test_argv[test_argc]; test_argc++) { } + const char* opts = "qwc:l:"; + + assert(getopt(test_argc, test_argv, opts) == 'q'); + assert(getopt(test_argc, test_argv, opts) == 'w'); + assert(getopt(test_argc, test_argv, opts) == 'c'); + assert(strcmp(optarg, "-") == 0); + assert(getopt(test_argc, test_argv, opts) == 'c'); + assert(strcmp(optarg, "fgfgfg") == 0); + assert(getopt(test_argc, test_argv, opts) == '?'); + assert(optopt == 'f'); + assert(getopt(test_argc, test_argv, opts) == 'l'); + assert(strcmp(optarg, "hello") == 0); + assert(getopt(test_argc, test_argv, opts) == -1); + // assert(optopt == 0); // no more options + assert(strcmp(test_argv[optind], "-") == 0); + assert(strcmp(test_argv[optind+1], "force-sync") == 0); + //end of getopt test + + //now test our statfs funct + stat("c:\\cert.cer", &ourfs); + + char *timee; + + timee = ctime(&ourfs.st_mtime); + + if (S_ISREG(ourfs.st_mode)) + { + printf("is a normal file\n"); + } + else + { + printf("is a directory?\n"); + exit(1); + } + + lstat(getenv("WINDIR"), &ourfs); + + if ( S_ISDIR(ourfs.st_mode)) + { + printf("is a directory\n"); + } + else + { + printf("is a file?\n"); + exit(1); + } + + //test the syslog functions + openlog("Box Backup", 0,0); + //the old ones are the best... + syslog(LOG_ERR, "Hello World"); + syslog(LOG_ERR, "Value of int is: %i", 6); + + closelog(); + + /* + //first off get the path name for the default + char buf[MAX_PATH]; + + GetModuleFileName(NULL, buf, sizeof(buf)); + std::string buffer(buf); + std::string conf("-c " + buffer.substr(0,(buffer.find("win32test.exe"))) + "bbackupd.conf"); + //std::string conf( "-c " + buffer.substr(0,(buffer.find("bbackupd.exe"))) + "bbackupd.conf"); + */ + + return 0; +} + +#endif // WIN32 diff --git a/test/win32/timezone.cpp b/test/win32/timezone.cpp new file mode 100644 index 00000000..1d81bcb3 --- /dev/null +++ b/test/win32/timezone.cpp @@ -0,0 +1,87 @@ +#include +#include + +typedef int uid_t; +typedef int gid_t; +typedef int u_int32_t; + +#include "emu.h" + +int main(int argc, char** argv) +{ + time_t time_now = time(NULL); + char* time_str = strdup(asctime(gmtime(&time_now))); + time_str[24] = 0; + + printf("Time now is %d (%s)\n", time_now, time_str); + + char testfile[80]; + snprintf(testfile, sizeof(testfile), "test.%d", time_now); + printf("Test file is: %s\n", testfile); + + _unlink(testfile); + + /* + int fd = open(testfile, O_RDWR | O_CREAT | O_EXCL); + if (fd < 0) + { + perror("open"); + exit(1); + } + close(fd); + */ + + HANDLE fh = CreateFileA(testfile, FILE_READ_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE, NULL); + + if (!fh) + { + fprintf(stderr, "Failed to open file '%s': error %d\n", + testfile, GetLastError()); + exit(1); + } + + BY_HANDLE_FILE_INFORMATION fi; + + if (!GetFileInformationByHandle(fh, &fi)) + { + fprintf(stderr, "Failed to get file information for '%s': " + "error %d\n", testfile, GetLastError()); + exit(1); + } + + if (!CloseHandle(fh)) + { + fprintf(stderr, "Failed to close file: error %d\n", + GetLastError()); + exit(1); + } + + time_t created_time = ConvertFileTimeToTime_t(&fi.ftCreationTime); + time_str = strdup(asctime(gmtime(&created_time))); + time_str[24] = 0; + + printf("File created time: %d (%s)\n", created_time, time_str); + + printf("Difference is: %d\n", created_time - time_now); + + if (abs(created_time - time_now) > 30) + { + fprintf(stderr, "Error: time difference too big: " + "bug in emu.h?\n"); + exit(1); + } + + /* + sleep(1); + + if (_unlink(testfile) != 0) + { + perror("Failed to delete test file"); + exit(1); + } + */ + + exit(0); +} -- cgit v1.2.3 From 41a0c4dba5b0c6283bc9a1bfa09822e8cb2cb3a5 Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Fri, 30 Jun 2017 15:01:19 -0400 Subject: Import boxbackup_0.12~gitcf52058f-3.debian.tar.xz [dgit import tarball boxbackup 0.12~gitcf52058f-3 boxbackup_0.12~gitcf52058f-3.debian.tar.xz] --- NEWS | 24 ++ NEWS.upstream | 124 ++++++++ README.Debian | 117 +++++++ boxbackup-client.config | 117 +++++++ boxbackup-client.cron.d | 2 + boxbackup-client.dirs | 5 + boxbackup-client.docs | 3 + boxbackup-client.init | 71 +++++ boxbackup-client.install | 4 + boxbackup-client.manpages | 6 + boxbackup-client.postinst | 364 ++++++++++++++++++++++ boxbackup-client.postrm | 15 + boxbackup-client.service | 13 + boxbackup-client.templates | 137 ++++++++ boxbackup-server.config | 111 +++++++ boxbackup-server.dirs | 6 + boxbackup-server.docs | 3 + boxbackup-server.init | 82 +++++ boxbackup-server.install | 8 + boxbackup-server.logcheck.ignore | 10 + boxbackup-server.manpages | 7 + boxbackup-server.postinst | 212 +++++++++++++ boxbackup-server.postrm | 22 ++ boxbackup-server.service | 13 + boxbackup-server.templates | 77 +++++ changelog | 356 +++++++++++++++++++++ clean | 146 +++++++++ clean.sh | 8 + compat | 1 + control | 59 ++++ copyright | 48 +++ patches/03-adjust-syslog-facility.diff | 50 +++ patches/05-dont_use_net_for_docs.diff | 12 + patches/06-fixup-bbstored-certs.diff | 35 +++ patches/07-fix-ftbfs-signed-char.diff | 11 + patches/series | 4 + po/POTFILES.in | 2 + po/cs.po | 512 ++++++++++++++++++++++++++++++ po/da.po | 515 +++++++++++++++++++++++++++++++ po/de.po | 526 +++++++++++++++++++++++++++++++ po/es.po | 549 +++++++++++++++++++++++++++++++++ po/eu.po | 527 +++++++++++++++++++++++++++++++ po/fi.po | 504 ++++++++++++++++++++++++++++++ po/fr.po | 526 +++++++++++++++++++++++++++++++ po/gl.po | 516 +++++++++++++++++++++++++++++++ po/it.po | 525 +++++++++++++++++++++++++++++++ po/ja.po | 509 ++++++++++++++++++++++++++++++ po/nl.po | 528 +++++++++++++++++++++++++++++++ po/pt.po | 519 +++++++++++++++++++++++++++++++ po/pt_BR.po | 522 +++++++++++++++++++++++++++++++ po/ru.po | 519 +++++++++++++++++++++++++++++++ po/sv.po | 515 +++++++++++++++++++++++++++++++ po/templates.pot | 446 ++++++++++++++++++++++++++ po/vi.po | 514 ++++++++++++++++++++++++++++++ rules | 107 +++++++ source/format | 1 + testing-notes.org | 404 ++++++++++++++++++++++++ watch | 4 + 58 files changed, 11573 insertions(+) create mode 100644 NEWS create mode 100644 NEWS.upstream create mode 100644 README.Debian create mode 100644 boxbackup-client.config create mode 100644 boxbackup-client.cron.d create mode 100644 boxbackup-client.dirs create mode 100644 boxbackup-client.docs create mode 100644 boxbackup-client.init create mode 100644 boxbackup-client.install create mode 100644 boxbackup-client.manpages create mode 100644 boxbackup-client.postinst create mode 100644 boxbackup-client.postrm create mode 100644 boxbackup-client.service create mode 100644 boxbackup-client.templates create mode 100644 boxbackup-server.config create mode 100644 boxbackup-server.dirs create mode 100644 boxbackup-server.docs create mode 100644 boxbackup-server.init create mode 100644 boxbackup-server.install create mode 100644 boxbackup-server.logcheck.ignore create mode 100644 boxbackup-server.manpages create mode 100644 boxbackup-server.postinst create mode 100644 boxbackup-server.postrm create mode 100644 boxbackup-server.service create mode 100644 boxbackup-server.templates create mode 100644 changelog create mode 100644 clean create mode 100644 clean.sh create mode 100644 compat create mode 100644 control create mode 100644 copyright create mode 100644 patches/03-adjust-syslog-facility.diff create mode 100644 patches/05-dont_use_net_for_docs.diff create mode 100644 patches/06-fixup-bbstored-certs.diff create mode 100644 patches/07-fix-ftbfs-signed-char.diff create mode 100644 patches/series create mode 100644 po/POTFILES.in create mode 100644 po/cs.po create mode 100644 po/da.po create mode 100644 po/de.po create mode 100644 po/es.po create mode 100644 po/eu.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/gl.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/nl.po create mode 100644 po/pt.po create mode 100644 po/pt_BR.po create mode 100644 po/ru.po create mode 100644 po/sv.po create mode 100644 po/templates.pot create mode 100644 po/vi.po create mode 100755 rules create mode 100644 source/format create mode 100644 testing-notes.org create mode 100644 watch diff --git a/NEWS b/NEWS new file mode 100644 index 00000000..2c3e7c25 --- /dev/null +++ b/NEWS @@ -0,0 +1,24 @@ +boxbackup (0.11~rc2+r2072-1) unstable; urgency=low + + * The upstream parts of this file have been renamed to a new file called + NEWS.upstream to make the process of updating it easier. + + -- Reinhard Tartler Wed, 01 Apr 2009 10:24:51 +0200 + +boxbackup (0.10-1) unstable; urgency=low + + * This Package has been initially prepared and mantained by Jérôme + Schell since 2004 in a private repository. I like the software, and + decided to take it over in order to have it in Debian. Please note + that I'm actively looking for co-maintainers, so do not hesitate to + get a copy of my bzr branch and share your commits with me. + + The only major change has been to drop the boxbackup-utils package. It + contained only one single command to manage certificates. It has been + moved to the boxbackup-server package. + + The complete debconf integration has been written by Jérôme. It works + for me quite well. If it doesn't for you, please file a bug and CC + Jérôme to that bugreport. Thanks. + + -- Reinhard Tartler Wed, 25 Apr 2007 18:06:04 +0200 diff --git a/NEWS.upstream b/NEWS.upstream new file mode 100644 index 00000000..2ae473dd --- /dev/null +++ b/NEWS.upstream @@ -0,0 +1,124 @@ +The following notes have been copied from the boxbackup trac wiki at +http://www.boxbackup.org/trac/wiki/011?format=txt + +== Changes in This Release == + + * Fixed some bugs with backing up, restoring and comparing [#2 files over 2GB] in compressed size. + * Added new logging infrastructure, allows more control over whether messages are sent to the console or system logs, and at what level of detail. + * Changed keepalive and diff timers to run in real time, not CPU time. + * Enable KeepAlive time by default on new installations, set to 120 seconds. + * Added bbackupctl commands for improved scripting of syncs. + * Fixed a bug with restoring symlinks to directories outside of the backed-up location (thanks to Hans-Joachim Baader) + * Ported unit tests for Windows. + * Added full unit tests for keep-alives and diff timer on most platforms. + * Fixed a number of bugs in the Windows port. + * Added option to send Extended Logs to a file instead of to system logs. + * Added option to log all file access, for debugging when a file is not backed up or causes the backup to fail mysteriously. + * Improved error messages to identify the causes of some errors which were difficult to track down before. + * Added bbackupd option to set the length of time before unused locations are deleted. + * Changed default location of bbackupd.conf on Windows to the same directory as bbackupd.exe. + * Fixed a bug where bbstoreaccounts could modify an account while it was locked by a running backup. + * Improved command-line option handling. + * Added command-line help (-h option) to bbackupd and bbstored. + * Add a new -F option for daemons, which runs in the foreground but still accepts multiple connections, which is what SINGLEPROCESS used to do. + * Fixed compare of timestamps on filesystems which cannot set them more accurately than 1 second. + * Added new backup-start and backup-finish events to the NotifyScript, which can be used to implement more advanced functionality such as snapshotting databases. + * Added a new sample !NotifySysAdmin script for Windows, written by James O'Gorman in VBscript. + * Added support for multiple Box Backup (bbackupd) services on Windows, with different service names and named pipe names, to implement redundancy. + * Fixed bbackupd mysteriously failing to back up if one of the location paths did not exist. + * Fixed entering of international characters into bbackupquery on Windows (instructions) and Unixes with editline. + * Improve Makefiles by reducing verbosity during build, so that any errors and warnings can be seen more easily. + * Added saving of the list of unused root directory entries to the !StoreObjectInfoFile, so that they will persist across restarts of bbackup (thanks to Gary Niemcewicz). + * Updated built-in documentation (program manuals, installation guide and administrator's guide). + * Improved build targets (thanks to James O'Gorman). + * On Unix platforms, all commands have moved from bin to sbin. + +== Source Code == + +The source code for all platforms can be downloaded +[http://www.boxbackup.org/svn/box/packages/boxbackup-0.11rc2.tgz here], although for Windows Native builds please read the [wiki:Installation#Windows Windows installation] notes. + +== Upgrading == + +Upgrade all clients and servers to [wiki:010 0.10] first. + +Remove any Windows services before upgrading (with `bbackupd -r`) and reinstall after upgrading (with `bbackupd -i`). + +New logging options (LogAllFileAccess, ExtendedLogging and [wiki:ManualPages command-line options]) are useful but not required. To use LogAllFileAccess you need to start the daemon with the `-V` option as well. + +The protocol is the same, so it shouldn't require the store server to be updated at the same time as the clients, or even fix the order of updating them. We would recommend that you upgrade the store server first, and then the clients one at a time. + +You might want to either regenerate their configs, or look at the difference between a fresh config and their current one, to enable some useful options like KeepAliveTime (which is enabled by default in new installations) and to think about enabling StoreObjectInfoFile. + +Most syslog messages have changed their format, so any scripts which parse syslog will have to be updated. + +Anyone using SINGLEPROCESS in anger (e.g. to run bbstored as a managed service under daemontools or similar) should shoot themselves quietly in the foot and prepare to change it to '''-F''' after the upgrade. (this was never a documented option, and now behaves a little differently). + +If you have problems with large files (over 2GB compressed) not being backed up, restored or compared, you will need to delete them from the store server to fix them properly. You can do this before or after upgrading, but if they are uploaded again by the 0.10 client then the problem will not be solved. + +You will probably get this error message every backup run: + +{{{ +BACKUP PROBLEM on host your.client.host (unknown) +}}} + +To fix this, edit the !NotifyScript (usually `/etc/box/bbackupd/NotifyScript.sh`), find the line that starts with the word `else`, and add these three lines immediately before it: + +{{{ +elif [ "$1" = backup-start -o "$1" = backup-finish -o "$1" = backup-ok ]; then + # do nothing by default + true +}}} + +== Known Issues == + +[[TicketQuery(status!=closed)]] + +== Credits == + + * '''Martin Ebourne''' reviewed code from Windows port for merge; + + * '''Pierre-Henri Lavigne''' created and started maintaining fink packages for MacOS X; + + * '''Stuart Hickinbottom''', '''Mark''', '''Nestor Arocha Rodriguez''' and '''James Stark''' contributed code; + + * '''Pete Jalajas''' contributed the Windows installer + + * '''Charles Lecklider''' reviewed code from Windows port for merge; + + * '''Kenny Millington''' provided the bbreporter.py Python script; + + * '''Gary Niemcewicz''' tested and fixed support for Microsoft Visual Studio as a compiler; + + * '''James O'Gorman''' hosts our website, wrote a lot of documentation, bought us a real SSL certificate and fixed autoconf problems; + + * '''Ben Summers''' contributed ideas and reviewed code from Windows port for merge; + + * '''Reinhard Tartler''' created and started maintaining Debian packages; + + * '''Per Thomsen''' wrote the Docbook documentation; + + * '''Chris Wilson''' implemented most of the new features, tested Solaris, FreeBSD and MacOS X and helped out on the mailing list; + + * Testing and bug reports (in alphabetical order) + * Tom Albers + * Tobias Balle-Petersen + * Damien B + * Dave Bamford + * Torsten Boob + * Matt Brown (who also lent us a MacOS X laptop for testing) + * Eric Cronin + * Johann Glaser + * Alex Harper + * Guno Heitman + * Stuart Hickinbottom + * Richard Hurt + * Pete Jalajas + * David Kaufman + * kiru + * Paul MacKenzie + * Mitja Muzenic + * Tuukka Pasanen + * Phil Shelley + + * Please let us know if we've missed you out! diff --git a/README.Debian b/README.Debian new file mode 100644 index 00000000..749da0cb --- /dev/null +++ b/README.Debian @@ -0,0 +1,117 @@ +Quick setup guide for boxbackup system +-------------------------------------- + +NOTE: The debian package should handle most of the configuration +for you via debconf. + +However this is a quick guide if you prefer to do this by +yourself. + +If you want to use debconf to configure Boxbackup, do NOT follow +those explanations. Jump directly to the section on Managing certificates + +Boxbackup-server configuration +------------------------------ + +You need to create the server configuration files contained in +/etc/boxbackup. + +For this you must first use the raidfile-config script. + +raidfile-config /etc/boxbackup 2048 /raid/0.0 /raid/0.1 /raid/0.2 + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default) +- 2048 is the block size of the RAID system, this should be set to + the block size of the underlying filesystem +- the three following path names are the location of the 3 RAID partitions + used by boxbackup to store the backup. They should be on 3 different + physical drive. You can disable the use of userland RAID by specifying + only one path name. + +You should now have a file /etc/boxbackup/raidfile.conf that you can +customize to add another set of disc. + +Now run the bbstored-config script: + +bbstored-config /etc/boxbackup serverhostname bbstored + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default). +- serverhostname is the fqdn name of the server you are installing on, + this is used to determine on which interface the daemon will listen on. +- bbstored is the user the server will run under, this user is automatically + created by the Debian package. + +Now you have to manage your certificate. See below for this. + +To manage the client accounts use the bbstoreaccounts utility. +To add an account: +bbstoreaccounts create ACCOUNT_NUMBER DISC_SET SOFT_QUOTA HARD_QUOTA + +where: +- ACCOUNT_NUMBER is the account number to create, a 8 digits hexadecimal number. +- DISC_SET is a disc set number defined in /etc/boxbackup/raidfile.conf where the + files for that account will go into. +- SOFT_QUOTA is the soft storage quota size, the client will avoid to upload files + when reaching that limit +- HARD_QUOTA is the hard storage quota size, the server will not store files when + reaching that limit. + +An exemple of invocation: +bbstoreaccounts create 1EF235CA 0 1024M 1250M +(suffixes M, G and B are accepted for quota size meaning respectively Megabytes, +Gigabytes and Blocks) + +Boxbackup-client configuration +------------------------------ + +You need to create the client configuration files contained in +/etc/boxbackup. + +For this you must use the bbackupd-config script. + +bbackupd-config /etc/boxbackup lazy ACCOUNT_NUMBER SERVER_NAME /var/lib/bbackupd BACKUP_DIR [[BACKUP_DIR]...] + +where: +- /etc/boxbackup is the location of the configuration files (don't + change that as several scripts use that by default). +- lazy: backup mode, could be lazy (continuous scan of filesystem) or + snapshot (backup launch by a cron script, see /etc/cron.d/boxbackup-client) +- ACCOUT_NUMBER: your account number provided by the backup server administrator +- SERVER_NAME is the fqdn name of the server you will connect to. +- /var/lib/bbackupd: location of working directory (don't change) +- BACKUP_DIR: a list of directories to backup (they must not contain another + mounted filesystem) + + +Managing certificates +--------------------- + +For this you need to use the bbstored-certs script contained in the +boxbackup-server package. + +To initialise your CA (creates a "ca" directory with private key and certificate in it) launch: +bbstored-certs ca init + +To sign a server certificate: +bbstored-certs ca sign-server server-csr.pem + +To sign a client certificate: +bbstored-certs ca sign clientaccount-csr.pem + +You will find a more detailed documentation on the boxbackup Web site: +http://www.boxbackup.org + + +"Upstream" tarball +------------------ + +The "upstream" tarball is produced using git-archive from the upstream +git branch at github: + +https://github.com/boxbackup/boxbackup + + -- Reinhard Tartler , Sat, 17 Jun 2017 17:57:00 -0400 diff --git a/boxbackup-client.config b/boxbackup-client.config new file mode 100644 index 00000000..a2ed866d --- /dev/null +++ b/boxbackup-client.config @@ -0,0 +1,117 @@ +#!/bin/bash -e + +# Source debconf library +. /usr/share/debconf/confmodule + +# This conf script is capable of backing up +#db_version 2.0 +#db_capb backup + +#db_metaget debconf/priority value +#CONFPRIO=$RET + +# Handle with debconf or not? +db_input medium boxbackup-client/debconf || true +db_go +db_get boxbackup-client/debconf +if [ "$RET" = "false" ]; then + exit 0 +fi + +# Backup mode +db_get boxbackup-client/backupMode +OLDMODE=$RET + +db_input medium boxbackup-client/backupMode || true +db_go + + +# accountNumber +ANOK=0 +while [ $ANOK = 0 ]; do + db_input critical boxbackup-client/accountNumber || true + db_go + + db_get boxbackup-client/accountNumber + + if [ -z `echo $RET | sed 's/[[:xdigit:]]//g'` ]; then + ANOK=1 + fi + + if [ $ANOK = 0 ]; then + db_input critical boxbackup-client/incorrectAccountNumber || true + db_go + fi +done + +# backupServer +db_input critical boxbackup-client/backupServer || true +db_go + +# backupDirs +DIRSOK=0 +while [ $DIRSOK = 0 ]; do + db_input critical boxbackup-client/backupDirs || true + db_go + + db_get boxbackup-client/backupDirs + + if [ ! -z "$RET" ]; then + DIRSOK=1 + for dir in $RET; do + if [ ! -z `echo $dir | sed 's/^[[:space:]]*\/[[:alnum:]\.\_-]*\/*\([[:alnum:]\.\_-]*\/*\)*[[:space:]]*$//g'` ]; then + DIRSOK=0; + fi + done + fi + + if [ $DIRSOK = 0 ]; then + db_input critical boxbackup-client/incorrectDirectories || true + db_go + fi +done + +# UpdateStoreInterval MinimumFileAge MaxUploadWait +#db_get boxbackup-client/backupMode + +# This is a way to get back to the default values when switching the backup mode +#if [ ! -z $OLDMODE ]; then +# if [ $OLDMODE != $RET ]; then +# db_set boxbackup-client/UpdateStoreInterval "3600" +# db_set boxbackup-client/MinimumFileAge "21600" +# db_set boxbackup-client/MaxUploadWait "86400" +# fi +#fi + +db_get boxbackup-client/backupMode +if [ "$RET" = "lazy" ]; then + for param in UpdateStoreInterval MinimumFileAge MaxUploadWait; do + NUMOK=0 + while [ $NUMOK = 0 ]; do + db_input medium boxbackup-client/$param || true + db_go + + db_get boxbackup-client/$param + + if [ -z `echo $RET | sed 's/[[:digit:]]//g'` ]; then + NUMOK=1 + fi + + if [ $NUMOK = 0 ]; then + db_input critical boxbackup-client/IncorrectNumber || true + db_go + fi + done + done +fi + + +# NotifyMail +db_input medium boxbackup-client/notifyMail || true +db_go + +# x509 and private key +db_input medium boxbackup-client/generateCertificate || true +db_go + +exit 0 diff --git a/boxbackup-client.cron.d b/boxbackup-client.cron.d new file mode 100644 index 00000000..486e4be3 --- /dev/null +++ b/boxbackup-client.cron.d @@ -0,0 +1,2 @@ +# Launch the boxbackup client/server synchronization when in snapshot mode +#0 0 * * * root /usr/sbin/bbackupctl -q sync diff --git a/boxbackup-client.dirs b/boxbackup-client.dirs new file mode 100644 index 00000000..96e786f8 --- /dev/null +++ b/boxbackup-client.dirs @@ -0,0 +1,5 @@ +usr/sbin +var/lib/bbackupd +etc/boxbackup +etc/boxbackup/bbackupd + diff --git a/boxbackup-client.docs b/boxbackup-client.docs new file mode 100644 index 00000000..ccd1de71 --- /dev/null +++ b/boxbackup-client.docs @@ -0,0 +1,3 @@ +BUGS.txt +distribution/boxbackup/LINUX.txt +distribution/boxbackup/THANKS.txt diff --git a/boxbackup-client.init b/boxbackup-client.init new file mode 100644 index 00000000..90026ba4 --- /dev/null +++ b/boxbackup-client.init @@ -0,0 +1,71 @@ +#! /bin/sh +# +### BEGIN INIT INFO +# Provides: boxbackup-client +# Required-Start: $syslog $remote_fs $network +# Required-Stop: $syslog $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: boxbackup client +# Description: Init script to start and stop the boxbackup client +### END INIT INFO + +. /lib/lsb/init-functions + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/bbackupd +NAME=bbackupd +DESC=boxbackup-client +CONF=/etc/boxbackup/bbackupd.conf + +test -f $DAEMON || exit 0 + +test -f $CONF || exit 0 + +PIDFILE=`grep 'PidFile' $CONF | sed 's/[[:space:]]*PidFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +CERTFILE=`grep 'CertificateFile' $CONF | sed 's/[[:space:]]*CertificateFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +ACCNUM=`grep 'AccountNumber' $CONF | sed 's/[[:space:]]*AccountNumber[[:space:]]*=[[:space:]]*\([A-Za-z0-9/]*\)/\1/'` + +[ -z $PIDFILE ] && PIDFILE="/var/run/bbackupd.pid" + +# Don't start if certificate file or account number are not present +[ ! -e $CERTFILE -o -z $ACCNUM ] && exit 0 + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $CONF + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --oknodo --retry 5 --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + echo "$NAME." + ;; + reload|force-reload) + echo "Reloading $DESC configuration files." + start-stop-daemon --stop --signal 1 --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + ;; + restart) + echo -n "Restarting $DESC: " + start-stop-daemon --oknodo --retry 5 --stop --quiet --pidfile \ + $PIDFILE --exec $DAEMON + sleep 1 + start-stop-daemon --start --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + #echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/boxbackup-client.install b/boxbackup-client.install new file mode 100644 index 00000000..5dbf00bf --- /dev/null +++ b/boxbackup-client.install @@ -0,0 +1,4 @@ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupctl /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupd /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupd-config /usr/sbin/ +parcels/boxbackup-*-backup-client-*-gnu*/bbackupquery /usr/sbin/ diff --git a/boxbackup-client.manpages b/boxbackup-client.manpages new file mode 100644 index 00000000..562b2f7e --- /dev/null +++ b/boxbackup-client.manpages @@ -0,0 +1,6 @@ +docs/man/bbackupd.8.gz +docs/man/bbackupd.conf.5.gz +docs/man/bbackupd-config.8.gz +docs/man/bbackupctl.8.gz +docs/man/bbackupquery.8.gz + diff --git a/boxbackup-client.postinst b/boxbackup-client.postinst new file mode 100644 index 00000000..a0bc9a57 --- /dev/null +++ b/boxbackup-client.postinst @@ -0,0 +1,364 @@ +#! /bin/bash +# postinst script for boxbackup-client +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package +# +# quoting from the policy: +# Any necessary prompting should almost always be confined to the +# post-installation script, and should be protected with a conditional +# so that unnecessary prompting doesn't happen if a package's +# installation fails and the `postinst' is called with `abort-upgrade', +# `abort-remove' or `abort-deconfigure'. + +#loading debconf module +. /usr/share/debconf/confmodule + +CONFDIR=/etc/boxbackup +DEBCONFBB=$CONFDIR/bbackupd.debconf +BBCONF=$CONFDIR/bbackupd.conf +BBKEY=$CONFDIR/bbackupd/boxbackup-client-encrypt-key.raw +BBPRIVKEY=$CONFDIR/bbackupd/boxbackup-client-priv-key.pem +BBCERTREQ=$CONFDIR/bbackupd/boxbackup-client-cert-req.pem +BBCERT=$CONFDIR/bbackupd/boxbackup-client-cert.pem +BBCACERT=$CONFDIR/bbackupd/boxbackup-server-ca-cert.pem +DEBCONFNOTIFY=$CONFDIR/bbackupd/notifyadmin.debconf +NOTIFYSCRIPT=$CONFDIR/bbackupd/notifyadmin + +case "$1" in + configure) + db_get boxbackup-client/debconf + if [ "$RET" = "true" ]; then + # Generate configuration files + # backupd.conf + echo "#To reconfigure boxbackup-client run #dpkg-reconfigure boxbackup-client" >> $DEBCONFBB + + db_get boxbackup-client/backupServer + echo "StoreHostname = $RET" >> $DEBCONFBB + + db_get boxbackup-client/accountNumber + ACCOUNT=$RET + echo "AccountNumber = 0x$ACCOUNT" >> $DEBCONFBB + echo "KeysFile = $BBKEY" >> $DEBCONFBB + echo "" >> $DEBCONFBB + echo "CertificateFile = $BBCERT" >> $DEBCONFBB + echo "PrivateKeyFile = $BBPRIVKEY" >> $DEBCONFBB + echo "TrustedCAsFile = $BBCACERT" >> $DEBCONFBB + echo "" >> $DEBCONFBB + echo "DataDirectory = /var/lib/bbackupd" >> $DEBCONFBB + + cat >>$DEBCONFBB <<__EOF + +# This script is run whenever bbackupd 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. +# The default script emails the system administrator. +NotifyScript = $NOTIFYSCRIPT + +__EOF + + db_get boxbackup-client/backupMode + if [ "$RET" = "lazy" ]; then + db_get boxbackup-client/UpdateStoreInterval + UPDATE=$RET + [ -z "$UPDATE" ] && UPDATE="3600" + + db_get boxbackup-client/MinimumFileAge + FILEAGE=$RET + [ -z "$FILEAGE" ] && FILEAGE="21600" + + db_get boxbackup-client/MaxUploadWait + UPWAIT=$RET + [ -z "$UPWAIT" ] && UPWAIT="86400" + + AUTO=yes + else + AUTO=no + UPDATE=0 + FILEAGE=0 + UPWAIT=0 + fi + + cat >>$DEBCONFBB <<__EOF +# Backup mode specification +# With snapshot mode, you will need to run bbackupctl to instruct the daemon to upload files. +# Set to no for snapshot mode and yes for lazy mode +AutomaticBackup = $AUTO + +# A scan of the local discs will be made once an hour (approximately). +# To avoid cycles of load on the server, this time is randomly adjusted by a small +# percentage as the daemon runs. +# Defaults: 3600 for lazy mode - 0 for snapshot mode +UpdateStoreInterval = $UPDATE + +# A file must have been modified at least 6 hours ago before it will be uploaded. +# Defaults: 21600 for lazy mode - 0 for snapshot mode +MinimumFileAge = $FILEAGE + +# If a file is modified repeated, it won't be uploaded immediately in case it's modified again. +# However, it should be uploaded eventually. This is how long we should wait after first noticing +# a change. (1 day) +# Defaults: 86400 for lazy mode - 0 for snapshot mode +MaxUploadWait = $UPWAIT + +# 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. 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 want +# to spend more time searching for unchanged blocks. +MaximumDiffingTime = 20 + +# Uncomment this line to see exactly what the daemon is going when it's connected to the server. +# ExtendedLogging = yes + +# Use this to temporarily stop bbackupd from syncronising or connecting to the store. +# 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 prints 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 the script will +# be asked again in that number of seconds. +# For example, you could use this on a laptop to only backup when on a specific network. + +# SyncAllowScript = /path/to/intepreter/or/exe script-name parameters etc + +# Where the command socket is created in the filesystem. +CommandSocket = /var/run/bbackupd.sock + +Server +{ + PidFile = /var/run/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. +# +# 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. +# + +BackupLocations +{ +__EOF + + db_get boxbackup-client/backupDirs + + for dir in $RET; do + NAME=`echo $dir | sed 's/\//-/g' | sed 's/^-//'` + + # TODO : exclude encrypt key file from the backup + + echo " $NAME" >> $DEBCONFBB + echo " {" >> $DEBCONFBB + echo " Path = $dir" >> $DEBCONFBB + echo " }" >> $DEBCONFBB + done + + echo "}" >> $DEBCONFBB + + # Encryption key + if [ ! -e $BBKEY ]; then + if ! openssl rand -out $BBKEY 1024 >&2; then + echo "Can't generate encryption key. Check why." >&2 + fi + fi + + chmod 600 $BBKEY || true + + # SSL stuff + if [ ! -z "$ACCOUNT" ]; then + if [ ! -e $BBPRIVKEY -a ! -e $BBCERT ]; then + db_get boxbackup-client/generateCertificate + + if [ "$RET" = "true" ]; then + if ! openssl genrsa -out $BBPRIVKEY 2048 >&2; then + echo "Private key generation failed! Check why." >&2 + else + chmod 600 $BBPRIVKEY || true + fi + + + if ! openssl req -new -key $BBPRIVKEY -sha1 -out $BBCERTREQ >&2 <<__EOF +. +. +. +. +. +BACKUP-$ACCOUNT +. +. +. +__EOF + then + echo "Certificate request generation failed ! Check why." >&2 + fi + fi + fi + fi + + # Generate notify script + CLIENTNAME=`hostname --fqdn` + + db_get boxbackup-client/notifyMail + MAILTO=$RET + + cat >>$DEBCONFNOTIFY <<__EOF +#!/bin/sh +#To reconfigure boxbackup-client run #dpkg-reconfigure boxbackup-client + +# 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. + +SUBJECT="BACKUP PROBLEM on host $CLIENTNAME" +SENDTO="$MAILTO" + +if [ "\$1" = "" ]; then + echo "Usage: \$0 " >&2 + exit 2 +elif [ "\$1" = store-full ]; then + $sendmail \$SENDTO <&2 + exit 1 + ;; +esac + +db_stop + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/boxbackup-client.postrm b/boxbackup-client.postrm new file mode 100644 index 00000000..5272f93e --- /dev/null +++ b/boxbackup-client.postrm @@ -0,0 +1,15 @@ +#!/bin/sh +set -e + +if [ "$1" = "purge" ]; then + for i in /etc/boxbackup/bbackupd.conf /etc/boxbackup/bbackupd/notifyadmin; do + if [ -e $i ]; then + rm -f $i + fi + if which ucf >/dev/null; then + ucf -p $i + fi + done +fi + +#DEBHELPER# diff --git a/boxbackup-client.service b/boxbackup-client.service new file mode 100644 index 00000000..609674e1 --- /dev/null +++ b/boxbackup-client.service @@ -0,0 +1,13 @@ +[Unit] +Description=Box Backup Client +ConditionPathExists=/etc/boxbackup/bbackupd.conf +Wants=network.target +After=network.target + +[Service] +ExecStart=/usr/sbin/bbackupd -F -c /etc/boxbackup/bbackupd.conf +KillMode=process +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/boxbackup-client.templates b/boxbackup-client.templates new file mode 100644 index 00000000..cf0d41c8 --- /dev/null +++ b/boxbackup-client.templates @@ -0,0 +1,137 @@ +# These templates have been reviewed by the debian-l10n-english +# team +# +# If modifications/additions/rewording are needed, please ask +# for an advice to debian-l10n-english@lists.debian.org +# +# Even minor modifications require translation updates and such +# changes should be coordinated with translators and reviewers. + +Template: boxbackup-client/debconf +Type: boolean +Default: false +_Description: Should the BoxBackup client be configured automatically? + The package configuration scripts can create the configuration files + for the BoxBackup client. + . + You should choose this option if you are not familiar with BoxBackup's + configuration options. + . + Please read the /usr/share/doc/boxbackup-client/README.Debian for details + about the configuration of the BoxBackup client. + +Template: boxbackup-client/backupMode +Type: select +Choices: lazy, snapshot +#flag:comment:3 +# Translators, please keep reference to 'lazy' and 'snapshot' as +# these options are written as is in the software documentation +_Description: Run mode for the BoxBackup client: + The BoxBackup client supports two modes of backup: + . + In the 'lazy' mode, the backup daemon will regularly scan the file system + searching for modified files. It will then upload the files older than a + specified age to the backup server. + . + In the 'snapshot' mode the backup will be explicitly run at regular intervals. + A cron file (/etc/cron.d/boxbackup-client) is provided with the + package and should be adapted to suit your needs. + +Template: boxbackup-client/accountNumber +Type: string +_Description: Account number for this node on the backup server: + The administrator of the BoxBackup server should have assigned this client + a hexadecimal account number. + . + If no account number has been assigned yet, leave this field blank and + configure it later by running 'dpkg-reconfigure boxbackup-client' as root. + +Template: boxbackup-client/incorrectAccountNumber +Type: error +_Description: Invalid account number + The account number must be a hexadecimal number (such as 1F04 or 4500). + +Template: boxbackup-client/backupServer +Type: string +_Description: Fully qualified domain name of the backup server: + Please enter the fully qualified domain name of the BoxBackup server which + your client will use. + . + The client will connect to the server on TCP port 2201. + +Template: boxbackup-client/backupDirs +Type: string +_Description: List of directories to backup: + Please give a space-separated list of directories to be backed up onto + the remote server. + . + Those directories should not contain mounted file systems at any level + in their subdirectories. + +Template: boxbackup-client/incorrectDirectories +Type: error +_Description: Invalid path name + The path names to the directories must be absolute path names, separated + by spaces. + . + For example: /home/myaccount /etc/ + +Template: boxbackup-client/UpdateStoreInterval +Type: string +Default: 3600 +_Description: Interval (in seconds) between directory scans: + BoxBackup regularly scans the selected directories, looking for modified + files. + . + Please choose the scan interval in seconds. + +Template: boxbackup-client/MinimumFileAge +Type: string +Default: 21600 +_Description: Minimum time to wait (in seconds) before uploading a file: + A file will be uploaded to the server only after a certain time after its + last modification. + . + Low interval values will trigger frequent uploads to the server and more + revisions being created with older revisions being removed earlier. + +Template: boxbackup-client/MaxUploadWait +Type: string +Default: 86400 +_Description: Maximum time to wait (in seconds) before uploading a file: + Frequently modified files are likely to never get uploaded if they + never reach the minimum wait time. + . + Please enter the maximum time to reach before the upload of a modified + file to the server is enforced. + +Template: boxbackup-client/IncorrectNumber +Type: error +_Description: Invalid time entered + Please enter an integer value greater null. + +Template: boxbackup-client/notifyMail +Type: string +Default: root +_Description: Recipient for alert notifications: + The BoxBackup client sends alert notifications when a problem occurs + during the backup. + . + Please enter either a local user name (for example 'root') or an email + address (for example 'admin@example.org'). + +Template: boxbackup-client/generateCertificate +Type: boolean +Default: true +_Description: Generate the client private key and X.509 certificate request? + The BoxBackup client needs an RSA private key and the corresponding X.509 + certificate to authenticate itself with the server. + . + Both can be generated automatically. You will need to send the + certificate request to the BoxBackup server administrator who will + sign it and send it back to you along with the server's Certification + Authority certificate. + . + These files should be copied into BoxBackup's configuration + directory. The file names to use are given in the + /etc/boxbackup/bbackupd.conf file. diff --git a/boxbackup-server.config b/boxbackup-server.config new file mode 100644 index 00000000..d722d7d7 --- /dev/null +++ b/boxbackup-server.config @@ -0,0 +1,111 @@ +#!/bin/bash -e + +# Source debconf library +. /usr/share/debconf/confmodule + +# This conf script is capable of backing up +#db_version 2.0 +#db_capb backup + +#db_metaget debconf/priority value +#CONFPRIO=$RET + +# Handle with debconf or not? +db_input medium boxbackup-server/debconf || true +db_go +db_get boxbackup-server/debconf +if [ "$RET" = "false" ]; then + exit 0 +fi + +# RAID directories +db_get boxbackup-server/raidDirectories +OLDRAIDDIR=$RET +RAIDOK=0 +while [ $RAIDOK = 0 ]; do + db_input critical boxbackup-server/raidDirectories || true + db_go + + db_get boxbackup-server/raidDirectories + + DIR1=`echo "$RET" | awk '{ print $1 }'` + DIR2=`echo "$RET" | awk '{ print $2 }'` + DIR3=`echo "$RET" | awk '{ print $3 }'` + + if [ -n $DIR1 ]; then + if [ -z "$DIR2" -o -z "$DIR3" ]; then + DIR2=$DIR1 + DIR3=$DIR1 + fi + + PATHOK=1 + for i in $DIR1 $DIR2 $DIR3; do + if [ `echo $i | awk '{ if (/^\/[A-Za-z0-9\.\-_]+\/?([A-Za-z0-9\.\-_]+\/?)*$/) { print 1 } else { print 0 } }'` = 0 ]; then + PATHOK=0 + fi + done + + if [ $PATHOK = 1 ]; then + RAIDOK=1; + fi + fi + + if [ $RAIDOK = 0 ]; then + db_input critical boxbackup-server/incorrectDirectories || true + db_go + fi +done + +# RAID block size +# Try to figure out the block size of the first partition given +db_get boxbackup-server/raidDirectories +if [ "$OLDRAIDDIR" != "$RET" ]; then # Directories have been changed so we can try to guess the block size + TMPDIR=`echo $DIR1 | sed 's/\/$//'` + + while [ "$TMPDIR" != "" ]; do + DEV=`df -P | grep "$TMPDIR$" | awk '{ print $1 }'` + + if [ -z "$DEV" ]; then + TMPDIR=`echo $TMPDIR | sed 's/\/[^\/]*$//'` + else + TMPDIR="" + fi + done + + if [ "$DEV" != "" ]; then + TUNE2FS="$(command -v tune2fs)" + if [ -x ${TUNE2FS} ]; then + BS=`${TUNE2FS} -l $DEV 2>/dev/null | grep 'Block size' | awk '{print $3 }'` + + if [ $? = 0 -a $BS != "" ]; then + db_set boxbackup-server/raidBlockSize "$BS" + fi + fi + fi +fi + +BSOK=0 +while [ $BSOK = 0 ]; do + db_input critical boxbackup-server/raidBlockSize || true + db_go + + db_get boxbackup-server/raidBlockSize + + if [ `echo $RET | awk '{ if (/^[0-9]+$/) { print 1 } else { print 0 } }'` = 1 ]; then + if [ `echo $RET | awk '{ bs=sqrt($1); if (bs ~ /^[0-9]+$/) { print 1 } else { print 0 } }'` = 1 ]; then + BSOK=1 + fi + fi + + if [ $BSOK = 0 ]; then + db_input critical boxbackup-server/incorrectBlocksize || true + db_go + fi +done + +# x509 and private key +db_input medium boxbackup-server/generateCertificate || true +db_go + +exit 0 + diff --git a/boxbackup-server.dirs b/boxbackup-server.dirs new file mode 100644 index 00000000..a6c52ea6 --- /dev/null +++ b/boxbackup-server.dirs @@ -0,0 +1,6 @@ +usr/sbin +etc/boxbackup +etc/boxbackup/bbstored +etc/logcheck/ignore.d.workstation +etc/logcheck/ignore.d.server + diff --git a/boxbackup-server.docs b/boxbackup-server.docs new file mode 100644 index 00000000..ccd1de71 --- /dev/null +++ b/boxbackup-server.docs @@ -0,0 +1,3 @@ +BUGS.txt +distribution/boxbackup/LINUX.txt +distribution/boxbackup/THANKS.txt diff --git a/boxbackup-server.init b/boxbackup-server.init new file mode 100644 index 00000000..027edc41 --- /dev/null +++ b/boxbackup-server.init @@ -0,0 +1,82 @@ +#! /bin/sh +# +### BEGIN INIT INFO +# Provides: boxbackup-server +# Required-Start: $syslog $remote_fs $network +# Required-Stop: $syslog $remote_fs $network +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: boxbackup server +# Description: Init script to start and stop the boxbackup server +### END INIT INFO + +. /lib/lsb/init-functions + +PATH=/sbin:/bin:/usr/sbin:/usr/bin +DAEMON=/usr/sbin/bbstored +NAME=bbstored +DESC=boxbackup-server +CONF=/etc/boxbackup/bbstored.conf + +test -f $DAEMON || exit 0 + +test -f $CONF || exit 0 + +PIDFILE=`grep 'PidFile' $CONF | sed 's/[[:space:]]*PidFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` +CERTFILE=`grep 'CertificateFile' $CONF | sed 's/[[:space:]]*CertificateFile[[:space:]]*=[[:space:]]*\(\/[A-Za-z0-9/]*\)/\1/'` + +[ -z $PIDFILE ] && PIDFILE="/var/run/bbstored.pid" + +# Don't start if certificate file is not present +[ ! -e $CERTFILE ] && exit 0 + +set -e + +case "$1" in + start) + echo -n "Starting $DESC: " + start-stop-daemon --start --quiet --pidfile $PIDFILE \ + --exec $DAEMON -- $CONF + echo "$NAME." + ;; + stop) + echo -n "Stopping $DESC: " + start-stop-daemon --oknodo --stop --quiet --pidfile $PIDFILE \ + --exec $DAEMON + echo "$NAME." + ;; + #reload) + # + # If the daemon can reload its config files on the fly + # for example by sending it SIGHUP, do it here. + # + # If the daemon responds to changes in its config file + # directly anyway, make this a do-nothing entry. + # + # echo "Reloading $DESC configuration files." + # start-stop-daemon --stop --signal 1 --quiet --pidfile \ + # /var/run/$NAME.pid --exec $DAEMON + #;; + restart|force-reload) + # + # If the "reload" option is implemented, move the "force-reload" + # option to the "reload" entry above. If not, "force-reload" is + # just the same as "restart". + # + echo -n "Restarting $DESC: " + start-stop-daemon --oknodo --stop --quiet --pidfile \ + $PIDFILE --exec $DAEMON + sleep 1 + start-stop-daemon --start --quiet --pidfile \ + $PIDFILE --exec $DAEMON -- $CONF + echo "$NAME." + ;; + *) + N=/etc/init.d/$NAME + # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $N {start|stop|restart|force-reload}" >&2 + exit 1 + ;; +esac + +exit 0 diff --git a/boxbackup-server.install b/boxbackup-server.install new file mode 100644 index 00000000..24809064 --- /dev/null +++ b/boxbackup-server.install @@ -0,0 +1,8 @@ + +debian/tmp/etc/logcheck/ignore.d.server/boxbackup-server /etc/logcheck/ignore.d.server +debian/tmp/etc/logcheck/ignore.d.workstation/boxbackup-server /etc/logcheck/ignore.d.workstation +parcels/boxbackup-*-backup-server-*-gnu*/bbstoreaccounts /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored-certs /usr/bin/ +parcels/boxbackup-*-backup-server-*-gnu*/bbstored-config /usr/sbin/ +parcels/boxbackup-*-backup-server-*-gnu*/raidfile-config /usr/sbin/ diff --git a/boxbackup-server.logcheck.ignore b/boxbackup-server.logcheck.ignore new file mode 100644 index 00000000..c19b43db --- /dev/null +++ b/boxbackup-server.logcheck.ignore @@ -0,0 +1,10 @@ +bbstored/hk\[.*\]: Starting housekeeping +bbstored/hk\[.*\]: Finished housekeeping +bbstored/hk\[.*\]: Housekeeping process started +bbstored\[.*\]: Starting daemon +bbstored\[.*\]: Terminating daemon +bbstored\[.*\]: Incoming connection from.*port.*\(handling in child.*\) +bbstored\[.*\]: Certificate CN: +bbstored\[.*\]: Login: Client ID +bbstored\[.*\]: Session finished + diff --git a/boxbackup-server.manpages b/boxbackup-server.manpages new file mode 100644 index 00000000..09a62b31 --- /dev/null +++ b/boxbackup-server.manpages @@ -0,0 +1,7 @@ +docs/man/bbstoreaccounts.8.gz +docs/man/bbstored-certs.8.gz +docs/man/bbstored.conf.5.gz +docs/man/bbstored-config.8.gz +docs/man/bbstored.8.gz +docs/man/raidfile-config.8.gz +docs/man/raidfile.conf.5.gz diff --git a/boxbackup-server.postinst b/boxbackup-server.postinst new file mode 100644 index 00000000..1b19de97 --- /dev/null +++ b/boxbackup-server.postinst @@ -0,0 +1,212 @@ +#! /bin/sh +# postinst script for boxbackup-server +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package +# +# quoting from the policy: +# Any necessary prompting should almost always be confined to the +# post-installation script, and should be protected with a conditional +# so that unnecessary prompting doesn't happen if a package's +# installation fails and the `postinst' is called with `abort-upgrade', +# `abort-remove' or `abort-deconfigure'. + +#loading debconf module +. /usr/share/debconf/confmodule + +CONFDIR=/etc/boxbackup +DEBCONFRAID=$CONFDIR/raidfile.debconf +DEBCONFBB=$CONFDIR/bbstored.debconf +RAIDCONF=$CONFDIR/raidfile.conf +BBCONF=$CONFDIR/bbstored.conf +BBACCOUNTS=$CONFDIR/bbstored/boxbackup-server-accounts.txt +BBUSER=bbstored +BBPRIVKEY=$CONFDIR/bbstored/boxbackup-server-key.pem +BBCERTREQ=$CONFDIR/bbstored/boxbackup-server-cert-req.pem +BBCERT=$CONFDIR/bbstored/boxbackup-server-cert.pem +BBCACERT=$CONFDIR/bbstored/boxbackup-client-ca-cert.pem + +case "$1" in + configure) + + # Set up the bbstored user + if [ -z "`getent passwd $BBUSER`" ]; then + echo "Creating $BBUSER user." >&2 + adduser --system --no-create-home \ + --disabled-password --disabled-login \ + --shell /bin/false --group --home /var $BBUSER + else + echo "User $BBUSER already exists." >&2 + fi + + db_get boxbackup-server/debconf + if [ "$RET" = "true" ]; then + # Generate configuration files + # raidfile.conf + echo "#To reconfigure boxbackup-server run #dpkg-reconfigure boxbackup-server" >> $DEBCONFRAID + + echo "disc0" >> $DEBCONFRAID + echo "{" >> $DEBCONFRAID + echo " SetNumber = 0" >> $DEBCONFRAID + + db_get boxbackup-server/raidBlockSize + echo " BlockSize = $RET" >> $DEBCONFRAID + + db_get boxbackup-server/raidDirectories + + DIR1=`echo "$RET" | awk '{ print $1 }'` + DIR2=`echo "$RET" | awk '{ print $2 }'` + DIR3=`echo "$RET" | awk '{ print $3 }'` + + if [ -n $DIR1 ]; then + if [ -z "$DIR2" -o -z "$DIR3" ]; then + DIR2=$DIR1 + DIR3=$DIR1 + fi + fi + + echo " Dir0 = $DIR1" >> $DEBCONFRAID + echo " Dir1 = $DIR2" >> $DEBCONFRAID + echo " Dir2 = $DIR3" >> $DEBCONFRAID + + echo "}" >> $DEBCONFRAID + + # Handle backup directories creation/permissions + for dir in "$DIR1" "$DIR2" "$DIR3"; do + if [ -d "$dir/backup" ]; then + # need stat package on Woody + #if (`stat -c %U $dir/backup` != $BBUSER); then + if [ `ls -ld $dir/backup | awk '{ print $3 }'` != "$BBUSER" ]; then + echo "Incorrect owner of backup directory. Changing it to $BBUSER..." >&2 + chown $BBUSER:$BBUSER $dir/backup + fi + + #if [ `stat -c %a $dir/backup` != "700" ]; then + if [ `ls -ld $dir/backup | awk '{ print $1 }'` != "drwx------" ]; then + chmod 700 $dir/backup + fi + else + echo "Creating $dir/backup directory..." >&2 + mkdir -p $dir/backup + chown $BBUSER:$BBUSER $dir/backup + chmod 700 $dir/backup + fi + done + + if ! dpkg-statoverride --list $CONFDIR/bbstored > /dev/null; then + dpkg-statoverride --update --add $BBUSER $BBUSER 700 $CONFDIR/bbstored + fi + + # Accounts file + if [ ! -e $BBACCOUNTS ]; then + touch $BBACCOUNTS + fi + + #if [ `stat -c %U $BBACCOUNTS` != $BBUSER ]; then + if [ `ls -ld $BBACCOUNTS | awk '{ print $3 }'` != "$BBUSER" ]; then + chown $BBUSER:$BBUSER $BBACCOUNTS + fi + + #if [ `stat -c %a $BBACCOUNTS` != "600" ]; then + if [ `ls -ld $BBACCOUNTS | awk '{ print $1 }'` != "drw-------" ]; then + chmod 600 $BBACCOUNTS + fi + + SERVNAME=`hostname --fqdn` + + # SSL stuff + if [ ! -e $BBPRIVKEY -a ! -e $BBCERT ]; then + db_get boxbackup-server/generateCertificate + + if [ "$RET" = "true" ]; then + if ! openssl genrsa -out $BBPRIVKEY 2048 >&2; then + echo "Private key generation failed! Check why." >&2 + else + chown $BBUSER: $BBPRIVKEY + chmod 600 $BBPRIVKEY || true + fi + + if ! openssl req -new -key $BBPRIVKEY -sha1 -out $BBCERTREQ >&2 <&2 + fi + fi + fi + + # Generate bbstored.conf + echo "#To reconfigure boxbackup-server run #dpkg-reconfigure boxbackup-server" >> $DEBCONFBB + echo "RaidFileConf = $RAIDCONF" >> $DEBCONFBB + echo "AccountDatabase = $BBACCOUNTS" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "# Uncomment this line to see exactly what commands are being received from clients." >> $DEBCONFBB + echo "# ExtendedLogging = yes" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "# scan all accounts for files which need deleting every 15 minutes." >> $DEBCONFBB + echo "TimeBetweenHousekeeping = 900" >> $DEBCONFBB + echo >> $DEBCONFBB + echo "Server" >> $DEBCONFBB + echo "{" >> $DEBCONFBB + echo " PidFile = /var/run/bbstored.pid" >> $DEBCONFBB + echo " User = bbstored" >> $DEBCONFBB + echo " ListenAddresses = inet:$SERVNAME" >> $DEBCONFBB + echo " CertificateFile = $BBCERT" >> $DEBCONFBB + echo " PrivateKeyFile = $BBPRIVKEY" >> $DEBCONFBB + echo " TrustedCAsFile = $BBCACERT" >> $DEBCONFBB + echo "}" >> $DEBCONFBB + + if [ -x "`which ucf`" ]; then + ucf --three-way --debconf-ok $DEBCONFRAID $RAIDCONF + fi + rm -f $DEBCONFRAID + chmod 644 $RAIDCONF || true + chown root:root $RAIDCONF || true + + if [ -x "`which ucf`" ]; then + ucf --three-way --debconf-ok $DEBCONFBB $BBCONF + fi + rm -f $DEBCONFBB + chmod 644 $BBCONF || true + chown root:root $BBCONF || true + fi + db_stop + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + db_stop + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + db_stop + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/boxbackup-server.postrm b/boxbackup-server.postrm new file mode 100644 index 00000000..7e6ac797 --- /dev/null +++ b/boxbackup-server.postrm @@ -0,0 +1,22 @@ +#! /bin/sh +set -e + +if [ "$1" = "purge" ]; then + for i in /etc/boxbackup/raidfile.conf /etc/boxbackup/bbstored.conf; do + if [ -e $i ]; then + rm -f $i + fi + if which ucf >/dev/null; then + ucf -p $i + fi + done + + dpkg-statoverride --remove /etc/boxbackup/bbstored || true + + if which deluser >/dev/null; then + getent passwd bbstored >/dev/null && deluser bbstored + getent group bbstored >/dev/null && delgroup bbstored + fi +fi + +#DEBHELPER# diff --git a/boxbackup-server.service b/boxbackup-server.service new file mode 100644 index 00000000..a491ddaa --- /dev/null +++ b/boxbackup-server.service @@ -0,0 +1,13 @@ +[Unit] +Description=Box Backup Server +ConditionPathExists=/etc/boxbackup/bbstored.conf +Wants=network.target +After=network.target + +[Service] +ExecStart=/usr/sbin/bbstored -F -c /etc/boxbackup/bbstored.conf +KillMode=process +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/boxbackup-server.templates b/boxbackup-server.templates new file mode 100644 index 00000000..3c6016e2 --- /dev/null +++ b/boxbackup-server.templates @@ -0,0 +1,77 @@ +# These templates have been reviewed by the debian-l10n-english +# team +# +# If modifications/additions/rewording are needed, please ask +# for an advice to debian-l10n-english@lists.debian.org +# +# Even minor modifications require translation updates and such +# changes should be coordinated with translators and reviewers. + +Template: boxbackup-server/debconf +Type: boolean +Default: false +_Description: Should BoxBackup be configured automatically? + The package configuration scripts can create the configuration files + for the BoxBackup server. + . + You should choose this option if you are not familiar with BoxBackup's + configuration options. The configuration can be done manually with the + 'raidfile-config' and 'bbstored-config' scripts. + . + The server will not start if it is not configured. In all cases, + reading the /usr/share/doc/boxbackup-server/README.Debian is + recommended. + +Template: boxbackup-server/raidDirectories +Type: string +_Description: Location of the RAID directories: + Please choose the location for the three RAID file directories. + . + To enable RAID, the directory names should be a space-separated list of + three partitions, each on different physical hard drives (for example: + '/raid/0.0 /raid/0.1 /raid/0.2'). + . + If you don't want to enable RAID, just specify the path to one directory + where the backups will be stored (for example, /usr/local/lib/boxbackup). + . + These directories will be created if they do not exist. + +Template: boxbackup-server/incorrectDirectories +Type: error +_Description: Invalid path names + The path names to the directories must be absolute path names, + separated by spaces. + . + For example: /raid/0.0 /raid/0.1 /raid/0.2 + +Template: boxbackup-server/raidBlockSize +Type: string +Default: 4096 +_Description: Block size for the userland RAID system: + BoxBackup uses userland RAID techniques. + . + Please choose the block size to use for the storage. + For maximum efficiency, you should choose the block size of the underlying + file system (which can be displayed for ext2 filesystems with the 'tune2fs -l' + command). + . + This value should be set even if you don't plan to use RAID. + +Template: boxbackup-server/generateCertificate +Type: boolean +Default: true +_Description: Generate a server private key and X.509 certificate request? + The BoxBackup server needs an RSA private key and the corresponding X.509 + certificate to perform client-server authentication and communication + encryption. + . + Both can be generated automatically. You will need to sign the + certificate with your root CA (see the boxbackup-server package) and + put this signed certificate and the root CA certificate in the + configuration folder. + +Template: boxbackup-server/incorrectBlocksize +Type: error +_Description: Invalid block size + The block size must be a power of two (e.g. 1024 or 4096). + diff --git a/changelog b/changelog new file mode 100644 index 00000000..d6087111 --- /dev/null +++ b/changelog @@ -0,0 +1,356 @@ +boxbackup (0.12~gitcf52058f-3) unstable; urgency=medium + + * QA upload. + * Run the testsuite on amd64 and i386 only (Closes: #866372) + + -- Reinhard Tartler Fri, 30 Jun 2017 15:01:19 -0400 + +boxbackup (0.12~gitcf52058f-2) unstable; urgency=medium + + * Fix FTBFS on signed arch architectures (Closes: #865092) + + -- Reinhard Tartler Tue, 20 Jun 2017 22:23:07 -0400 + +boxbackup (0.12~gitcf52058f-1) unstable; urgency=medium + + * QA upload. + * Honor test failures during build (Closes: #744266) + * New upstream release: 0.12 (Closes: #744911) + - has openssl 1.1 support (Closes: #851092) + * Update the notifyadmin.sh script (Closes: #483928) + * Add systemd unit files. + + -- Reinhard Tartler Sat, 17 Jun 2017 17:54:09 -0400 + +boxbackup (0.11.1~r2837-4) unstable; urgency=medium + + * QA upload. + * Check for ucf and deluser availability before calling them during postrm + purge. (Closes: #688373, #667535) + * Add Italian (it) debconf translations by Beatrice Torracca. + (Closes: #658724) + * Add Dutch (nl) debconf translations by Frans Spiesschaert. + (Closes: #766532) + * Build with dh_autotools-dev_*. + * Switch to source format 3.0 (quilt). + + -- Andreas Beckmann Sun, 22 Jan 2017 03:29:52 +0100 + +boxbackup (0.11.1~r2837-3) unstable; urgency=medium + + * QA upload. + * Build with openssl 1.0. Thanks to Adrian Bunk. (Closes: #828253) + * Bump Standards-Version to 3.9.8. + + -- Chris Lamb Sat, 12 Nov 2016 14:15:06 +0000 + +boxbackup (0.11.1~r2837-2) unstable; urgency=medium + + * Ensure that LWP/UserAgent.pm is available (Closes: #816517) + * Orphaning package, cf. #817078 + + -- Reinhard Tartler Mon, 07 Mar 2016 20:19:45 -0500 + +boxbackup (0.11.1~r2837-1) unstable; urgency=low + + * New upstream release. + - Bug fix: "Handling of "get" command broken due to bug in + temp file name creation.", thanks to Matto Marjanovic + (Closes: #606559). + * Bug fix: "[INTL:pt_BR] Brazilian Portuguese debconf templates + translation", thanks to Adriano Rafael Gomes (Closes: #610416). + * Bug fix: "[INTL:da] Fix Danish translation of the debconf templates + BoxBackup", thanks to Joe Dalton (Closes: #619303). + * Bug fix: "Documentation errors regarding bbstored-certs", thanks to + Clint Adams (Closes: #601882). + * Build in verbose mode. + * Bump standards version to 3.9.2, no changes needed. + * Normalize fields in debian/control with wrap-and-sort(1). + * implement build-arch and build-indep targets + + -- Reinhard Tartler Fri, 28 Oct 2011 01:53:48 +0200 + +boxbackup (0.11~rc8~r2714-1) unstable; urgency=low + + * New upstream release. + * New Upstream fixes Bug: "boxbackup-client complains about + /home/*/.gvfs", thanks for reporting to Sune Mølgaard. + Closes: #593401, LP: #496334 + * Enhancement: "Please use newer bdb", thanks to Clint Adams + Closes: #548475 + * bin/bbstored/bbstored-certs: reduce root CA expiration date to avoid + Y2k38 overflow. Thanks to Clint Adams for + reporting it. Closes: #601506, LP: #641112 + * By default, do not manage boxbackup-client configuration via debconf + * Allow dpkg-reconfigure to work even when the debconf level is set to + 'high'. + * Various debconf severity fixes in boxbackup-*.conf + + -- Reinhard Tartler Tue, 09 Nov 2010 18:14:58 +0100 + +boxbackup (0.11~rc3~r2502-4) unstable; urgency=low + + * use more globs in install paths to avoid failures on kFreeBSD + * Bump standards version, no changes needed + + -- Reinhard Tartler Wed, 13 Oct 2010 20:51:39 +0200 + +boxbackup (0.11~rc3~r2502-3) unstable; urgency=low + + * Update Vitnamese debconf translations. Closes: #598581 + + -- Reinhard Tartler Tue, 12 Oct 2010 10:50:37 +0200 + +boxbackup (0.11~rc3~r2502-2.1ubuntu1) maverick; urgency=low + + * fix install paths for armel (LP: #616461) + + -- David Sugar Mon, 16 Aug 2010 23:49:22 +0200 + +boxbackup (0.11~rc3~r2502-2.1) unstable; urgency=low + + * Non-maintainer upload. + * No longer hardcode path to tune2fs in config script + * Fix pending l10n issues. Debconf translations: + - Spanish (Omar Campagne). Closes: #537589 + - Japanese (Hideki Yamane (Debian-JP)). Closes: #558074 + - Danish (Joe Hansen). Closes: #582174 + + -- Christian Perrier Mon, 24 May 2010 09:33:23 +0200 + +boxbackup (0.11~rc3~r2502-2) unstable; urgency=low + + * Bug fix: "tries to contact a server build-time", thanks to + Riku Voipio for reporting. Fix based on a patch contributed + by peter green, thanks as well! (Closes: #525277). + * Bug fix: "missing #include", thanks to Martin Michlmayr + (Closes: #526152, LP: #371809) + + -- Reinhard Tartler Tue, 05 May 2009 07:42:40 +0200 + +boxbackup (0.11~rc3~r2502-1) unstable; urgency=low + + * New upstream release, built from svn revision 2502. + - silently ignores sockets and named pipes (Closes: #479145) + - syslog facility is now configurable (Closes: #521283) + + -- Reinhard Tartler Thu, 02 Apr 2009 13:58:17 +0200 + +boxbackup (0.11~rc2+r2072-1) unstable; urgency=low + + * New upstream release. (Closes: #503942) + * no longer use the upstream tarballs, but create them with the script + debian/get-orig-source.sh to create the orig.tar.gz. See README.Debian + for a rationale of this step. + * Therefore, run autoconf on the buildds. + * always use config.sub/config.guess from the autotools-dev package + * Fake a "VERSION.txt" instead of letting the build system try to figure + out the svn version by itself (which would fail) + * no longer include patches to the upstream source inline. This doesn't + work out too well with bzr. Port all patches to quilt instead. + * factor out distribution independent cleaning rules to a new shell script + called debian/clean.sh. + * make debian/NEWS actually parsable by dpkg-parsechangelog. This should + make apt-parsechangelog work. + * Update debian specific documentation to new URL: http://boxbackup.org + * Don't install the files DOCUMENTATION.txt and CONTACT.txt. Both contain + just (outdated) contact addresses and URLs. (Closes: #503659) + * add translation for swedish debconf messages. Translation provided by + Martin Bagge . (Closes: #513776) + * adjust configure.ac to only AC_SUBST single variables instead of 3 in + a row. Fixes FTBFS on sid, found at http://bugs.gentoo.org/205558 + + -- Reinhard Tartler Tue, 31 Mar 2009 15:58:23 +0200 + +boxbackup (0.11~rc2-6) unstable; urgency=low + + * Fix shell scripting in the debconf interaction code of the package's + postinst script. This should prevent problems like LP: #222999 + + -- Reinhard Tartler Wed, 11 Feb 2009 08:55:05 +0100 + +boxbackup (0.11~rc2-5) unstable; urgency=low + + * Bugfix: "Please build-depend on docbook-xml". Thanks to Luca Falavigna + for reporting. Closes: #507973 + * Add missing #includes in lib/common/Logging.cpp and + lib/common/DebugMemLeakFinder.cpp. Also use malloc, free, etc from the + namespace std:: instead of the global namespace in several other files + Closes: #505696, #512510 + + -- Reinhard Tartler Thu, 22 Jan 2009 08:26:24 +0100 + +boxbackup (0.11~rc2-4) unstable; urgency=low + + * add a note to documentation about adjusted syslog facility. + * build and install the administrator guide and the installation guide. + * By default do not configure boxbackup. Most users of this package need + to know what the postinst/configure scripts are doing anyway, and this + avoids piuparts to fail here. Closes: #502742, #502461 + + -- Reinhard Tartler Thu, 23 Oct 2008 20:25:10 +0200 + +boxbackup (0.11~rc2-3.1) unstable; urgency=low + + * Non-maintainer upload to fix pending l10n issues. + * Debconf translations: + - German. Closes: #477719 + - French. Closes: #478557 + - Portuguese. Closes: #483869 + - Czech. Closes: #483981 + - Galician. Closes: #483857 + - Finnish. Closes: #484353 + - Basque. Closes: #484563 + - Russian. Closes: #484829 + + -- Christian Perrier Tue, 20 May 2008 07:54:18 +0200 + +boxbackup (0.11~rc2-3) unstable; urgency=low + + * fix another miss '#include ' to fix compilation with g++-4.3. + + -- Reinhard Tartler Wed, 23 Apr 2008 13:10:17 +0200 + +boxbackup (0.11~rc2-2) unstable; urgency=low + + * merge forgotten changes from the unstable Branch. (sorry to the + translators for the confusion). + * The previous upload forgot to change some references to the default + configuration directory from /etc/box -> /etc/boxbackup. Now rectified. + * add an '#include ' in ./lib/common/Logging.cpp. Should fix + build failiures on mips, powerpc and sparc. + + -- Reinhard Tartler Mon, 21 Apr 2008 13:41:18 +0200 + +boxbackup (0.11~rc2-1) unstable; urgency=low + + * new upstream release. + * update watch file. + * bump standards version to 3.7.3 (No changes needed) + * update frensh debconf template translations. Thanks to + Christian Perrier (Closes: #476090) + + -- Reinhard Tartler Sun, 20 Apr 2008 14:01:27 +0200 + +boxbackup (0.11~rc1-1) experimental; urgency=low + + * New upstream release. + - should fix builds on kFreeBSD architectures: (Closes: #440156). + - build against libbd4.6 instead of libdb4.3. (Closes: #442640). + - the config file template has been updated to be more specific for + the AlwaysIncludeFile Option (Closes: #435860). + * remove all generated files in clean target of debian/rules. Allows + package to build twice in a row (Closes: #442515). + * Install file ExceptionCodes.txt to documentation directories. The file + contains a list of Error codes found in logfiles. + * Simplify debian/rules by removing code to build arch-independent + packages. There are none. + + -- Reinhard Tartler Sun, 20 Jan 2008 19:09:59 +0100 + +boxbackup (0.10+really0.10-2) unstable; urgency=medium + + * raise urgency because of RC bug! + * use rather 'nocheck' instead of 'notest'. Thanks to Michael Banck + for notifying me about this. + * Ack NMUs. Thanks Amaya for handling these! (Closes: #470060, #467628). + * Merge Ubuntu changes, fixing installations without tty. (Closes: #474587). + * build against libdb4.6 (Closes: #442640) + * don't ignores errors on clean. Thanks lintian. + * update Standards Version to 3.7.3, no changes needed. + * clarify debconf question "boxbackup-client/IncorrectNumber" (Closes: #467635). + * clarify debconf question "boxbackup-server/raidBlockSize" (Closes: #467636). + + -- Reinhard Tartler Sun, 13 Apr 2008 11:38:01 +0200 + +boxbackup (0.10+really0.10-1.2) unstable; urgency=low + + * Non-maintainer upload. + * Fix FTBFS introduced by my previous NMU. Apply patch from Cyril Brulebois + (Closes: #454862). + * Added debian debconf translation by Kai Wasserbäch (Closes: #467628). + + -- Amaya Rodrigo Sastre Sat, 05 Apr 2008 12:37:16 +0200 + +boxbackup (0.10+really0.10-1.1) unstable; urgency=low + + * Non-maintainer upload. + * iFix LSB header in init.d script (Closes: #470060). + + -- Amaya Rodrigo Sastre Mon, 31 Mar 2008 18:43:40 +0200 + +boxbackup (0.10+really0.10-1ubuntu3) hardy; urgency=low + + * Don't redirect ucf calls. This was forgotten in the previous upload, + but should have been there. + + -- Tollef Fog Heen Thu, 06 Mar 2008 07:17:35 +0100 + +boxbackup (0.10+really0.10-1ubuntu2) hardy; urgency=low + + * Fix up postinst so we don't call db_stop too early, and use + --debconf-ok to ucf. (No versioned dependency since even oldstable + has a new enough ucf.) Use db_stop near the end to make sure we don't + hang after starting the daemon though. + + -- Tollef Fog Heen Wed, 05 Mar 2008 22:33:57 +0100 + +boxbackup (0.10+really0.10-1ubuntu1) hardy; urgency=low + + * Rebuild for libdb4.3 -> libdb4.6 migration. + * Set MOTU to maintainer. + + -- Chuck Short Mon, 03 Mar 2008 12:37:35 -0500 + +boxbackup (0.10+really0.10-1) unstable; urgency=low + + * revert new upstream accidentally slipped into unstable. + * big apologies that I now need to upload this package earlier than + promised to the boxbackup translators :( + * add watchfile + * Bug fix: "boxbackup-server recommends boxbackup-utils (unavailable)", + thanks to Pelayo Gonzalez (Closes: #424992). + * Apply new debconf templates and debian/control review. Thanks to + Christan Perrier! (Closes: #429396) + * Bug fix: "boxbackup: French debconf templates translation", thanks to + Vincent Bernat (Closes: #430856). + * Bug fix: "[l10n] Czech translation of boxbackup debconf messages", + thanks to Miroslav Kure (Closes: #431463). + * Bug fix: "boxbackup: [INTL:vi] Vietnamese debconf templates + translation", thanks to Clytie Siddall (Closes: #430535). + * Bug fix: "depends on non-essential package ucf in postrm", thanks to + Michael Ablassmeier (Closes: #431518). + * Bug fix: "depends on non-essential package ucf in postrm", thanks to + Michael Ablassmeier (Closes: #431519). + * run testsuite on build, use 'notest' in $DEB_BUILD_OPTIONS to disable + + -- Reinhard Tartler Tue, 03 Jul 2007 12:30:49 +0200 + +boxbackup (0.10-1) unstable; urgency=low + + * upload to debian (Closes: #416605) + * Cleanups in debian/rules + * Apply patch from svn, commit #626, in order to fix FTBFS + see http://bbdev.fluffy.co.uk/trac/changeset/626 + * Drop the boxbackup-utils package, since it only shipped one single + script, which is generally used for CA maintenance, so move it to the + to the boxbackup-server package + * Bump standards version to 3.7.2 + * add missing manpages + * add README.Debian + * use debhelper 5 + * cleanup the versioned dependencies in debian/control + + [ Jérôme Schell ] + + * New upstream version + * Add LSB headers in init script + + -- Reinhard Tartler Mon, 18 Jun 2007 15:35:55 +0100 + +boxbackup (0.09-3) unstable; urgency=low + + * Added man pages for bbackupd, bbackupd-config, bbackupctl, bbackupquery + * Improve lintian compatibility of the packages + + -- Jérôme Schell Mon, 10 Oct 2005 14:16:20 +0200 diff --git a/clean b/clean new file mode 100644 index 00000000..0f10520d --- /dev/null +++ b/clean @@ -0,0 +1,146 @@ +# generated using bzr status +ExceptionCodes.txt +Makefile +aclocal.m4 +configure +runtest.pl +test-backupdiff.log +test-backupstore.log +test-backupstorefix.log +test-backupstorepatch.log +test-basicserver.log +test-bbackupd.log +test-common.log +test-compress.log +test-crypto.log +test-httpserver.log +test-raidfile.log +bin/bbackupctl/Makefile +bin/bbackupd/Makefile +bin/bbackupd/autogen_ClientException.cpp +bin/bbackupd/autogen_ClientException.h +bin/bbackupd/bbackupd-config +bin/bbackupobjdump/Makefile +bin/bbackupquery/Makefile +bin/bbackupquery/autogen_Documentation.cpp +bin/bbackupquery/makedocumentation.pl +bin/bbstoreaccounts/Makefile +bin/bbstored/Makefile +bin/bbstored/autogen_BackupProtocolServer.cpp +bin/bbstored/autogen_BackupProtocolServer.h +bin/bbstored/bbstored-certs +bin/bbstored/bbstored-config +bin/s3simulator/Makefile +contrib/debian/bbackupd +contrib/debian/bbstored +contrib/mac_osx/org.boxbackup.bbackupd.plist +contrib/mac_osx/org.boxbackup.bbstored.plist +contrib/redhat/bbackupd +contrib/redhat/bbstored +contrib/solaris/bbackupd-manifest.xml +contrib/solaris/bbackupd-smf-method +contrib/solaris/bbstored-manifest.xml +contrib/solaris/bbstored-smf-method +contrib/suse/bbackupd +contrib/suse/bbstored +contrib/windows/installer/boxbackup.mpi +docs/docbook/ExceptionCodes.xml +docs/docbook/adminguide.pdf +docs/docbook/instguide.pdf +infrastructure/BoxPlatform.pm +infrastructure/makebuildenv.pl +infrastructure/makedistribution.pl +infrastructure/makeparcels.pl +lib/backupclient/Makefile +lib/backupclient/autogen_BackupProtocolClient.cpp +lib/backupclient/autogen_BackupProtocolClient.h +lib/backupclient/autogen_BackupStoreException.cpp +lib/backupclient/autogen_BackupStoreException.h +lib/backupstore/Makefile +lib/common/BoxConfig.h +lib/common/BoxConfig.h.in +lib/common/BoxPortsAndFiles.h +lib/common/Makefile +lib/common/autogen_CommonException.cpp +lib/common/autogen_CommonException.h +lib/common/autogen_ConversionException.cpp +lib/common/autogen_ConversionException.h +lib/common/makeexception.pl +lib/compress/Makefile +lib/compress/autogen_CompressException.cpp +lib/compress/autogen_CompressException.h +lib/crypto/Makefile +lib/crypto/autogen_CipherException.cpp +lib/crypto/autogen_CipherException.h +lib/httpserver/Makefile +lib/httpserver/autogen_HTTPException.cpp +lib/httpserver/autogen_HTTPException.h +lib/intercept/Makefile +lib/raidfile/Makefile +lib/raidfile/autogen_RaidFileException.cpp +lib/raidfile/autogen_RaidFileException.h +lib/raidfile/raidfile-config +lib/server/Makefile +lib/server/autogen_ConnectionException.cpp +lib/server/autogen_ConnectionException.h +lib/server/autogen_ServerException.cpp +lib/server/autogen_ServerException.h +lib/server/makeprotocol.pl +lib/win32/Makefile +test/backupdiff/Makefile +test/backupdiff/_main.cpp +test/backupdiff/_t +test/backupdiff/_t-gdb +test/backupstore/Makefile +test/backupstore/_main.cpp +test/backupstore/_t +test/backupstore/_t-gdb +test/backupstorefix/Makefile +test/backupstorefix/_main.cpp +test/backupstorefix/_t +test/backupstorefix/_t-gdb +test/backupstorefix/testfiles/testbackupstorefix.pl +test/backupstorepatch/Makefile +test/backupstorepatch/_main.cpp +test/backupstorepatch/_t +test/backupstorepatch/_t-gdb +test/basicserver/Makefile +test/basicserver/_main.cpp +test/basicserver/_t +test/basicserver/_t-gdb +test/basicserver/autogen_TestProtocolClient.cpp +test/basicserver/autogen_TestProtocolClient.h +test/basicserver/autogen_TestProtocolServer.cpp +test/basicserver/autogen_TestProtocolServer.h +test/bbackupd/Makefile +test/bbackupd/_main.cpp +test/bbackupd/_t +test/bbackupd/_t-gdb +test/bbackupd/testfiles/bbackupd-exclude.conf +test/bbackupd/testfiles/bbackupd-snapshot.conf +test/bbackupd/testfiles/bbackupd-symlink.conf +test/bbackupd/testfiles/bbackupd.conf +test/bbackupd/testfiles/extcheck1.pl +test/bbackupd/testfiles/extcheck2.pl +test/bbackupd/testfiles/notifyscript.pl +test/bbackupd/testfiles/syncallowscript.pl +test/common/Makefile +test/common/_main.cpp +test/common/_t +test/common/_t-gdb +test/compress/Makefile +test/compress/_main.cpp +test/compress/_t +test/compress/_t-gdb +test/crypto/Makefile +test/crypto/_main.cpp +test/crypto/_t +test/crypto/_t-gdb +test/httpserver/Makefile +test/httpserver/_main.cpp +test/httpserver/_t +test/httpserver/_t-gdb +test/raidfile/Makefile +test/raidfile/_main.cpp +test/raidfile/_t +test/raidfile/_t-gdb diff --git a/clean.sh b/clean.sh new file mode 100644 index 00000000..90a30513 --- /dev/null +++ b/clean.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +rm -rf debug/ +rm -rf local/ +rm -rf parcels/ +rm -rf release/ +rm -rf docs/htmlguide/ +rm -rf docs/man/ diff --git a/compat b/compat new file mode 100644 index 00000000..f599e28b --- /dev/null +++ b/compat @@ -0,0 +1 @@ +10 diff --git a/control b/control new file mode 100644 index 00000000..a83e43ac --- /dev/null +++ b/control @@ -0,0 +1,59 @@ +Source: boxbackup +Section: utils +Priority: optional +Maintainer: Debian QA Group +Build-Depends: + autoconf, + automake, + autotools-dev, + debhelper (>> 10), + docbook-utils, + docbook-xml, + docbook-xsl, + libdb-dev (>= 4.7), + libedit-dev, + libssl1.0-dev, + libtest-lwp-useragent-perl, + xsltproc, + zlib1g-dev +Standards-Version: 3.9.8 +Homepage: http://boxbackup.org +Vcs-Git: git://anonscm.debian.org/collab-maint/boxbackup.git +Vcs-Browser: https://anonscm.debian.org/git/collab-maint/boxbackup.git + +Package: boxbackup-server +Architecture: any +Depends: + adduser, + debconf | debconf-2.0, + gawk, + lsb-base (>= 3.0-6), + openssl, + perl, + ucf, + ${misc:Depends}, + ${shlibs:Depends} +Description: server for the BoxBackup remote backup system + BoxBackup is an automatic on-line backup system. + The server waits for connections from remote clients, + authenticates them via X.509 certificates and stores the + encrypted data on hard drives with optional RAID techniques. + It also supports versions historization and per-user quotas. + +Package: boxbackup-client +Architecture: any +Depends: + debconf | debconf-2.0, + lsb-base (>= 3.0-6), + openssl, + perl, + ucf, + ${misc:Depends}, + ${shlibs:Depends} +Description: client for the BoxBackup remote backup system + BoxBackup is an automatic on-line backup system. + The client watches for changes on the local file system, + connects to a BoxBackup server and sends the changes via a + secure channel. All data is encrypted before being sent to + the server. A command-line tool is provided for restoration + of backups including deleted files and old versions. diff --git a/copyright b/copyright new file mode 100644 index 00000000..ffb3eead --- /dev/null +++ b/copyright @@ -0,0 +1,48 @@ +This package was debianized by Jérôme Schell on +Tue, 1 Jun 2004 07:51:24 +0000. + +It was downloaded from http://boxbackup.org + +Upstream Author: Ben Summers + +Copyright: + +Copyright (c) 2003, 2004 + Ben Summers. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. All use of this software and associated advertising materials must + display the following acknowledgement: + This product includes software developed by Ben Summers. +4. The names of the Authors may not be used to endorse or promote + products derived from this software without specific prior written + permission. + +[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. + +Commentary + +This license is based on a standard BSD license. Some minor changes in wording have been made to fit in with English law. + +© Ben Summers, 2003, 2004 diff --git a/patches/03-adjust-syslog-facility.diff b/patches/03-adjust-syslog-facility.diff new file mode 100644 index 00000000..4856026d --- /dev/null +++ b/patches/03-adjust-syslog-facility.diff @@ -0,0 +1,50 @@ +change default syslog facility from LOG_LOCAL6 to LOG_DAEMON + +--- a/lib/common/Logging.cpp ++++ b/lib/common/Logging.cpp +@@ -411,7 +411,7 @@ bool Syslog::Log(Log::Level level, const + return true; + } + +-Syslog::Syslog() : mFacility(LOG_LOCAL6) ++Syslog::Syslog() : mFacility(LOG_DAEMON) + { + ::openlog("Box Backup", LOG_PID, mFacility); + } +@@ -454,8 +454,8 @@ int Syslog::GetNamedFacility(const std:: + #undef CASE_RETURN + + BOX_ERROR("Unknown log facility '" << rFacility << "', " +- "using default LOCAL6"); +- return LOG_LOCAL6; ++ "using default DAEMON"); ++ return LOG_DAEMON; + } + + bool FileLogger::Log(Log::Level Level, const std::string& file, int line, +--- a/docs/docbook/adminguide.xml ++++ b/docs/docbook/adminguide.xml +@@ -286,6 +286,12 @@ local5.info /var + Note: Separators must be tabs, + otherwise these entries will be ignored. + ++ Note2: The packaged ++ debian and ubuntu versions of boxbackup do not log to local6, ++ but to the more standard 'daemon' facility. This means you ++ should not have anything to do to your syslog configuration, ++ since it is configured to be logged by default. ++ + touch /var/log/box + touch /var/log/raidfile + +--- a/lib/bbstored/BackupStoreDaemon.cpp ++++ b/lib/bbstored/BackupStoreDaemon.cpp +@@ -203,7 +203,7 @@ void BackupStoreDaemon::Run() + SetProcessTitle("housekeeping, idle"); + whichSocket = 1; + // Change the log name +- ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); ++ ::openlog("bbstored/hk", LOG_PID, LOG_DAEMON); + // Log that housekeeping started + BOX_INFO("Housekeeping process started"); + // Ignore term and hup diff --git a/patches/05-dont_use_net_for_docs.diff b/patches/05-dont_use_net_for_docs.diff new file mode 100644 index 00000000..71cb6c25 --- /dev/null +++ b/patches/05-dont_use_net_for_docs.diff @@ -0,0 +1,12 @@ +=== modified file 'docs/Makefile' +--- a/docs/Makefile ++++ b/docs/Makefile +@@ -10,7 +10,7 @@ + + all: docs + +-DBPROC_COMMAND = xsltproc ++DBPROC_COMMAND = xsltproc --nonet + MKDIR_COMMAND = mkdir + CP_COMMAND = cp + PERL_COMMAND = perl diff --git a/patches/06-fixup-bbstored-certs.diff b/patches/06-fixup-bbstored-certs.diff new file mode 100644 index 00000000..f752bd2c --- /dev/null +++ b/patches/06-fixup-bbstored-certs.diff @@ -0,0 +1,35 @@ +From: Reinhard Tartler +Subject: Fixup bbstored for newer openssl + +It appears that modern openssl versions slightly changed the formatting +for printing the common name of a certificate. + +I've also dropped the check against filename because I cound't get the +filename to match against my local files - the check didn't appear too +useful to me. + + +--- a/bin/bbstored/bbstored-certs.in ++++ b/bin/bbstored/bbstored-certs.in +@@ -171,12 +171,6 @@ sub cmd_sign + + my $acc = $1; + +- # check against filename +- if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) +- { +- die "Certificate request filename does not match name in certificate ($common_name)" +- } +- + print <<__E; + + This certificate is for backup account +@@ -288,7 +282,7 @@ sub get_csr_common_name + my $subject; + while() + { +- $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ ++ $subject = $1 if m/Subject:.+?CN\s?=\s?([-\.\w]+)/ + } + close CSRTEXT; + diff --git a/patches/07-fix-ftbfs-signed-char.diff b/patches/07-fix-ftbfs-signed-char.diff new file mode 100644 index 00000000..559fb314 --- /dev/null +++ b/patches/07-fix-ftbfs-signed-char.diff @@ -0,0 +1,11 @@ +--- a/lib/httpserver/cdecode.cpp ++++ b/lib/httpserver/cdecode.cpp +@@ -12,7 +12,7 @@ extern "C" + + int base64_decode_value(char value_in) + { +- static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; ++ static const signed char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; diff --git a/patches/series b/patches/series new file mode 100644 index 00000000..a03c1985 --- /dev/null +++ b/patches/series @@ -0,0 +1,4 @@ +03-adjust-syslog-facility.diff +05-dont_use_net_for_docs.diff +06-fixup-bbstored-certs.diff +07-fix-ftbfs-signed-char.diff diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 00000000..6cd99807 --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +[type: gettext/rfc822deb] boxbackup-client.templates +[type: gettext/rfc822deb] boxbackup-server.templates diff --git a/po/cs.po b/po/cs.po new file mode 100644 index 00000000..195059dd --- /dev/null +++ b/po/cs.po @@ -0,0 +1,512 @@ +# Czech translation of boxbackup debconf messages. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Miroslav Kure , 2007,2008. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-01 11:36+0200\n" +"Last-Translator: Miroslav Kure \n" +"Language-Team: Czech \n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Má se klient BoxBackupu nastavit automaticky?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Konfigurační skript balíku může vytvořit konfigurační soubory pro klienta " +"BoxBackupu." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Pokud zrovna nekamarádíte s konfiguračními volbami BoxBackupu, měli byste " +"tuto možnost povolit." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Podrobnosti o nastavení klienta BoxBackupu naleznete v souboru /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Režim spouštění klienta BoxBackupu:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Klient BoxBackupu podporuje dva režimy zálohování:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"V „líném“ režimu bude zálohovací daemon pravidelně prohledávat souborový " +"systém a hledat změněné soubory. Poté nahraje soubory starší než zadaný věk " +"na zálohovací server." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Ve „snímkovém“ režimu se bude zálohování spouštět v pravidelných " +"intervalech. S balíkem se dodává cronový soubor /etc/cron.d/boxbackup-" +"client, který byste měli upravit dle svých potřeb." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Číslo účtu tohoto uzlu na zálohovacím serveru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Správce serveru BoxBackup by měl tomuto klientovi přidělit hexadecimální " +"číslo účtu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Pokud tomuto klientovi ještě nebylo přiděleno číslo účtu, ponechte pole " +"prázdné a nastavte jej později spuštěním „dpkg-reconfigure boxbackup-client“ " +"pod uživatelem root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Neplatné číslo účtu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Číslo účtu musí být hexadecimální číslo (např. 1F04 nebo 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Plně kvalifikované jméno zálohovacího serveru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Zadejte prosím plně kvalifikované doménové jméno BoxBackup serveru, který má " +"tento klient používat." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klient se připojí k serveru na TCP portu 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Seznam adresářů pro zálohování:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Zadejte prosím mezerami oddělený seznam adresářů, které se mají zálohovat na " +"vzdálený server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Tyto adresáře by v žádné úrovni podadresářů neměly obsahovat připojené " +"souborové systémy." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Neplatná cesta" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "Cesty k souborům musí být absolutní a oddělené mezerami." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Například: /home/mujucet /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (v sekundách) mezi prohledáváním adresářů:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup pravidelně prohledává vybrané adresáře a hledá změněné soubory." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Zadejte prosím interval mezi prohledáváními." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimální doba čekání (v sekundách) před nahráním souboru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Soubor se zazálohuje na server pouze po uplynutí určitého času od jeho " +"poslední změny." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Nízké hodnoty způsobí častější nahrávání na server, vytváření více revizí a " +"rychlejší rušení starých revizí." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximální doba čekání (v sekundách) před nahráním souboru:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Často upravované soubory nejspíš nikdy nedosáhnou minimální hranice před " +"nahráním souboru a tedy nebudou zálohovány." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Zadejte prosím maximální čas, po kterém je nahrání změněného souboru na " +"server vynuceno." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Zadán neplatný čas" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Zadejte prosím neprázdnou celočíselnou hodnotu větší než nula." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Příjemce výstražných upozornění:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Jestliže se během zálohy vyskytne problém, klient BoxBackupu posílá " +"výstražné upozornění." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Zadejte prosím jméno lokálního uživatele (například „root“) nebo poštovní " +"adresu (například „spravce@priklad.cz“)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Vytvořit privátní klíč klienta a požadavek na certifikát X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Klient BoxBackupu vyžaduje privátní RSA klíč a odpovídající certifikát " +"X.509, aby se mohl autentizovat vůči serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Oba se mohou vytvořit automaticky. Požadavek na certifikát budete muset " +"poslat správci BoxBackup serveru, který požadavek podepíše a pošle zpět " +"současně s certifikátem certifikační autority serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Tyto soubory by měly být nakopírovány do konfiguračního adresáře BoxBackupu. " +"Očekávaná jména souborů jsou zadána v souboru /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Má se BoxBackup nastavit automaticky?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Konfigurační skripty balíku mohou vytvořit konfigurační soubory BoxBackup " +"serveru." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Pokud zrovna nekamarádíte s konfiguračními volbami BoxBackupu, měli byste " +"tuto možnost povolit. Konfiguraci můžete provést i ručně pomocí skriptů " +"„raidfile-config“ a „bbstored-config“." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Dokud jej nenakonfigurujete, server se odmítne spustit. Každopádně je " +"doporučeno přečíst si /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Umístění RAID adresářů:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Vyberte prosím umístění tří adresářů se souborovými RAIDy." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Pro povolení RAIDu zadejte mezerami oddělený seznam tří oblastí, kde každá " +"by měla ležet na jiném fyzickém disku (například: „/raid/0.0 /raid/0.1 /" +"raid/0.2“)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Nechcete-li povolit RAID, zadejte cestu k jedinému adresáři, do kterého se " +"budou ukládat zálohy (například /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Pokud neexistují, budou tyto adresáře vytvořeny." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Neplatné cesty" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Například: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Velikost bloku RAIDu v uživatelském prostoru:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup využívá techniky RAIDu v uživatelském prostoru." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Zadejte prosím velikost bloku, která se má použít pro úložiště. Pro " +"maximální efektivitu byste měli zvolit stejnou velikost bloku, jakou má " +"souborový systém, na kterém je úložiště postaveno (pro souborové systémy " +"ext2 to zjistíte zjistíte například příkazem „tune2fs -l“)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Tuto hodnotu byste měli nastavit i v případě, že neplánujete použití RAIDu." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Vytvořit privátní klíč serveru a požadavek na certifikát X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup server vyžaduje privátní RSA klíč a odpovídající certifikát X.509, " +"aby mohl provádět autentizaci mezi klientem a serverem a aby mohl šifrovat " +"komunikaci." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Oba se mohou vytvořit automaticky. Certifikát budete muset podepsat svou " +"kořenovou certifikační autoritou (viz balík boxbackup-server) a podepsaný " +"jej umístit společně s kořenovým certifikátem certifikační autority do " +"složky s konfigurací." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Neplatná velikost bloku" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Velikost bloku musí být mocninou dvou (např. 1024 nebo 4096)." diff --git a/po/da.po b/po/da.po new file mode 100644 index 00000000..d9ca9924 --- /dev/null +++ b/po/da.po @@ -0,0 +1,515 @@ +# Danish translation BoxBackup. +# Copyright (C) 2010 BoxBackup & Joe Hansen. +# This file is distributed under the same license as the BoxBackup package. +# Joe Hansen , 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: BoxBackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2010-05-18 17:30+01:00\n" +"Last-Translator: Joe Hansen \n" +"Language-Team: Danish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Skal klienten til BoxBackup konfigureres manuelt?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Pakkekonfigurationsskripterne kan oprette konfigurationsfiler for klienten " +"til BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Du skal vælge denne indstilling, hvis du ikke kender til BoxBackups " +"konfigurationsindstillinger." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Læs venligst /usr/share/doc/boxbackup-client/README.Debian for detaljer om " +"konfigurationen til BoxBackup-klienten." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Kørselstilstand for klienten til BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "" +"Klienten til BoxBackup understøtter to tilstande til sikkerhedskopiering:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"I tilstanden 'lazy' vil dæmonen til sikkerhedskopiering med passende " +"mellemrum skanne filsystemet på jagt efter ændrede filer. Den vil så " +"overføre filer ældre end en angivet alder til sikkerhedskopiserveren." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"I tilstanden 'snapshot' vil sikkerhedskopien blive kørt med faste " +"intervaller. Et cronjob (/etc/cron.d/boxbackup-client) tilbydes sammen med " +"pakken og skal tilpasses til dine behov." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontonummer for denne knude på sikkerhedskopiserveren:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Administratoren på BoxBackup-serveren skal have tildelt denne klient et " +"heksadecimalt kontonummer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Hvis intet kontonummer er blevet tildelt endnu, så efterlad dette felt tomt " +"og konfigurer det senere ved at køre 'dpkg-reconfigure boxbackup-client' som " +"administrator (root)." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ugyldigt kontonummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Kontonummeret skal være et heksadecimalt nummer (såsom 1F04 eller 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Fuldt kvalificeret domænenavn på sikkerhedskopiserveren:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Indtast venligt det fuldt kvalificeret domænenavn på den BoxBackup-server " +"som din klient vil bruge." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klienten vil forbinde til serveren på TCP port 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste af mapper der skal sikkerhedskopieres:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Angiv venligst en mellemrumsadskilt liste af mapper der skal " +"sikkerhedskopieres til den eksterne server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"De mapper må ikke indeholde monterede filsystemer på noget niveau i deres " +"undermapper." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ugyldigt stinavn" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Stinavnene til mapperne skal være absolutte stinavne, adskilt af mellemrum." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "For eksempel. /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (i sekunder) mellem mappeskanninger:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup skanner med faste intervaller de udvalgte mapper, på jagt efter " +"ændrede filer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Vælg venligst skanningsinterval i sekunder." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimum ventetid (i sekunder) før overførsel af en fil:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"En fil vil først blive overført til serveren efter et bestemt tidsinterval " +"efter sidste ændring." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lave intervalværdier vil udløse ofte overførsler til serveren og flere " +"revisioner oprettes, hvilket medfører at gamle revisioner vil blive fjernet " +"tidligere." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maksimal ventetid (i sekunder) før overførsel af en fil:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Ofte ændrede filer vil højst sandsynlig aldrig blive overført, hvis de " +"aldrig når den mindste ventetid." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Indtast venligst den maksimale tid før en ændret fil bliver overført til " +"serveren." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ugyldig tid indtastede" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Indtast venligst en heltalsværdi større end nul." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Modtager til alarmbeskeder:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Klienten til BoxBackup sender alarmbeskeder, når et problem opstår under " +"sikkerhedskopieringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Indtast venligst enten et lokalt brugernavn (for eksempel 'root') eller en e-" +"post-adresse (for eksempel 'admin@eksempl.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Opret klientens private nøgle og X-509-certifikatanmodning?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Klienten til BoxBackup kræver en RSA-privatnøgle og det tilsvarende X.509-" +"certificat for at kunne dokumentere sig selv i forhold til serveren." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Begge kan oprettes automatisk. Du vil skulle sende certifikatanmodningen til " +"BoxBackups serveradministrator som vil underskrive den og sende den tilbage " +"til dig sammen med serverens Certification Authority-certifikat." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Disse filer skal kopiers ind i BoxBackups konfigurationsmappe. Filnavnene " +"der skal bruges er oplyst i filen /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Skal BoxBackup automatisk konfigureres?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Skriptene for pakkekonfigurationen kan oprette konfigurationsfilerne for " +"BoxBackup-serveren." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Du skal vælge denne indstilling hvis du ikke kender til BoxBackups " +"konfigurationsindstillinger. Denne konfiguration kan foretages manuelt med " +"skripterne 'raidfile-config' og 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Serveren vil ikke starte hvis den ikke er konfigureret. Uanset hvad så " +"anbefales det at læse /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Placering af RAID-mapperne:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Vælg venligst placeringen af de tre RAID-filmapper." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"For at aktivere RAID, skal mappenavnene være en mellemrumsadskilt liste af " +"tre partitioner, hver på forskellige fysiske harddiske (for eksempel: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Hvis du ikke ønsker at aktivere RAID, så specificer bare stien til en mappe " +"hvor sikkerhedskopierne vil blive gemt (for eksempel, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Disse mapper vil blive oprettet, hvis de ikke findes." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ugyldige stinavne" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "For eksempel: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blokstørrelse for userland-RAID-systemet:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup bruger userland-RAID-teknikker." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Vælg venligst blokstørrelse til brug for lageret. For maksimal effektivitet, " +"skal du vælge blokstørrelsen på det underlæggende filsystem (som kan ses for " +"ext2-filsystemer med kommandoen 'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Denne værdi skal angives, selv om du ikke planlægger at bruge RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Opret en serverprivatnøgle og X.509-certifikatforespørgsel?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-serveren kræver en RSA-privatnøgle og den tilsvarende X.509-" +"certificat for at udføre klient-server bekræftelse og kryptering af " +"kommunikation." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Begge kan oprettes automatisk. Du vil skulle underskrive certifikatet med " +"din root CA (se pakken boxbackup-server) og placer dette underskrevne " +"certificat og root CA-certifikatet i konfigurationsmappen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ugyldig blokstørrelse" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Blokstørrelsen skal kunne opløses i 2 (for eksempel 1024 eller 4096)." diff --git a/po/de.po b/po/de.po new file mode 100644 index 00000000..20eca289 --- /dev/null +++ b/po/de.po @@ -0,0 +1,526 @@ +# Translation of the boxbackup debconf template. +# Copyright (C) 2008 Kai Wasserbäch +# This file is distributed under the same license as boxbackup package. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.10+really0.10-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-02-13 22:06+0100\n" +"Last-Translator: Kai Wasserbäch \n" +"Language-Team: German \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Soll der BoxBackup-Client automatisch konfiguriert werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Die Paket-Konfigurations-Skripte können die Konfigurationsdateien für den " +"BoxBackup-Client erstellen." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Sie sollten diese Option wählen, falls Sie nicht mit den " +"Konfigurationsoptionen von BoxBackup vertraut sind." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Bitte lesen Sie für detaillierte Informationen zur Konfiguration des " +"BoxBackup-Clients die Datei /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Betriebsmodus für den BoxBackup-Client:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Der BoxBackup-Client unterstützt zwei Backup-Modi:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Im »lazy«-Modus prüft der Sicherungsdienst das Dateisystem regelmäßig, um " +"veränderte Dateien zu finden. Dateien, die ein zuvor festgelegtes Alter " +"überschritten haben, werden auf den Sicherungsserver überspielt." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Im »snapshot«-Modus wird die Sicherung in zuvor festgelegten Abständen " +"durchgeführt. Eine Cron-Datei (/etc/cron.d/backup-client) wird mit diesem " +"Paket ausgeliefert und sollte an die eigenen Anforderungen angepasst werden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontennummer für diesen Knoten auf dem Sicherungsserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Der Administrator des BoxBackup-Servers sollte diesem Client eine " +"hexadezimale Kontennummer zugewiesen haben." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Falls bislang noch keine Kontennummer zugewiesen wurde, lassen Sie dieses " +"Feld frei und konfigurieren Sie die Option später durch Ausführen des " +"Befehls »dpkg-reconfigure boxbackup-client« als root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ungültige Kontennummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Die Kontennummer muss hexadezimal sein (wie zum Beispiel 1F04 oder 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Vollständiger Domainname des Sicherungsservers:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Bitte geben Sie den durch Ihren Client zu verwendenden vollständigen " +"Domainnamen des BoxBackup-Servers an." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Der Client wird sich mit dem Server auf dem TCP-Port 2201 verbinden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste der zu sichernden Verzeichnisse:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Bitte geben Sie die auf den entfernten Server zu sichernden Verzeichnisse " +"als durch Leerzeichen getrennte Liste ein." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Diese Verzeichnisse sollten in keinem Unterverzeichnis ein eingebundenes " +"Dateisystem enthalten." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ungültige Pfadangabe" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Die Pfade zu den Verzeichnissen müssen absolut sein und durch Leerzeichen " +"getrennt angegeben werden." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Zum Beispiel: /home/meinkonto /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervall (in Sekunden) zwischen Verzeichnisüberprüfungen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup überprüft die ausgewählten Verzeichnisse von Zeit zu Zeit auf " +"modifizierte Dateien." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Bitte wählen Sie das Überprüfungsintervall in Sekunden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Mindestwartezeit (in Sekunden) vor dem Hochladen einer Datei:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Eine Datei wird erst eine bestimmte Zeit nach der letzten Modifikation auf " +"den Server übertragen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Kleine Intervalle resultieren in häufigeren Uploads auf den Server. Dabei " +"werden mehr Revisionen angelegt und alte früher gelöscht." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximale Wartezeit (in Sekunden) bevor eine Datei hochgeladen wird:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Häufig modifizierte Dateien werden wahrscheinlich nie auf den Server " +"übertragen, falls sie die minimale Wartezeit nicht erreichen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Bitte geben sie die Zeit ein, die maximal erreicht werden darf, bevor das " +"Hochladen einer Datei erzwungen wird." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ungültige Zeit eingegeben" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Bitte geben Sie einen ganzzahligen Wert größer Null ein." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Empfänger für Alarmmeldungen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Der BoxBackup-Client versendet Alarmmeldungen, wenn während des Sicherns ein " +"Problem auftritt." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Bitte geben Sie entweder einen lokalen Benutzernamen (zum Beispiel »root«) " +"oder eine E-Mail-Adresse (zum Beispiel »admin@example.org«) ein." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Soll der private Schlüssel für den Client und der X.509-Zertifikat-Anfrage " +"erzeugt werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Der BoxBackup-Client benötigt einen privaten RSA-Schlüssel und das " +"zugehörige X.509-Zertifikat, um sich gegenüber dem Server authentifizieren " +"zu können." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Beides kann automatisch erzeugt werden. Sie müssen die Zertifikatanfrage an " +"den Administrator des BoxBackup-Servers senden, der dieses dann signieren " +"und Ihnen zusammen mit dem Certification-Authority-Zertifikat des Servers " +"zurücksenden wird." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Diese Dateien sollten in das Konfigurationsverzeichnis von BoxBackup kopiert " +"werden. Die zu verwendenden Dateinamen werden in /etc/boxbackup/bbacupd.conf " +"angegeben." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Soll BoxBackup automatisch konfiguriert werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Die im Paket enthaltenen Konfigurationskripte können die Konfiguration für " +"den BoxBackup-Server erzeugen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Sie sollten diese Option wählen, falls Sie nicht mit den " +"Konfigurationsoptionen von BoxBackup vertraut sind. Die Konfiguration kann " +"mit Hilfe der Skripte »raidfile-config« und »bbstored-config« manuell " +"vorgenommen werden." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Der Server wird nicht starten, falls er nicht konfiguriert wurde. In jedem " +"Fall wird empfohlen, /usr/share/doc/boxbackup-server/README.Debian zu lesen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ort der RAID-Verzeichnisse:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Bitte wählen Sie den Ort für die drei RAID-Datei-Verzeichnisse aus." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Um die RAID-Funktion zu aktivieren, sollte eine durch Leerzeichen getrennte " +"Liste dreier Partitionen eingegeben werden. Jede dieser Partitionen sollten " +"auf einem anderen physischen Laufwerk sein (zum Beispiel »/raid/0.0 /" +"raid/0.1 /raid/0.2«)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Falls Sie die RAID-Funktion nicht aktivieren wollen, geben Sie einfach einen " +"Pfad zu einem Verzeichnis an, in dem die Sicherungskopien gespeichert werden " +"sollen (zum Beispiel /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Diese Verzeichnisse werden angelegt, sollten sie nicht existieren." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ungültige Pfadnamen" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Zum Beispiel: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blockgröße für das im Benutzerbereich angesiedelte RAID-System:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup verwendet RAID-Techniken für den Benutzerbereich." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Bitte wählen Sie die zu verwendende Blockgröße zur Speicherung. Um maximale " +"Effizienz zu erreichen, sollten Sie die Blockgröße des darunterliegenden " +"Dateisystems wählen. Diese kann für Ext-Dateisysteme mit dem Befehl »tune2fs " +"-l« angezeigt werden." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Dieser Wert sollte auch dann angegeben werden, falls Sie kein RAID verwenden " +"wollen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Soll ein privater Server-Schlüssel und eine X.509-Zertifikatanfrage erzeugt " +"werden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Der BoxBackup-Server benötigt einen privaten RSA-Schlüssel und ein " +"zugehöriges X.509-Zertifikat, um die Client-Server-Authentifizierung " +"durchführen zu können und die Kommunikation zu verschlüsseln." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Beides kann automatisch erzeugt werden. Sie müssen das Zertifikat mit Ihrem " +"Wurzel-CA-Zertifikat signieren (siehe das Paket »boxbackup-server«) und das " +"signierte Zertifikat sowie das Wurzel-CA-Zertifikat in Ihrem " +"Konfigurationsordner ablegen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ungültige Blockgröße." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Die Blockgröße muss eine Potenz von Zwei sein (z.B. 1024 oder 4096)." diff --git a/po/es.po b/po/es.po new file mode 100644 index 00000000..cf7bb72e --- /dev/null +++ b/po/es.po @@ -0,0 +1,549 @@ +# boxbackup po-debconf translation to Spanish +# Copyright (C) 2009 Software in the Public Interest +# This file is distributed under the same license as the boxbackup package. +# +# Changes: +# - Initial translation +# Omar Campagne , 2009 +# +# - Updates +# TRADUCTOR , AÑO +# +# Traductores, si no conocen el formato PO, merece la pena leer la +# de gettext, especialmente las secciones dedicadas a este +# formato, por ejemplo ejecutando: +# info -n '(gettext)PO Files' +# info -n '(gettext)Header Entry' +# +# Equipo de traducción al español, por favor, lean antes de traducir +# los siguientes documentos: +# +# - El proyecto de traducción de Debian al español +# http://www.debian.org/intl/spanish/ +# especialmente las notas de traducción en +# http://www.debian.org/intl/spanish/notas +# +# - La guía de traducción de po's de debconf: +# /usr/share/doc/po-debconf/README-trans +# o http://www.debian.org/intl/l10n/po-debconf/README-trans +# +msgid "" +msgstr "" +"Project-Id-Version: 0.11\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-07-16 18:15+0200\n" +"Last-Translator: Omar Campagne \n" +"Language-Team: Debian l10n Spanish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "¿Desea que el cliente de BoxBackup se configure automáticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"El paquete de scripts de configuración puede generar los archivos de " +"configuración para el cliente de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Debería escoger esta opción si no está familiarizado con las opciones de " +"configuración de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Lea el archivo «/usr/share/doc/boxbackup-client/README.Debian» para más " +"detalles acerca de la configuración del cliente de BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de ejecución para el cliente de BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "El cliente de BoxBackup ofrece dos modos de copia de seguridad:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"En modo «lazy» (vago), el demonio de copia de seguridad examinará con " +"regularidad el sistema de archivos en busca de archivos modificados. " +"Finalizado esto, subirá los archivos de una antigüedad específica al " +"servidor de copias de seguridad." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"En modo «snapshot» (captura de imagen), la copia de seguridad se generará a " +"intervalos regulares. Un archivo cron (/etc/cron.d/boxbackup-client) se " +"incluye en el paquete, el cual puede modificar para satisfacer sus " +"necesidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de cuenta de este nodo en el servidor de copia de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"El administrador del servidor de BoxBackup debería haber asignado a este " +"cliente un número de cuenta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Si no ha asignado aún ningún número de cuenta, deje este campo en blanco y " +"configúrelo más tarde ejecutando «dpkg-reconfigure boxbackup-client» como " +"administrador." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de cuenta no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"El número de cuenta debe ser un número hexadecimal (p. ej., «1F04» o «4500»)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nombre de dominio completo del servidor de copias de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Introduzca el nombre de dominio completo del servidor de BoxBackup que el " +"cliente usará." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "El cliente se conectará al servidor a través del puerto TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de directorios de los cuales crear una copia de seguridad:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Introduzca una lista de directorios, separados por un espacio, de los cuales " +"crear una copia de seguridad en el servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esos directorios no pueden contener sistemas de archivos montados en ningún " +"nivel de sus subdirectorios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nombre de ruta no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Los nombres de ruta de los directorios deben ser nombres de ruta absolutos, " +"separados por espacios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por ejemplo: «/home/tucuenta /etc/»" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo de tiempo (en segundos) entre exploraciones de directorios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Boxbackup explora los directorios seleccionados con regularidad en busca de " +"archivos modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Seleccione el intervalo entre cada exploración en segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tiempo mínimo de espera (en segundos) antes de subir un archivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un archivo se sube al servidor sólo después de un tiempo especifico desde su " +"última modificación." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Un valor bajo de intervalo propicia subidas de archivos más regulares al " +"servidor, generando más revisiones y haciendo que las revisiones más " +"antiguas se eliminen antes." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tiempo máximo de espera (en segundos) antes de subir un archivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Puede que los archivos modificados con frecuencia no se suban si nunca " +"alcanzan el tiempo mínimo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Introduzca el tiempo máximo de espera antes de forzar la subida de un " +"archivo modificado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tiempo introducido no válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Introduzca un valor entero mayor que 0." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario de las notificaciones de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"El cliente de BoxBackup envía notificaciones de alerta cuando ocurre un " +"problema durante la creación de la copia de seguridad." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Introduzca un nombre de usuario local (por ejemplo, «root») o una dirección " +"de correo electrónico (por ejemplo, «admin@example.org»)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"¿Desea generar la clave privada del cliente y la petición de certificado " +"X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"El cliente de BoxBackup necesita una clave privada RSA y su correspondiente " +"certificado X.509 para autenticarse con el servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos pueden ser generados automáticamente. Necesitará enviar la petición de " +"certificado al administrador del servidor de BoxBackup, el cual lo firmará y " +"reenviará a usted junto con el certificado de la «CA» (Autoridad de " +"Certificación) del servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Estos archivos se deberían copiar en el directorio de configuración de " +"BoxBackup. Los nombres de archivo empleados se encuentran en el archivo «/" +"etc/boxbackup/bbackupd.conf»." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "¿Desea que BoxBackup se configure automáticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Los scripts de configuración del paquete pueden generar los archivos de " +"configuración del servidor de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Debería escoger esta opción si no está familiarizado con las opciones de " +"configuración de BoxBackup. Puede configurarlo manualmente, o mediante los " +"scripts «raidfile-config» y «bbstored-config»." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"El servidor no se iniciará si no está configurado. En cualquier caso, se " +"recomienda leer «/usr/share/doc/boxbackup-server/README.Debian»." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ubicación de los directorios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Escoja la ubicación de los tres directorios RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para habilitar RAID, los nombres de directorio deberían conformar una lista " +"de tres particiones, separando cada elemento del mismo con un espacio, y " +"cada uno de ellos en diferentes discos duros físicos (por ejemplo: «/" +"raid/0.0 /raid/0.1 /raid/0.2»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Si no desea habilitar RAID, especifique sólo la ruta a un directorio donde " +"poder guardar las copias de seguridad (p. ej., «/usr/local/lib/boxbackup»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "En caso de no existir, se crearán estos directorios." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nombres de ruta no válidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por ejemplo: «/raid/0.0 /raid/0.1 /raid/0.2»" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamaño de bloque para el sistema «userland» de RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup utiliza técnicas «userland» de RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Escoja el tamaño de bloque empleado para el almacenamiento. Para obtener la " +"máxima eficiencia, debería escoger el tamaño de bloque del sistema de " +"archivos subyacente (el cual se muestra en los sistemas de archivos ext2 " +"ejecutando la orden «tune2fs -l»)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Debe definir este valor aunque no tenga planeado utilizar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"¿Desea generar una clave privada de servidor y la petición de certificado " +"X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"El servidor «BoxBackup» requiere una clave privada RSA y el correspondiente " +"certificado X.509 para la autenticación cliente-servidor y el cifrado de " +"comunicaciones." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos pueden ser generados automáticamente. Necesitará firmar el certificado " +"con su «CA» raíz (vea el paquete «boxbackup-server») y poner este " +"certificado firmado y el certificado de la «CA» raíz en el directorio de " +"configuración." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamaño de bloque no válido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "El tamaño del bloque ha de ser potencia de dos (p. ej., 1024 o 4096)." diff --git a/po/eu.po b/po/eu.po new file mode 100644 index 00000000..ff3d71b8 --- /dev/null +++ b/po/eu.po @@ -0,0 +1,527 @@ +# translation of boxbackup debconf to Basque +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# xabier bilbao , 2008. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup-eu\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-03 02:21+0200\n" +"Last-Translator: xabier bilbao \n" +"Language-Team: basque \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "BoxBackup bezeroa automatikoki konfiguratu behar al da?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Paketearen konfigurazio script-ek BoxBackup bezeroarentzako konfigurazio-" +"fitxategiak sor ditzakete." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Aukera hau hautatu beharko zenuke ez baldin badituzu Boxbackup-en " +"konfigurazio-aukerak ondo ezagutzen." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"BoxBackup bezeroaren konfigurazioaz xehetasun gehiago jakiteko, irakur " +"ezazu /usr/share/doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup bezeroaren exekuzio-modua:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup bezeroak bi modutara egin dezake babeskopia:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"'Lazy' (sinplifikatu) moduan, babeskopia-deabruak fitxategi-sistema aldiro " +"arakatuko du aldatutako fitxategien bila. Data jakin bat baino zaharragoak " +"diren fitxategiak babeskopien zerbitzarira igoko ditu." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"'Snapshot' (argazki) moduan, babeskopia aldiro exekutatuko da. Cron " +"fitxategi bat (/etc/cron.d/boxbackup-client) dakar paketeak, norberaren " +"beharren arabera egokitu behar dena." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Nodo honentzako kontu-zenbakia babeskopien zerbitzarian:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Bezero honek kontu-zenbaki hamaseitar bat izan behar luke BoxBackup " +"zerbitzariaren administratzaileak ezarririk." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Oraindik ez bazaio kontu-zenbakirik ezarri, utzi bete gabe arlo hau eta " +"konfigura ezazu geroago, 'dpkg-reconfigure boxbackup-client' root gisa " +"exekutatuz." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Kontu-zenbaki baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Zenbaki hamaseitarra izan behar da kontu-zenbakia (1F04 edo 4500 erakoa)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Babeskopia-zerbitzariaren guztiz kualifikaturiko domeinu-izena (FQDN):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Sar ezazu hemen zure bezeroak erabiliko duen BoxBackup zerbitzariaren guztiz " +"kualifikaturiko domeinu-izena." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Bezeroa TCP 2201 atakan konektatuko da zerbitzarira." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Babeskopian gordetzeko direktorioen zerrenda:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Zerrenda itzazu, tarte soilez banatuta, urruneko zerbitzarian babeskopian " +"gordetzeko direktorioak." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Direktorio horiek ez lukete eduki behar muntatutako fitxategi-sistemarik " +"haien azpidirektorioetako edozein mailatan." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Bide-izen baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Direktorioetarako bide-izen absolutuak idatzi behar dira, tartez banaturik." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Adibidez: /home/nirekontua /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Denbora-tartea (segundotan) direktorioen arakatzeen artean:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup-ek aldiro arakatzen ditu aukeratu diren direktorioak, aldatutako " +"fitxategien bila." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Zehaztu ezazu arakatzea zenbatero egin behar den." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" +"Fitxategi bat igo aurretik itxaron beharreko gutxieneko denbora (segundotan):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Denbora jakin bat itxarongo du programak fitxategi bakoitza, aldaketak jaso " +"ondoren, zerbitzarira igo aurretik." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Arakatzeetarako denbora tarte txikiak ezarriz gero, sarri igoko dira " +"fitxategiak zerbitzarira, eta sarri berrituko dira babeskopiak, ondorioz " +"kopia zaharrak lehenago ezabatuz." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "" +"Fitxategi bakoitza igo aurretik itxaron beharreko gehienezko denbora " +"(segundotan):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Sarri aldatzen diren fitxategiak zerbitzarira inoiz ere igo gabe geldi " +"daitezke, ez badute gutxieneko denbora irauten aldatu gabe." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Sar ezazu itxaron beharreko gehienezko denbora aldatutako fitxategi bakoitza " +"zerbitzarira igo aurretik." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Denbora baliogabea sartu duzu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Sar ezazu balio oso bat, zero baino handiagoa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Alerta abisuen hartzailea:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup bezeroak alerta abisuak bidaltzen ditu babeskopia egitean arazoren " +"bat gertatzen denean." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Sar ezazu erabiltzaile-izen lokal bat (adibidez, 'root') edo eposta helbide " +"bat (adibidez, 'admin@adibidea.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Bezeroarentzako gako pribatua eta X.509 ziurtagiriaren eskaera sortzea nahi " +"duzu?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup bezeroak RSA gako pribatua eta hari dagokion X.509 ziurtagiria " +"behar ditu bere burua zerbitzarian egiaztatzeko." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Biak automatikoki sor daitezke. Ziurtagiri-eskaera BoxBackup zerbitzariko " +"administratzaileari bidali beharko diozu. Hark izenpetuko du eta atzera zuri " +"bidaliko dizu zerbitzariko Ziurtagiri Emailearen (CA) ziurtagiriarekin " +"batera." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Fitxategi horiek BoxBackup-en konfigurazio direktoriora kopiatu behar dira. " +"Fitxategiei eman beharreko izenak /etc/boxbackup/bbackupd.conf fixategian " +"aurkituko dituzu." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup automatikoki konfiguratu?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Paketearen konfigurazio script-ek BoxBackup zerbitzarirako konfigurazio-" +"fitxategiak sor ditzakete." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Hauta ezazu bide hau ez badituzu BoxBackup-en konfigurazio aukerak ondo " +"ezagutzen. Konfigurazioa eskuz egin daiteke 'raidfile-config' eta 'bbstored-" +"config' script-en bidez." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Zerbitzaria ez da abiatuko konfiguraturik ez badago. Zure kasua edozein dela " +"ere, gomendagarria da /usr/share/doc/boxbackup-server/README.Debian " +"irakurtzea." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID direktorioen kokalekua:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Aukera ezazu hiru RAID fitxategi-direktorioen kokalekua." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAID kudeaketa gaitzeko, hiru partizioren zerrenda gisa eman behar dira " +"direktorio-izenak, tarte hutsez banaturik, eta direktorio bakoitza disko " +"gogor fisiko batean kokaturik (adibidez: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"RAID kudeaketa gaitu nahi ez baduzu, nahikoa duzu direktorio baterako bidea " +"ematea, babeskopiak bertan gorde daitezen (adibidez, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Direktorio hauek sortu egingo dira aurretik ez badaude." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Bide-izen baliogabeak" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Adibidez: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Userland (erabiltzaile-eremuko) RAID sistemarako bloke-tamaina:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "Boxbackup-ek userland (erabiltzaile-eremuko) RAID sistema darabil." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Aukera ezazu biltegiratzerako erabiliko den bloke-tamaina. Eraginkortasun " +"handiena lortzeko, aukera ezazu azpiko fitxategi-sistemak duen bloke-tamaina " +"bera (hura zein den ikusteko 'tune2fs -l' komandoa erabil daiteke ext2 " +"fitxategi-sistemen kasuan)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Balio hau ezarri beharra dago, RAID erabiltzeko asmoa ez baduzu ere." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Zerbitzarirako gako pribatua eta X.509 ziurtagiriaren eskaera sortzea nahi " +"duzu?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup bezeroak RSA gako pribatua eta hari dagokion X.509 ziurtagiria " +"behar ditu bezero/zerbitzari egiaztapena eta komunikazio-enkriptazioa " +"gauzatzeko." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Automatikoki sor daitezke biak. Horretarako, ziurtagiria sinatu beharko dizu " +"zure Ziurtagiri Emaile (CA) nagusiak (ikus boxbackup-server paketea) eta " +"sinaturiko ziurtagiria, root (erro) CAren ziurtagiriarekin batera, " +"konfigurazio-karpetan kokatu beharko duzu." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Bloke-tamaina baliogabea" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Bloke-tamainak biren berredura izan behar du (adb. 1024 edo 4096)." diff --git a/po/fi.po b/po/fi.po new file mode 100644 index 00000000..da150ff4 --- /dev/null +++ b/po/fi.po @@ -0,0 +1,504 @@ +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-03 23:20+0200\n" +"Last-Translator: Esko Arajärvi \n" +"Language-Team: Finnish \n" +"Language: fi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Finnish\n" +"X-Poedit-Country: FINLAND\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Tulisiko BoxBackup-asiakkaan asetukset tehdä automaattisesti?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"BoxBackup-asiakkaan asetustiedostot voidaan luoda paketin " +"asetuskomentosarjoilla." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "Valitse tämä, jos BoxBackupin asetusvalitsimet eivät ole tuttuja." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Lisätietoja BoxBackup-asiakkaan asetuksista löytyy tiedostosta /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup-asiakkaan ajotapa:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup-asiakas tukee kahdenlaisia varmuuskopioita:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Laiska tapa (”lazy”) tarkoittaa, että taustaohjelma säännöllisesti etsii " +"tiedostojärjestelmästä muuttuneita tiedostoja. Tämän jälkeen tiettyä ikää " +"vanhemmat tiedostot kopioidaan varmuuskopiopalvelimelle." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Levykuvatapa (”snapshot”) tarkoittaa, että varmuuskopio otetaan säännöllisin " +"aikavälein. Paketissa on mukana cron-tiedosto (/etc/cron.d/boxbackup-" +"client), joka tulisi mukauttaa tarpeisiin." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Tämän solmun tilinumero varmuuskopiopalvelimella:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"BoxBackup-palvelimen ylläpitäjän olisi tullut antaa tälle asiakkaalle " +"heksadesimaalinen tilinumero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Jos tilinumeroa ei ole vielä annettu, jätä kenttä tyhjäksi ja aseta se " +"myöhemmin ajamalla pääkäyttäjänä komento ”dpkg-reconfigure boxbackup-client”." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Virheellinen tilinumero" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Tilinumeron tulee olla heksadesimaaliluku (kuten 1F04 tai 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Varmuuskopiopalvelimen täydellinen verkkonimi:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "Anna asiakkaan käyttämän BoxBackup-palvelimen täydellinen verkkonimi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Asiakas ottaa yhteyden palvelimen TCP-porttiin 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Hakemistot, joista otetaan varmuuskopiot:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Anna välilyönnein eroteltu lista hakemistoista, joista tehdään varmuuskopiot " +"etäpalvelimelle." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Näiden hakemistojen ei tulisi sisältää liitettyjä tiedostojärjestelmiä " +"millään alihakemistojen tasolla." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Virheellinen polku" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Hakemistojen polkujen tulee olla täydellisiä ja ne tulee erottaa toisistaan " +"välilyönnein." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Esimerkiksi: /home/omahakemisto /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Hakemistojen skannausten välinen aika (sekunteina):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup säännöllisesti skannaa valitut hakemistot etsien muutettuja " +"tiedostoja." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Anna skannausten aikaväli sekunteina." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Vähimmäisodotusaika (sekunteina) ennen tiedoston kopioimista:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Tiedosto kopioidaan palvelimelle vasta, kun sen viimeisimmästä muokkauksesta " +"on kulunut tietty aika." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lyhyt aika-arvo aiheuttaa tiheitä kopiointeja palvelimelle, jolloin luodaan " +"useampia versioita ja vanhemmat versiot poistetaan aiemmin." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Enimmäisodotusaika (sekunteina) ennen tiedoston kopioimista:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Usein muokattuja tiedostoja ei kopioida koskaan talteen, jos muokkausten " +"välissä ei ole vähimmäisodotusaikaa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Anna enimmäisaika, jonka jälkeen muokattu tiedosto kopioidaan pakolla " +"palvelimelle." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Virheellinen aika annettu" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Anna nollaa suurempi kokonaisluku." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Varoitusviestien vastaanottaja:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup-asiakas lähettää varoitusviestejä, kun varmuuskopioiden otossa " +"ilmenee ongelmia." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Anna joko paikallinen käyttäjätunnus (esimerkiksi ”root”) tai " +"sähköpostiosoite (esimerkiksi ”admin@example.org”)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Luodaanko salattu asiakasavain ja X.509-varmennepyyntö?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup-asiakas tarvitsee salatun RSA-avaimen ja sitä vastaavan X.509-" +"varmenteen tunnistautuakseen palvelimelle." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Molemmat voidaan luoda automaattisesti. Varmennepyyntö tulee lähettää " +"BoxBackup-palvelimen ylläpitäjälle, joka allekirjoittaa sen ja lähettää sen " +"takaisin palvelimen varmentaja-varmenteen kanssa." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Nämä tiedostot tulisi kopioida BoxBackupin asetustiedostoon. Käytettävät " +"tiedostonimet on annettu tiedostossa /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Tulisiko BoxBackupin asetukset tehdä automaattisesti?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"BoxBackup-palvelimen asetustiedostot voidaan luoda paketin " +"asetuskomentosarjoilla." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Valitse tämä, jos BoxBackupin asetusvalitsimet eivät ole tuttuja. Asetukset " +"voidaan tehdä käsin komentosarjoilla ”raidfile-config” ja ”bbstored-config”." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Palvelin ei käynnisty ennen kuin sen asetukset on tehty. On joka tapauksessa " +"suositeltavaa lukea tiedosto /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID-hakemistojen sijainti:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Anna kolmen RAID-tiedostohakemiston sijainnit." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAIDin ottaminen käyttöön vaatii, että annetut hakemistot ovat kolmen, eri " +"fyysisillä kovalevyillä sijaitsevan osion välilyönnein eroteltu lista. " +"(esimerkiksi: ”/raid/0.0 /raid/0.1 /raid/0.2”)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Jos RAIDia ei haluta käyttää, anna vain yhden hakemiston polku (esimerkiksi: " +"”/usr/local/lib/boxbackup”). Varmuuskopiot tallennetaan tänne." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Nämä hakemistot luodaan, jos niitä ei vielä ole olemassa." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Virheellisiä polkuja" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Esimerkiksi: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Userland RAID -järjestelmien lohkokoko:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup käyttää userland RAID -tekniikoita." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Valitse varastossa käytettävä lohkokoko. Paras tehokkuus saadaan, jos " +"lohkokoko on sama kuin alla olevassa tiedostojärjestelmässä (joka saadaan " +"ext2-tiedostojärjestelmässä näkyviin komennolla ”tune2fs -l”)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Tämä arvo tulisi asettaa, vaikka RAIDia ei aiottaisi käyttää." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Luodaanko salainen palvelinavain ja X.509-varmennepyyntö?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-palvelin tarvitsee salaisen RSA-avaimen ja sitä vastaavan X.509-" +"varmenteen asiakkaiden ja palvelimen väliseen tunnistautumiseen ja " +"viestinnän salaamiseen." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Molemmat voidaan luoda automaattisesti. Varmenne tulee allekirjoittaa " +"juurivarmenteella (”root CA”, katso pakettia boxbackup-server). " +"Allekirjoitettu varmenne ja juurivarmenne tulee laittaa ohjelman " +"asetustiedostoon." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Virheellinen lohkokoko" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Lohkokoon tulee olla kahden potenssi (esim. 1024 tai 4096)." diff --git a/po/fr.po b/po/fr.po new file mode 100644 index 00000000..6294c5aa --- /dev/null +++ b/po/fr.po @@ -0,0 +1,526 @@ +# Copyright (C) 2007, Vincent Bernat +# This file is distributed under the same license as the boxbackup package. +# +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup_0.10-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2007-06-26 07:42+0200\n" +"Last-Translator: Vincent Bernat \n" +"Language-Team: French \n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Faut-il automatiquement configurer le client BoxBackup ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Les scripts de configuration de ce paquet peuvent créer les fichiers de " +"configuration pour le client BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Vous devriez utiliser cette option si les options de configuration de " +"BoxBackup ne vous sont pas familières." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Veuillez consulter le fichier /usr/share/doc/boxbackup-client/README.Debian " +"pour obtenir des détails sur la configuration du client BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Mode d'exécution pour le client BoxBackup :" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Le client BoxBackup gère deux modes de sauvegarde :" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Avec le mode simplifié (« lazy »), le démon de sauvegarde recherchera " +"régulièrement les fichiers modifiés sur le système. Il enverra les fichiers " +"plus anciens qu'un âge donné au serveur de sauvegarde." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Avec le mode instantané (« snapshot »), la sauvegarde sera lancée " +"explicitement à intervalles réguliers. Le fichier /etc/cron.d/boxbackup-" +"client pour le démon cron est fourni avec le paquet et devra être adapté à " +"vos besoins." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Numéro de compte pour ce nœud sur le serveur de sauvegarde :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"L'administrateur de BoxBackup doit assigner à ce client un numéro de compte " +"en hexadécimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Si aucun numéro de compte n'est encore assigné, laissez ce champ vide et " +"remplissez le plus tard en lançant la commande « dpkg-reconfigure boxbackup-" +"client » en tant qu'utilisateur root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Numéro de compte invalide" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Le numéro de compte doit être un nombre en hexadécimal (comme 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nom d'hôte complet (FQDN) du serveur de sauvegarde :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Veuillez indiquer le nom d'hôte complet (FQDN) du serveur BoxBackup que " +"votre client doit utiliser." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Le client se connectera sur le port TCP 2201 du serveur." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Liste des répertoires à sauvegarder :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Veuillez indiquer une liste de répertoires, séparés par des espaces, à " +"sauvegarder sur le serveur distant." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Ces répertoires ne doivent pas contenir de systèmes de fichiers montés dans " +"l'un de leurs sous-répertoires, quelle que soit la profondeur." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Chemin invalide" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Ces répertoires doivent être indiqués sous forme de chemins absolus et " +"séparés par des espaces." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Exemple : /home/myaccount /etc/." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalle (en secondes) entre deux parcours du répertoire :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup parcourt régulièrement les répertoires sélectionnés à la recherche " +"de fichiers modifiés." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Veuillez choisir l'intervalle entre deux parcours en secondes." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Temps minimum à attendre (en secondes) avant d'envoyer un fichier :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un fichier est envoyé au serveur uniquement après un certain temps après sa " +"date de dernière modification." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Un intervalle faible provoquera des envois fréquents vers le serveur et des " +"versions successives en grand nombre. Les versions plus anciennes seront " +"également supprimées plus tôt." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Délai maximum (en secondes) avant d'envoyer un fichier :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Des fichiers fréquemment modifiés risquent de ne jamais être envoyés si le " +"temps minimal avant envoi n'est jamais atteint." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Veuillez indiquer le délai maximum à attendre avant de forcer l'envoi d'un " +"fichier vers le serveur." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Délai non valable" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Veuillez indiquer un entier strictement positif." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinataire des notifications d'alertes :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Le client BoxBackup envoie des notifications d'alertes quand un problème " +"survient lors d'une sauvegarde." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Vous pouvez soit indiquer un identifiant local (par exemple « root ») ou une " +"adresse de courrier électronique (par exemple « admin@example.org »)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Faut-il créer la clef privée et la demande de certificat X.509 du client ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Le client BoxBackup a besoin d'une clef privée RSA et d'un certificat X.509 " +"correspondant pour s'authentifier auprès du serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Tous deux peuvent être créés automatiquement. Vous devrez envoyer la demande " +"de certificat à l'administrateur du serveur BoxBackup qui la signera et vous " +"la renverra accompagnée du certificat de l'autorité de certification (CA) du " +"serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Ces fichiers doivent être copiés dans le répertoire de configuration de " +"BoxBackup. Les noms de fichier à utiliser sont indiqués dans le fichier /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Faut-il automatiquement configurer BoxBackup ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Les scripts de configuration de ce paquet peuvent créer les fichiers de " +"configuration pour le serveur BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Vous devriez utiliser cette option si les options de configuration de " +"BoxBackup ne vous sont pas familières. Vous pouvez aussi configurer le " +"serveur vous-même à l'aide des scripts « raidfile-config » et « bbstored-" +"config »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Le serveur ne démarrera pas s'il n'est pas configuré. Dans tous les cas, la " +"lecture de /usr/share/doc/boxbackup-server/README.Debian est recommandée." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Emplacement des répertoires RAID :" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Veuillez choisir l'emplacement des trois répertoires RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Pour activer la gestion RAID, veuillez indiquer, séparés par des espaces, " +"les noms de trois partitions situées sur des disques physiques différents " +"(par exemple : « /raid/0.0 /raid/0.1 /raid/0.2 »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Si vous ne voulez pas activer la gestion du RAID, indiquez seulement le " +"chemin d'un répertoire où les sauvegardes seront stockées (par exemple, /usr/" +"local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Ces répertoires seront créés s'ils n'existent pas." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Chemins non valables" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Par exemple : /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Taille des blocs pour le système RAID en espace utilisateur :" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup utilise un système de RAID en espace utilisateur." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Veuillez choisir une taille de blocs pour le stockage. Pour une efficacité " +"maximale, vous devriez choisir la même taille de blocs que le système de " +"fichiers sous-jacent (que vous pouvez obtenir, pour les systèmes de fichiers " +"ext2 et ext3, avec la commande « tune2fs -l »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Cette valeur est nécessaire même si vous ne comptez pas utiliser le RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Faut-il créer une clef privée et une demande de certificat X.509 pour le " +"serveur ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Le serveur BoxBackup a besoin d'une clef privée RSA et du certificat X.509 " +"correspondant pour l'authentification et le chiffrement entre le client et " +"le serveur." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ces deux éléments peuvent être créés automatiquement. Vous aurez besoin de " +"faire signer le certificat par votre autorité de certification principale " +"(voir le paquet boxbackup-server) et de placer le certificat signé " +"accompagné du certificat du CA racine dans le répertoire de configuration." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Taille de blocs non valable" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"La taille d'un bloc doit être une puissance de deux (par exemple 1024 ou " +"4096)." diff --git a/po/gl.po b/po/gl.po new file mode 100644 index 00000000..d981e9a6 --- /dev/null +++ b/po/gl.po @@ -0,0 +1,516 @@ +# Galician translation of boxbackup's debconf templates +# This file is distributed under the same license as the boxbackup package. +# Jacobo Tarrio , 2008. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-05-31 18:06+0100\n" +"Last-Translator: Jacobo Tarrio \n" +"Language-Team: Galician \n" +"Language: gl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "¿Quere configurar o cliente BoxBackup de xeito automático?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuración do paquete poden crear os ficheiros de " +"configuración do cliente BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Debería escoller esta opción se non está afeito ás opcións de configuración " +"de BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Consulte o ficheiro /usr/share/doc/boxbackup-client/README.Debian para máis " +"información sobre a configuración do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execución do cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup soporta dous modos de copia de seguridade:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo \"preguiceiro\" (\"lazy\"), o servizo de copias de seguridade ha " +"explorar periodicamente o sistema de ficheiros á busca de ficheiros " +"modificados. Despois ha copiar ao servidor os ficheiros máis vellos dunha " +"determinada idade." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo \"imaxe\" (\"snapshot\"), a copia de seguridade hase facer a " +"intervalos regulares. Fornécese un ficheiro cron (/etc/cron.d/boxbackup-" +"client) co paquete, que se debe adaptar ás súas necesidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de conta para este nodo no servidor de copias de seguridade:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup debeulle asignar a este cliente un " +"número de conta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se non se asignou aínda ningún número de conta, deixe este campo baleiro e " +"configúreo máis adiante executando \"dpkg-reconfigure boxbackup-client\" " +"coma administrador." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta non válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta debe ser un número hexadecimal (coma 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome de dominio completo do servidor de copias de seguridade:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Introduza o nome de dominio completo do servidor BoxBackup que ha empregar o " +"cliente." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente hase conectar ao servidor no porto TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de directorios a copiar:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Introduza unha lista de nomes de directorios a copiar no servidor remoto, " +"separados por espazos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Eses directorios non deberían conter sistemas de ficheiros montados en " +"ningún nivel dos seus subdirectorios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ruta non válida" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"As rutas dos directorios deben ser rutas absolutas, separadas por espazos." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/conta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (en segundos) entre as exploracións dos directorios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup explora periodicamente os directorios seleccionados, á busca de " +"ficheiros modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Escolla o intervalo de exploración en segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo a agardar (en segundos) antes de copiar un ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Só se ha copiar un ficheiro ao servidor despois de que pase un certo tempo " +"trala súa última modificación." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Unha periodicidade curta de máis ha causar actualizacións frecuentes no " +"servidor e a creación de máis revisións, o que ha facer que se eliminen " +"antes as revisións antigas." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo a agardar (en segundos) antes de copiar un ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"É posible que os ficheiros que se modifican con frecuencia non se copien " +"nunca se nunca chegan ao tempo de espera mínimo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Introduza o tempo máximo a agardar antes de forzar a copia dun ficheiro " +"modificado ao servidor." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Introduciuse un tempo non válido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Introduza un número enteiro maior que cero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario para os avisos das alertas:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envía alertas cando hai un problema durante a copia de " +"seguridade." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Introduza un nome de usuario local (por exemplo, \"root\") ou un enderezo de " +"email (por exemplo, \"admin@exemplo.org\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "¿Xerar a clave privada e a solicitude de certificado X.509 do cliente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa dunha clave privada RSA e o certificado X.509 " +"correspondente para se autenticar co servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Pódense xerar os dous automaticamente. Ha ter que enviar a solicitude de " +"certificado ao administrador do servidor BoxBackup, que a ha asinar e lla ha " +"enviar de volta co certificado de Autoridade Certificadora do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Debe copiar eses ficheiros ao directorio de configuración de BoxBackup. Os " +"nomes de ficheiro a empregar figuran no ficheiro /etc/boxbackup/bbackupd." +"conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "¿Quere configurar BoxBackup de xeito automático?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuración do paquete poden crear os ficheiros de " +"configuración do servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Debería escoller esta opción se non está afeito ás opcións de configuración " +"de BoxBackup. Pódese facer a configuración cos scripts \"raidfile-config\" e " +"\"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Non se ha iniciar o servidor se non está configurado. En tódolos casos, " +"recoméndase ler o ficheiro /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Ubicación dos directorios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Indique a ubicación dos tres directorios de ficheiros RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para activar o RAID, os nomes dos directorios deben ser unha lista con tres " +"particións separadas por espazos, cada unha delas nun disco duro físico " +"diferente (por exemplo, \"/raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se non quere activar o RAID, indique a ruta a un só directorio no que " +"armacenar as copias de seguridade (por exemplo, /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Hanse crear eses directorios se non existen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Rutas non válidas" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamaño de bloque para o sistema de RAID de nivel de usuario:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup emprega técnicas de RAID de nivel de usuario." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Escolla o tamaño de bloque a empregar para o armacenamento. Para alcanzar a " +"máxima eficiencia, debería escoller o tamaño de bloque do sistema de " +"ficheiros (que se pode ver, para os sistemas de ficheiros ext2, coa orde " +"\"tune2fs -l\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Debería configurar este valor incluso se non quere empregar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"¿Xerar a clave privada e a solicitude de certificado X.509 do servidor?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa dunha clave privada RSA e o certificado X.509 " +"correspondente para realizar a autenticación do cliente e o cifrado das " +"comunicacións." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Pódense xerar os dous automaticamente. Ha ter que asinar o certificado coa " +"CA raíz (vexa o paquete boxbackup-server) e poñer este certificado asinado e " +"o certificado da CA raíz no directorio de configuración." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamaño de bloque non válido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"O tamaño de bloque debe ser unha potencia de 2 (por exemplo, 1024 ou 4096)." diff --git a/po/it.po b/po/it.po new file mode 100644 index 00000000..564d3ccb --- /dev/null +++ b/po/it.po @@ -0,0 +1,525 @@ +# Italian translation of boxbackup debconf messages. +# Copyright (C) 2012 +# This file is distributed under the same license as the boxbackup package. +# Beatrice Torracca , 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11.1~r2837-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2012-01-30 15:51+0100\n" +"Last-Translator: Beatrice Torracca \n" +"Language-Team: Italiano \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n!=1)\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Configurare automaticamente il client BoxBackup?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Gli script di configurazione del pacchetto possono creare i file di " +"configurazione per il client BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Scegliere questa opzione se non si ha familiarità con le opzioni di " +"configurazione di BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Per dettagli sulla configurazione del client BoxBackup, leggere /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modalità di esecuzione del client BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Il client BoxBackup gestisce due modalità di backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Nella modalità \"pigra\" (\"lazy\"), il demone di backup scansiona a " +"intervalli regolari il file system alla ricerca di file modificati. Carica " +"quindi i file più vecchi di una data specificata sul server di backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Nella modalità \"istantanea\" (\"snapshot\") il backup viene eseguito " +"esplicitamente ad intervalli regolari. Con il pacchetto viene fornito un " +"file cron (/etc/cron.d/boxbackup-client) che deve essere adattato alle " +"proprie necessità." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Numero di account per questo nodo sul server di backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"L'amministratore del server BoxBackup dovrebbe aver assegnato un numero di " +"account esadecimale a questo client." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se non è ancora stato assegnato alcun numero di account, lasciare questo " +"campo in bianco e configurarlo in seguito eseguendo \"dpkg-reconfigure " +"boxbackup-client\" come utente root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Numero di account non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Il numero di account deve essere un numero esadecimale (come 1F04 o 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome di dominio pienamente qualificato del server di backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Inserire il nome di dominio pienamente qualificato del server BoxBackup che " +"verrà usato dal client." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Il client si connetterà al server sulla porta TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista di directory di cui fare il backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Inserire una lista, separata da spazi, di directory di cui fare il backup " +"sul server remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Queste directory non devono contenere file system montati in nessun livello " +"delle loro sottodirectory." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome di percorso non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"I nomi di percorso delle directory devono essere nomi di percorso assoluti, " +"separati da spazi." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Ad esempio: /home/mioaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervallo (in secondi) tra le scansioni delle directory:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup scansiona a intervalli regolari le directory selezionate alla " +"ricerca dei file modificati." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Scegliere l'intervallo di scansione in secondi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo minimo di attesa (in secondi) prima di caricare un file:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Un file viene caricato sul server solo dopo che è trascorso un certo tempo " +"dalla sua ultima modifica." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Valori di intervallo bassi fanno scattare caricamenti frequenti sul server, " +"creano più revisioni e fanno sì che le revisioni più vecchie vengano rimosse " +"prima." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo massimo di attesa (in secondi) prima di caricare un file:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"È possibile che i file modificati di frequente non vengano mai caricati se " +"non raggiungono mai il tempo minimo di attesa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Inserire il tempo massimo da raggiungere prima di forzare il caricamento di " +"un file modificato sul server." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tempo inserito non valido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Inserire un valore intero maggiore di zero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatario delle notifiche di avvertimento:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Il client BoxBackup invia notifiche di avvertimento quando si verifica un " +"problema durante il backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Inserire un nome di utente locale (ad esempio \"root\") oppure un indirizzo " +"di posta elettronica (ad esempio \"admin@example.com\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" +"Generare la richiesta di chiave privata e di certificato X.509 del client?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Il client BoxBackup necessita di una chiave RSA privata e del certificato " +"X.509 corrispondente per autenticarsi sul server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Entrambi possono essere generati automaticamente. È necessario inviare la " +"richiesta di certificato all'amministratore del server BoxBackup che la " +"firmerà e invierà indietro insieme al certificato Certification Authority " +"del server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Questi file devono essere copiati nella directory di configurazione di " +"BoxBackup. I nomi di file da usare sono indicati nel file /etc/boxbackup/" +"bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Configurare BoxBackup automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Gli script di configurazione possono creare i file di configurazione per il " +"server BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Scegliere questa opzione se non si ha familiarità con le opzioni di " +"configurazione di BoxBackup. La configurazione può essere fatta in modo " +"manuale con gli script \"raidfile-config\" e \"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Il server non si avvia se non è configurato. In ogni caso è raccomandata la " +"lettura di /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Posizione delle directory RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Scegliere la posizione per le tre directory dei file RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Per abilitare RAID, i nomi di directory devono essere una lista separata da " +"spazi di tre partizioni, ciascuna su un'unità fisica differente (ad esempio: " +"\"/raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se non si desidera abilitare RAID, specificare semplicemente il percorso di " +"una directory in cui verranno memorizzati i backup (ad esempio, /usr/local/" +"lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Se non esistono, queste directory verranno create." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomi di percorso non validi" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Ad esempio: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Dimensione dei blocchi per il sistema RAID in spazio utente:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa tecniche RAID in spazio utente." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Scegliere la dimensione dei blocchi da usare per la memorizzazione. Per una " +"massima efficienza, scegliere la dimensione dei blocchi del file system " +"sottostante (che, per i file system ext2, può essere visualizzata con il " +"comando \"tune2fs -l\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Questo valore andrebbe impostato anche se non si ha intenzione di usare RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Generare una richiesta di chiave privata e certificato X.509 per il server?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Il server BoxBackup necessita di una chiave RSA privata e del certificato " +"X.509 corrispondente per effettuare l'autenticazione client-server e la " +"cifratura delle comunicazioni." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Entrambi possono essere generati automaticamente. È necessario firmare il " +"certificato con la CA di root (vedere il pacchetto boxbackup-server) e " +"mettere il certificato firmato e il certificato CA di root nella directory " +"di configurazione." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Dimensione dei blocchi non valida" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"La dimensione dei blocchi deve essere una potenza del due (es., 1024 o 4096)." diff --git a/po/ja.po b/po/ja.po new file mode 100644 index 00000000..872b8d47 --- /dev/null +++ b/po/ja.po @@ -0,0 +1,509 @@ +# Copyright (C) 2009 Reinhard Tartler +# This file is distributed under the same license as boxbackup package. +# Hideki Yamane (Debian-JP) , 2009. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc3~r2502-2\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-11-10 14:26+0900\n" +"Last-Translator: Hideki Yamane (Debian-JP) \n" +"Language-Team: Japanese \n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "BoxBackup クライアントを自動的に設定しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"パッケージの設定スクリプトは BoxBackup クライアント用の設定ファイルを作成でき" +"ます。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"あなたが BoxBackup の設定オプションに詳しくない場合は、このオプションを選んで" +"ください。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"BoxBackup クライアントの設定の詳細については /usr/share/doc/boxbackup-client/" +"README.Debian を参照してください。" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "BoxBackup クライアントの動作モード:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup クライアントは 2 つのバックアップモードをサポートしています:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"'lazy' モードでは、バックアップのデーモンは変更されたファイルを探すため、定期" +"的にファイルシステムをスキャンします。そして、指定された期間よりも古いファイ" +"ルをバックアップサーバへアップロードします。" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"'snapshot' モードは、バックアップは明確に定期的な間隔で動作します。パッケージ" +"で cron ファイル (/etc/cron.d/boxbackup-client) が用意され、このファイルが用" +"途に合うはずです。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "バックアップサーバ上でのこのノードのアカウント番号:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"BoxBackup サーバの管理者は、このクライアントを 16 進数のアカウント番号で割り" +"当てる必要があります。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"アカウント番号がまだ割り当てられていない場合は、この欄を空白のままにして後ほ" +"ど root ユーザにて 'dpkg-reconfigure boxbackup-client' を実行して設定をしてく" +"ださい。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "アカウント番号が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "アカウント番号は (1F04 や 4500 などの) 16 進数である必要があります。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "バックアップサーバの完全修飾ドメイン名 (FQDN):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"クライアントが利用する BoxBackup サーバの完全修飾ドメイン名 (FQDN) を入力して" +"ください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "クライアントはサーバの TCP ポート 2201 に接続します。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "バックアップするディレクトリの一覧:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"リモートのサーバにバックアップされる、空白で区切ったディレクトリの一覧を指定" +"してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"このディレクトリには、サブディレクトリ以下にマウント済みのファイルシステムを" +"含んではいけません。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "パス名 (path) が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"ディレクトリへのパス名は絶対パスで、空白で区切られている必要があります。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "例: /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "ディレクトリをスキャンする間隔 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup は変更されたファイルを探すため、一定間隔で指定されたディレクトリを" +"スキャンします。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "スキャンの間隔を秒数で指定してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "ファイルをアップロードするまでの最小待ち時間 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"最後に変更されてから一定時間が経過しないとファイルはアップロードされません。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"短い間隔にすると、サーバへ頻繁にアップロードすることになり、作成されるリビ" +"ジョンが多くなって、古いリビジョンが早く削除されます。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "ファイルをアップロードするまでの最大待ち時間 (秒数):" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"頻繁に変更されるファイルが最小待ち時間にも達しない場合、このファイルは全く" +"アップロードされないでしょう。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"変更されたファイルがサーバへ強制的にアップロードされるまでの最大時間を入力し" +"てください。" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "入力された時間が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "自然数を入力してください。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "警告通知の受信者:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup クライアントは、バックアップ中に問題が起きた際に警告の通知を送信し" +"ます。" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"ローカルユーザの名前 (例えば 'root') かメールアドレス (例 'admin@example." +"org') を入力してください。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "クライアント秘密鍵と X.509 証明書のリクエストを生成しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup クライアントは、サーバに認証を受ける際に RSA 秘密鍵と対応する " +"X.509 証明書を必要とします。" + +# FIXME: もっとわかりやすい表現 +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"双方とも自動的に生成できます。証明書リクエストを BoxBackup サーバの管理者に送" +"信する必要があります。BoxBackup サーバの管理者は、サーバの認証局の認証に基づ" +"いて証明書リクエストに署名して返信します。" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"このファイルは BoxBackup の設定ディレクトリにコピーされる必要があります。ファ" +"イル名は /etc/boxbackup/bbackupd.conf ファイルで指定します。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup を自動的に設定しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"パッケージの設定スクリプトで BoxBackup サーバの設定ファイルを生成できます。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"BoxBackup の設定オプションに詳しくない場合はこのオプションを選んでください。" +"設定は、手動の場合は 'raidfile-config' や'bbstored-config' スクリプトで設定で" +"きます。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"サーバは設定されていないと起動しません。どのような場合でも、/usr/share/doc/" +"boxbackup-server/README.Debian を一読いただくのをお勧めします。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "RAID ディレクトリの位置:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "RAID ファイルが置かれる 3 つのディレクトリを指定してください。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"RAID を有効にするには、ディレクトリ名は 3 つのパーティションのリストを空白で" +"区切ったもので、それぞれが別の物理ドライブ上にある必要があります (例: '/" +"raid/0.0 /raid/0.1 /raid/0.2')。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"RAID を有効にしたくない場合は、バックアップが保存される単一ディレクトリへのパ" +"ス (path) を指定してください (例えば、/usr/local/lib/boxbackup)。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "このディレクトリが存在しない場合、作成されます。" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "パス名が正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "例: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "ユーザランド RAID システムのブロックサイズ:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup はユーザランドの RAID 機能を使います。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"ストレージに使うブロックサイズを選んでください。効率を最大限にするには、ファ" +"イルシステムのブロックサイズに合わせて選ぶ必要があります (ext2 ファイルシステ" +"ムは 'tune2fs -l' コマンドで表示できます)。" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "RAID を使う予定が無いとしても、この値は設定する必要があります。" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "サーバ秘密鍵と X.509 証明書リクエストを生成しますか?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup サーバは、クライアントとサーバ間の認証と通信暗号化を行うために RSA " +"秘密鍵と対応する X.509 証明書を必要とします。" + +# FIXME もっとわかりやすい表現 +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"双方とも自動的に生成ができます。ルート認証局 (root CA) での証明書への署名 " +"(boxbackup-server パッケージを参照) して、署名した証明書と CA 証明書を設定" +"フォルダに配置する必要があります。" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "ブロックサイズが正しくありません" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "ブロックサイズは 2 の乗数である必要があります (例: 1024、4096)。" diff --git a/po/nl.po b/po/nl.po new file mode 100644 index 00000000..0ef91ab4 --- /dev/null +++ b/po/nl.po @@ -0,0 +1,528 @@ +# Dutch translation of boxbackup debconf templates. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Frans Spiesschaert , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2014-10-10 15:14+0200\n" +"Last-Translator: Frans Spiesschaert \n" +"Language-Team: Debian Dutch l10n Team \n" +"Language: nl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Moet de BoxBackup-client automatisch ingesteld worden?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"De configuratiescripts van het pakket kunnen de configuratiebestanden voor " +"de BoxBackup-client aanmaken." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"U zou deze optie moeten kiezen indien u niet vertrouwd bent met de " +"configuratieopties van BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Gelieve het bestand /usr/share/doc/boxbackup-client/README.Debian te " +"raadplegen voor details over het configureren van de BoxBackup-client." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modus voor het uitvoeren van de BoxBackup-client:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "De BoxBackup-client ondersteunt twee back-upmodussen:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"In de 'luie' modus zal de back-upachtergronddienst regelmatig het " +"bestandssysteem overlopen op zoek naar gewijzigde bestanden. Daarna zal het " +"bestanden ouder dan een ingestelde ouderdom naar de server uploaden." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"In de 'momentopname'-modus zal expliciet een back-up gemaakt worden met " +"vastgelegde intervallen. Met het pakket wordt een cron-bestand (/etc/cron.d/" +"boxbackup-client) meegeleverd dat volgens uw behoeften aangepast zou moeten " +"worden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Accountnummer voor deze machine op de back-upserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"De beheerder van de BoxBackup-server zou deze client een hexadecimaal " +"accountnummer moeten gegeven hebben." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Indien nog geen accountnummer toegekend werd, laat u dit veld leeg en " +"configureert u het later door als systeembeheerder het commando 'dpkg-" +"reconfigure boxbackup-client' uit te voeren." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ongeldig accountnummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Het accountnummer moet een hexadecimaal getal zijn (zoals 1F04 of 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "" +"Volledige domeinnaam (fully qualified domain name) van de back-upserver:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Voer de volledige domeinnaam in van de BoxBackup-server waarvan uw client " +"gebruik zal maken." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "De client zal met de server contact maken op de TCP-poort 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lijst van mappen waarvoor een back-up gemaakt moet worden:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Geef een met komma's gescheiden lijst op van mappen waarvoor een reservekopie " +"moet gemaakt worden op de externe server." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Deze mappen zouden op geen enkel niveau in de onderliggende mappen " +"aangekoppelde bestandssystemen mogen bevatten." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ongeldig pad" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"De namen van de mappen moeten het absolute pad er naartoe bevatten en ze " +"moeten met spaties van elkaar gescheiden worden." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Bijvoorbeeld: /home/mijn_account /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (in seconden) tussen het doorlopen van de mappen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Met tussenpozen doorloopt BoxBackup de opgegeven mappen op zoek naar " +"gewijzigde bestanden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Gelieve het doorloopinterval te kiezen, uitgedrukt in seconden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Minimale wachttijd (in seconden) voor het uploaden van een bestand:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Slechts een bepaalde tijd na de laatste wijziging aan een bestand wordt het " +"naar de server geüpload." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Lage inervalwaarden zullen frequente uploads naar de server en het aanmaken " +"van meer revisies tot gevolg hebben en meteen ook het vroeger wissen van " +"oudere revisies." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximale wachttijd (in seconden) vooraleer een bestand geüpload wordt:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"De kans bestaat dat bestanden die frequent bijgewerkt worden nooit geüpload " +"raken omdat ze nooit de minimale wachttijd halen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Gelieve aan te geven hoelang maximaal gewacht mag worden vooraleer het " +"uploaden van een gewijzigd bestand naar de server afgedwongen wordt." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "De opgegeven tijd is ongeldig" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Gelieve een geheel getal in te geven groter dan nul." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Ontvanger van waarschuwingen:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"De BoxBackup-client stuurt een waarschuwing wanneer er zich een probleem " +"voordoet tijdens de back-up." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Geef ofwel de naam van een lokale gebruiker op (bijvoorbeeld 'root') of een " +"e-mailadres (bijvoorbeeld 'admin@voorbeeld.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Een geheime sleutel en een verzoek om een X.509-certificaat aanmaken?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"De BoxBackup-client heeft een geheime RSA-sleutel en een overeenkomstig " +"X.509-certificaat nodig om zich bij de server te authenticeren." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Beide kunnen automatisch aangemaakt worden. U zult het verzoek om een " +"certificaat moeten sturen naar de beheerder van de BoxBackup-server, die het " +"zal ondertekenen en naar u terugsturen samen met het certificaat van de " +"Certificatie-Autoriteit van de server." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Deze bestanden moeten gekopieerd worden naar de map die de configuratie van " +"BoxBackup bevat. De te gebruiken bestandsnamen staan vermeld in het bestand /" +"etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Moet BoxBackup automatisch ingesteld worden?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"De configuratiescripts van het pakket kunnen de configuratiebestanden voor " +"de BoxBackup-server aanmaken." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"U zou deze optie moeten kiezen indien u niet vertrouwd bent met de " +"configuratieopties voor BoxBackup. De configuratie kan manueel gebeuren met " +"behulp van de scripts 'raidfile-config' and 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"De server zal niet opstarten als hij niet geconfigureerd is. Hoe dan ook " +"wordt aanbevolen om /usr/share/doc/boxbackup-server/README.Debian te " +"raadplegen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Plaats van de RAID-mappen:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Gelieve de plaats op te geven van de drie RAID bestandsmappen." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Om RAID te gebruiken moeten de namen van de mappen ingegeven worden als een " +"door komma's gescheiden lijst van drie partities, die zich elk op een andere " +"fysieke harde schijf bevinden (bijvoorbeeld: '/raid/0.0 /raid/0.1 /" +"raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Indien u RAID niet wilt gebruiken, geeft u enkel het pad op naar een map " +"waarin de reservekopieën bewaard zullen worden (bijvoorbeeld /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Deze mappen zullen aangemaakt worden als ze nog niet bestaan." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ongeldige padnamen" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Bijvoorbeeld: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blok-grootte voor het RAID-systeem in gebruikersmodus:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup maakt gebruik van RAID-technieken in gebruikersmodus." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Gelieve de blok-grootte voor de opslag te kiezen. Voor maximale efficiëntie, " +"zou u de blok-grootte van het onderliggende bestandssysteem moeten kiezen " +"(voor het ext2-bestandssysteem kunt u dat te zien krijgen met het commando " +"'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Deze waarde moet ook ingesteld worden als u niet zinnens bent RAID te " +"gebruiken." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" +"Een geheime sleutel en een verzoek om een X.509-certificaat aanmaken voor de " +"server?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"De BoxBackup-server heeft een geheime RSA-sleutel en een overeenkomstig " +"X.509-certificaat nodig voor de client-server-authenticatie en de " +"versleuteling van de communicatie." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Beide kunnen automatisch aangemaakt worden. U zult het certificaat moeten " +"ondertekenen met uw systeembeheerders-CA (zie het boxbackup-serverpakket) en " +"dit ondertekend certificaat samen met het CA-certificaat van de " +"systeembeheerder in de configuratiemap plaatsen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Ongeldige blok-grootte" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "De blok-grootte moet een macht van twee zijn (bijv. 1024 of 4096)." diff --git a/po/pt.po b/po/pt.po new file mode 100644 index 00000000..2bcb0a08 --- /dev/null +++ b/po/pt.po @@ -0,0 +1,519 @@ +# pt translation of boxbackup. +# Copyright (C) 2008 THE BOXBACKUP'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Bruno Queirós , 2008. +# +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-05-31 20:05+0100\n" +"Last-Translator: Bruno Queirós \n" +"Language-Team: Portuguese \n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "O cliente BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuração do pacote podem criar os ficheiros de " +"configuração para o cliente do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Deve escolher esta opção se não está familiarizado com as opções de " +"configuração do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Por favor leia /usr/share/doc/boxbackup-client/README.Debian para detalhes " +"acerca da configuração do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execução para o cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup suporta dois modos de backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo 'lazy', o deamon de backup irá pesquisar regularmente o sistema de " +"ficheiros à procura de ficheiros modificados. De seguida envia os ficheiros " +"que sejam mais antigos que uma data especificada para o servidor de backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo 'snapshot' o backup será executado explicitamente em intervalos " +"regulares. Um ficheiro cron (/etc/cron.d/boxbackup-client) é fornecido " +"juntamente com o pacote e deve ser adaptado às suas necessidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número de conta para este nó no servidor de backups:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup deve ter atribuído a este cliente um " +"número de conta em hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se ainda não foi atribuído nenhum número de conta, deixe este campo em " +"branco e configure-o mais tarde executando 'dpkg-reconfigure boxbackup-" +"client' como root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta deve ser um número hexadecimal (como 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome completo do domínio do servidor de backups:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Por favor introduza o nome completo do domínio do sevidor BoxBackup que o " +"seu cliente irá utilizar." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente irá ligar-se o servidor por TCP na porta 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista dos directórios para backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Por favor forneça uma lista de directórios separados por um espaço para " +"serem copiados para o servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esses directórios não devem conter sistemas de ficheiros montados em nenhum " +"nível dos seus subdirectórios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome de localização inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Os nomes das localizações têm que ser nomes de localização absolutos, " +"separados por espaços." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/minhaconta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (em segundos) entre pesquisas de directórios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"O BoxBackup pesquisa regularmente os directórios seleccionados, à procura de " +"ficheiros modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Por favor escolha o intervalo de pesquisa em segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo de espera (em segundos) antes de carregar um ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Um ficheiro será carregado para o servidor apenas após ter passado um certo " +"tempo depois da sua última modificação." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Baixos valores de intervalo irão activar carregamentos frequentes para o " +"servidor e serão criadas mais revisões e as revisões mais antigas são " +"removidas mais cedo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo de espera (em segundos) antes de carregar um ficheiro:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Ficheiros que são frequentemente modificados raramente são carregados se " +"eles não atingirem o mínimo tempo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Por favor introduza o tempo máximo a atingir antes que o carregamento de um " +"ficheiro modificado para o servidor seja forçado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Introduzido tempo inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Por favor introduza um valor inteiro não nulo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatário para as notificações de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envia notificações de alerta quando ocorre um problema " +"durante o backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Por favor introduza ou um nome de utilizador local (por exemplo 'root') ou " +"um endereço de email (por exemplo 'admin@example.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do cliente e o pedido de certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa de uma chave privada RSA e o correspondente " +"certificado X.509 para se autenticar com o servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos podem ser gerados automaticamente. Você precisa de enviar o pedido de " +"certificado para o administrador do servidor BoxBackup que o irá assinar e " +"enviá-lo de volta para si juntamente com o certificado 'Certification " +"Authority' (CA) do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Estes ficheiros devem ser copiados para o directório de configuração do " +"BoxBackup. Os nomes de ficheiros a utilizar são dados no ficheiro /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "O BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuração do pacote podem criar os ficheiros de " +"configuração para o servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Deve escolher esta opção se não estiveir familiarizado com as opções de " +"configuração do BoxBackup. A configuração pode ser feita manualmente com os " +"scripts 'raidfile-config' e 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"O servidor não irá funcionar se não estiver configurado. Em todos os casos, " +"érecomendado ler o /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Localização dos directórios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" +"Por favor escolha a localização dos três directórios de ficheiros RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para activar o RAID, os nomes dos directórios devem ser uma lista separada " +"por um espaço de três particções, cada uma em discos rígidos diferentes (por " +"exemplo: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se não deseja activar o RAID, simplesmente especifique uma localização para " +"um directório onde os backups serão armazenados ( por exemplo, /usr/local/" +"lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Estes directórios devem ser criados caso não existam." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomes de localização inválidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Tamanho do bloco para a userland do sistema RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa técnicas userland RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Por favor escolha o tamanho do bloco para utilizar no armazenamento. Para " +"uma eficiência máxima, deverá escolher o tamanho do bloco do sistema de " +"ficheiros que utiliza (o qual pode ser visualizado com o comando 'tune2fs -" +"l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Este valor deve ser introduzido mesmo que não tencione usar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do servidor e o pedido de certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa de uma chave privada RSA e o correspondente " +"certificado X.509 para executar autenticações cliente-servidor e encriptação " +"de comunicação." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos podem ser gerados automaticamente. Precisará de assinar o certificado " +"com o seu root CA (veja o pacote boxbackup-server) e ponha este certificado " +"assinado e o certificado root CA na pasta de configuração." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamanho de bloco inválido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "O tamanho do bloco deve ser uma potência de 2 (e.g. 1024 ou 4096)." diff --git a/po/pt_BR.po b/po/pt_BR.po new file mode 100644 index 00000000..1a57bb70 --- /dev/null +++ b/po/pt_BR.po @@ -0,0 +1,522 @@ +# Debconf translations for boxbackup. +# Copyright (C) 2010 THE boxbackup'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# Bruno Gurgel , 2010. +# Adriano Rafael Gomes , 2010, 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc8~r2714-1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2008-04-21 12:53+0200\n" +"PO-Revision-Date: 2011-01-18 09:59-0200\n" +"Last-Translator: Adriano Rafael Gomes \n" +"Language-Team: Brazilian Portuguese \n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"pt_BR utf-8\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "O cliente BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Os scripts de configuração do pacote podem criar os arquivos de configuração " +"para o cliente BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Você deve escolher essa opção se você não estiver familiarizado com as " +"opções de configuração do BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Por favor, leia /usr/share/doc/boxbackup-client/README.Debian para detalhes " +"sobre a configuração do cliente BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Modo de execução para o cliente BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "O cliente BoxBackup suporta dois modos de backup:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"No modo 'lazy', o daemon do backup verificará regularmente o sistema de " +"arquivos procurando por arquivos modificados. Ele então fará o carregamento " +"(\"upload\") dos arquivos mais velhos que uma idade especificada para o " +"servidor de backup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"No modo 'snapshot', o backup será explicitamente executado em intervalos " +"regulares. Um arquivo do cron (/etc/cron.d/boxbackup-client) é fornecido com " +"o pacote e deve ser adaptado para atender as suas necessidades." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Número da conta para esse nó no servidor de backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"O administrador do servidor BoxBackup deve ter designado a esse cliente um " +"número de conta hexadecimal." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Se nenhum número de conta foi designado ainda, deixe esse campo em branco e " +"configure-o depois executando 'dpkg-reconfigure boxbackup-client' como root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Número de conta inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "O número de conta deve ser um número hexadecimal (como 1F04 ou 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Nome de domínio totalmente qualificado do servidor de backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Por favor, informe o nome de domínio totalmente qualificado do servidor " +"BoxBackup que seu cliente usará." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "O cliente conectará no servidor na porta 2201 TCP." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista de diretórios para fazer backup:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Por favor, informe uma lista separada por espaços de diretórios para fazer " +"backup no servidor remoto." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Esses diretórios não devem conter sistemas de arquivos montados em nenhum " +"nível em seus subdiretórios." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Nome de caminho inválido" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Os nomes de caminho para os diretórios precisam ser nomes de caminho " +"absolutos, separados por espaços." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Por exemplo: /home/minhaconta /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Intervalo (em segundos) entre as verificações dos diretórios:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"O BoxBackup regularmente verifica os diretórios selecionados, procurando por " +"arquivos modificados." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Por favor, escolha o intervalo de verificação em segundos." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Tempo mínimo de espera (em segundos) antes de carregar um arquivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Um arquivo será carregado para o servidor somente depois de um certo tempo " +"após sua última modificação." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Valores baixos de intervalo desencadearão carregamentos frequentes para o " +"servidor e mais revisões serão criadas, com as antigas revisões sendo " +"removidas mais cedo." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Tempo máximo para esperar (em segundos) antes de carregar um arquivo:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Arquivos frequentemente modificados são suscetíveis a nunca serem carregados " +"caso nunca atinjam o tempo mínimo de espera." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Por favor, informe o tempo máximo para atingir antes que o carregamento para " +"o servidor de um arquivo modificado seja forçado." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Tempo inválido informado" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Por favor, informe um valor inteiro maior que zero." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Destinatário para as notificações de alerta:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"O cliente BoxBackup envia notificações de alerta quando ocorre um problema " +"durante o backup." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Por favor, informe um nome de usuário local (por exemplo 'root') ou um " +"endereço de e-mail (por exemplo 'admin@exemple.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Gerar a chave privada do cliente e a requisição do certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"O cliente BoxBackup precisa de uma chave privada RSA e o certificado X.509 " +"correspondente para se autenticar com o servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Ambos podem ser gerados automaticamente. Você precisará enviar a requisição " +"do certificado para o administrador do servidor BoxBackup que irá assiná-lo " +"e enviá-lo de volta para você, juntamente com o certificado da Autoridade " +"Certificadora do servidor." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Esses arquivos devem ser copiados dentro do diretório de configuração do " +"BoxBackup. Os nomes de arquivo a serem usados são dados no arquivo /etc/" +"boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "O BoxBackup deve ser configurado automaticamente?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Os scripts de configuração do pacote podem criar os arquivos de configuração " +"para o servidor BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Você deve escolher essa opção se você não estiver familiarizado com as " +"opções de configuração do BoxBackup. A configuração pode ser feita " +"manualmente com os scripts 'raidfile-config' e 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"O servidor não inicializará se não for configurado. Em todos os casos, ler /" +"usr/share/doc/boxbackup-server/README.Debian é recomendado." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Localização dos diretórios RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" +"Por favor, escolha a localização para os três diretórios de arquivos RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Para habilitar o RAID, os nomes dos diretórios devem ser uma lista separada " +"por espaços de três partições, cada uma em diferentes discos físicos (por " +"exemplo: '/raid/0.0 /raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Se você não deseja habilitar o RAID, apenas especifique o caminho para um " +"diretório onde os backups serão armazenados (por exemplo, /usr/local/lib/" +"boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Esses diretórios serão criados se não existirem." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Nomes de caminho inválidos" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Por exemplo: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "" +"Tamanho do bloco para o sistema de RAID em espaço de usuário (\"userland\"):" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup usa técnicas de RAID em espaço de usuário." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Por favor, escolha o tamanho do bloco a ser usado para o armazenamento. Para " +"eficiência máxima, você deve escolher o tamanho de bloco do sistema de " +"arquivos subjacente (que pode ser mostrado para sistemas de arquivo ext2 com " +"o comando 'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Esse valor deve ser definido mesmo se você não planeja usar RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Gerar uma chave privada do servidor e o pedido do certificado X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"O servidor BoxBackup precisa de uma chave privada RSA e do certificado X.509 " +"correspondente para realizar a autenticação cliente-servidor e a " +"criptografia da comunicação." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Ambos podem ser gerados automaticamente. Você terá que assinar o certificado " +"com a sua CA raiz (veja o pacote boxbackup-server) e colocar este certificado " +"assinado e o certificado da CA raiz na pasta de configuração." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Tamanho de bloco inválido" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "O tamanho do bloco deve ser uma potência de dois (ex. 1024 ou 4096)." diff --git a/po/ru.po b/po/ru.po new file mode 100644 index 00000000..f0468989 --- /dev/null +++ b/po/ru.po @@ -0,0 +1,519 @@ +# translation of ru.po to Russian +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# +# Yuri Kozlov , 2008. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup new\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2008-06-06 21:51+0400\n" +"Last-Translator: Yuri Kozlov \n" +"Language-Team: Russian \n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Настроить клиента BoxBackup автоматически?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Сценарии настройки пакета могут создать файлы настройки клиента BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Вы должны ответить утвердительно, если не знакомы параметрами настройки " +"BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Подробное описание по настройке клиента BoxBackup дано в файле /usr/share/" +"doc/boxbackup-client/README.Debian." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Режим запуска клиента BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Клиент BoxBackup поддерживает два режима резервного копирования:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"В режиме 'lazy' служба резервного копирования регулярно сканирует файловую " +"систему, ища изменившиеся файлы. Затем она закачивает файлы на сервер, если " +"они новее, чем на сервере (срок можно настроить)." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"В режиме 'snapshot' резервное копирование выполняется через одинаковые " +"интервалы. В пакете есть файл задания cron (/etc/cron.d/boxbackup-client), " +"который должен быть изменён под ваши нужды." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Учётный номер этого узла на сервере резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Администратор сервера BoxBackup должен назначить этому клиенту " +"шестнадцатеричный учётный номер." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Если учётный номер ещё не назначен, оставьте это поле пустым, и настройте " +"его позже, запустив команду 'dpkg-reconfigure boxbackup-client' с правами " +"суперпользователя." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Неверный учётный номер" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" +"Учётный номер должен указываться в виде шестнадцатеричного числа (например, " +"1F04 или 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Полностью определённое доменное имя сервера резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Введите полностью определённое доменное имя сервера BoxBackup, который будет " +"использовать клиент." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Клиент будет подключаться к серверу по TCP-порту 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Список каталогов для резервного копирования:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Задайте через пробел список каталогов, для которых будет выполняться " +"резервное копирование на удалённый сервер." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Эти каталоги не должны содержать смонтированных файловых систем в своих " +"подкаталогах." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Неверный путь" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Пути к каталогам должны указываться в виде абсолютных путей и разделяться " +"пробелами." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Пример: /home/myaccount /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Интервал (в секундах) между сканированиями:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup регулярно сканирует указанные каталоги, ища изменившиеся файлы." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Введите промежуток между сканированиями в секундах." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Минимальное время ожидания (в секундах) перед закачкой файла:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Файл будет закачан на сервер только по прошествии определённого времени с " +"момента его последнего изменения." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Короткий интервал приводит к частым закачкам на сервер и созданию большого " +"числа версий и скорому удалению предыдущих версий." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Максимальное время ожидания (в секундах) перед закачкой файла:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Часто изменяемые файлы, скорее всего, никогда не будут закачаны, так как для " +"них никогда не наступит конец минимального интервала ожидания." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Введите максимальный интервал, после которого изменённый файл должен быть " +"принудительно закачан на сервер." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Указано неправильное время" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Введите целое число больше нуля." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Получатель уведомлений о сбоях:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Клиент BoxBackup посылает уведомления о сбоях, если возникает проблема при " +"выполнении резервного копирования." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Введите имя локального пользователя (например, 'root') или адрес электронной " +"почты (например, 'admin@example.org')." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Генерировать клиентский секретный ключ и запрос сертификата X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Для аутентификации клиента BoxBackup на сервере требуется секретный ключ RSA " +"и соответствующий сертификат X.509." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Они могут быть сгенерированы автоматически. Вам нужно отправить запрос " +"сертификата администратору сервера BoxBackup, который его подпишет и " +"отправит вам обратно вместе сертификатом сервера от удостоверяющего центра." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Эти файлы нужно скопировать в каталог настройки BoxBackup. Имена файлов " +"задаются в файле /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Настроить BoxBackup автоматически?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Сценарии настройки пакета могут создать файлы настройки сервера BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Вы должны ответить утвердительно, если не знакомы параметрами настройки " +"BoxBackup. Настройка может быть выполнена вручную с помощью сценариев " +"'raidfile-config' и 'bbstored-config'." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Сервер не запустится без настройки. В любом случае, прочитайте файл /usr/" +"share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Расположение каталогов RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Введите расположение для трёх каталогов RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Чтобы включить RAID, нужно указать имена каталогов трёх разделов через " +"пробел, каждый на отдельном физическом жёстком диске (например: '/raid/0.0 /" +"raid/0.1 /raid/0.2')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Если вы не хотите использовать RAID, просто укажите путь к одному каталогу, " +"где будут сохраняться резервные копии (например, /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Если каталоги не существуют, то они будут созданы." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Неверные имена каталогов" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Например: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Размер блока пользовательской системы RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "" +"В BoxBackup используются технологии RAID, работающие в пользовательском " +"окружении." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Введите размер блока хранилища. Для максимальной эффективности, вам нужно " +"указать размер блока файловой системы, на которой расположено хранилище (его " +"можно узнать, например, для файловой системы ext2, с помощью команды " +"'tune2fs -l')." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" +"Это значение должно быть задано, даже если вы не планируете использовать " +"RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Генерировать секретный ключ сервера и запрос сертификата X.509?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Для аутентификации клиентов и шифрования передачи серверу BoxBackup " +"требуется секретный ключ RSA и соответствующий сертификат X.509." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Они могут быть сгенерированы автоматически. Вам нужно подписать сертификат в " +"корневом CA (смотрите пакет boxbackup-server) и положить этот подписанный " +"сертификат и корневой сертификат CA в каталог с настройками." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Неверный размер блока" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" +"Размер блока должен быть кратен степени двойки (например, 1024 или 4096)." diff --git a/po/sv.po b/po/sv.po new file mode 100644 index 00000000..3d797880 --- /dev/null +++ b/po/sv.po @@ -0,0 +1,515 @@ +# translation of boxbackup.po to swedish +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the boxbackup package. +# +# Martin Bagge , 2009. +msgid "" +msgstr "" +"Project-Id-Version: boxbackup\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2009-02-01 04:14+0100\n" +"Last-Translator: Martin Bagge \n" +"Language-Team: swedish \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: KBabel 1.11.4\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Ska inställningarna för BoxBackup-klienten skapas automatiskt?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Paketet kan automatiskt skapa inställningsfiler för Boxbackup-klienten." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Du bör välja detta alternativ om du inte är bekant med BoxBackups " +"inställningar sedan tidiare." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Läs även i /usr/share/doc/boxbackup-client/README.Debian för närmare " +"information om inställningsalternativen för BoxBackup-klienten." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Körläge för BoxBackup-klienten:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "BoxBackup-klienten har stöd för två olika lägen för säkerhetskopior:" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"I \"lazy\"-mode söker tjänsten för säkerhetskopiering regelbundet av " +"dilsystemet för uppdaterade filer. Den kommer sedan att kopiera filer äldre " +"än ett visst tröskelvärde till servern med säkerhetskopia." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"I läget \"snapshot\" kommer säkerhetskopiorna att utföras enligt ett " +"rullande schema. Ett cronjobb kommer att installeras (/etc/cron.d/baxbackup-" +"client) med hjälp av paketet och kan ändras för att passa dina behov." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Kontonummer för den här noden på servern för säkerhetskopior:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Administratören för BoxBackup-servern ska ha givit denna klient ett " +"hexadecimalt-kontonummer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Om inget kontonummer har tilldelats ännu kan du lämna fältet blankt och " +"ställa in det senare genom att köra \"dpkg-reconfigure boxbackup-client\" " +"som root." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Ogiltigt kontonummer" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Kontonummret måste vara ett hexadecimaltnummer (ex. 1F04 eller 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Komplett domännamn för servern med säkerhetskopior:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Ange det kompletta domännamnet för BoxBackup-servern som din klient ska " +"använda." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Klienten kommer att ansluta till servern på TCP-port 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Lista med kataloger som ska säkerhetskopieras:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Ange en lista separerad av mellanslag med kataloger som ska " +"säkerhetskopieras till fjärrservern." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Dessa kataloger får inte innehålla några monterade filsystem i någon nivå i " +"deras underkataloger." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Ogiltig sökväg" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Sökvägarna till katalogerna måste vara absoluta separerade med mellanslag." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Exempelvis: /home/myaccount/etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Interval (i sekunder) mellan katalogsökningar:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"BoxBackup söker regelbundet av valda kataloger och letar efter uppdaterade " +"filer." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Ange avsökningsintervallet i sekunder." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" +"Minimal tid att vänta (i sekunder) innan en fil fkopieras till servern:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"En fil kommer att kopieras till servern så länge minst ett visst " +"tidsintervall har förflutit sedan förra förändringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Ett lågt intervall kommer att innebära frekventare kopieringar till servern " +"och fler revisioner kommer att skapas och därmed kommer äldre revisioner att " +"tas bort snabbare." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Maximal tid att vänta (i sekunder) innan en fil kopieras till servern:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Frekvent modifierade filer kan undgå att kopieras om de aldrig kommer upp i " +"den minsta väntetiden." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Ange maximal tid som måste förflyta efter att en fil har modifierats innan " +"en fil kopieras till servern." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Ogiltig tid angiven" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Ange ett heltal större än noll." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Mottagare som ska få varningsmeddelanden:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"BoxBackup-klienten skickar varningsmeddelanden när ett problem uppstår under " +"säkerhetskopieringen." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Ange antingen ett lokalt användarnamn (exempelvis \"root\") eller en e-post-" +"adress (exempelvis \"admin@example.org\")." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Skapa klientens privata nyckel och X.509-certifikatförfrågan?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"BoxBackup-klienten behöver en privat RSA-nyckel och dess motsvarande X.509-" +"certifikat för att kunna identifiera sig mot servern." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Båda kan skapas automatiskt. Du måste flytta certifikatförfrågningen till " +"administratören av BoxBackupservern som kommer att signera den och skicka " +"tillbaka den till dig tillsammans med servern certifikatutfärdar-certifikat." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Dessa filer ska kopieras till BoxBackups inställningskatalog. Filnamnen för " +"filerna anges i filen /etc/boxbackup/bbackupd.conf." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "Ska inställningar för BoxBackup skapas automatiskt?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Paketets inställningsskript kan skapa inställningsfilerna för Boxbackup-" +"servern." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Du ska anta detta alternativ om du inte känner till BoxBackups " +"inställningsalternativ sedan tidigare. Inställningarna kan göras manuellt " +"med skripten \"raidfile-config\" och \"bbstored-config\"." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Servern kommer inte att starta om inställningarna inte är kompletta. Du bör " +"läsa igenom /usr/share/doc/boxbackup-server/README.Debian." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Plats för RAID-kataloger:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Ange platsen för de tre RAID-katalogerna." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"För att aktivera RAID måste katalognamnen vara en mellanslagsseparerad lista " +"med tre partitioner, varje partition på en egen fysisk enhet (ex. \"/" +"raid/0.0 /raid/0.1 /raid/0.2\")." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Om du inte vill aktivera RAID anger du bara en sökväg till en ensam katalog " +"som säkerhetskopiorna ska lagras i (exempelvis /usr/local/lib/boxbackup)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Dessa kataloger kommer att skapas om de inte existerar." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Ogiltigt namn på sökväg" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Exempelvis: /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Blockstorleken för userland-RAID-system:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup använder userland-RAID-teknologi." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Välj den blockstorlek som ska användas för lagringen. För maximal " +"effektivitet ska du välja samma blockstorlek som det underliggande " +"filsystemet använder (storleken kan visas med kommandot \"tune2fs -l\" för " +"ext2)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Detta värde ska anges även om du inte tänker använda RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Skapa en privat servernyckel och X.509 certifikatförfrågan?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"BoxBackup-servern behöver en privat RSA-nyckel och ett tillhörande X.509-" +"certifikat för att kunna utföra klient-server-identifiering och " +"kommunikationskryptering." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Båda kan skapas automatiskt. Certifikatet måste signeras med ditt " +"huvudcertifikat från certifikatutfärdaren (se även paketet boxbackup-server) " +"sedan placeras både det signerade certifikatet och huvudcertifikatet från " +"certifikatutfärdaren i inställningskatalogen." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Felaktig storlek på block" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Blockstorleken måste vara binärkompatibel (ex. 1024 eller 4096)." diff --git a/po/templates.pot b/po/templates.pot new file mode 100644 index 00000000..b897dfbf --- /dev/null +++ b/po/templates.pot @@ -0,0 +1,446 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "" diff --git a/po/vi.po b/po/vi.po new file mode 100644 index 00000000..d1412967 --- /dev/null +++ b/po/vi.po @@ -0,0 +1,514 @@ +# Vietnamese translation for BoxBackup. +# Copyright © 2010 Free Software Foundation, Inc. +# Clytie Siddall , 2007-2010. +# +msgid "" +msgstr "" +"Project-Id-Version: boxbackup 0.11~rc3~r2502-2.1\n" +"Report-Msgid-Bugs-To: boxbackup@packages.debian.org\n" +"POT-Creation-Date: 2011-10-28 01:51+0200\n" +"PO-Revision-Date: 2010-09-30 18:14+0930\n" +"Last-Translator: Clytie Siddall \n" +"Language-Team: Vietnamese \n" +"Language: vi\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: LocFactoryEditor 1.8\n" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "Should the BoxBackup client be configured automatically?" +msgstr "Ứng dụng khách BoxBackup có nên được tự động cấu hình không?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup client." +msgstr "" +"Các văn lệnh cấu hình gói có khả năng tạo những tập tin cấu hình cho ứng " +"dụng khách BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options." +msgstr "" +"Có nên bật tùy chọn này nếu bạn chưa quen với các tùy chọn cấu hình của " +"BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:2001 +msgid "" +"Please read the /usr/share/doc/boxbackup-client/README.Debian for details " +"about the configuration of the BoxBackup client." +msgstr "" +"Xem tài liệu Đọc Đi « /usr/share/doc/boxbackup-client/README.Debian » để tìm " +"chi tiết về cấu hình của ứng dụng khách BoxBackup." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "Run mode for the BoxBackup client:" +msgstr "Chế độ chạy ứng dụng khách BoxBackup:" + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "The BoxBackup client supports two modes of backup:" +msgstr "Ứng dụng khách BoxBackup hỗ trợ hai chế độ sao lưu :" + +#. Type: select +#. Description +#. Translators, please keep reference to 'lazy' and 'snapshot' as +#. these options are written as is in the software documentation +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'lazy' mode, the backup daemon will regularly scan the file system " +"searching for modified files. It will then upload the files older than a " +"specified age to the backup server." +msgstr "" +"Trong chế độ « làm biếng », trình nền sao lưu sẽ quét đều đặn hệ thống tập " +"tin tìm tập tin bị sửa đổi. Tìm được thì cũng tải lên máy phục vụ sao lưu " +"tập tin nào « cũ » hơn một thời gian nào đó." + +#. Type: select +#. Description +#: ../boxbackup-client.templates:3001 +msgid "" +"In the 'snapshot' mode the backup will be explicitly run at regular " +"intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with the " +"package and should be adapted to suit your needs." +msgstr "" +"Trong chế độ « chụp hiện trạng », việc sao lưu sẽ được chạy dứt khoát theo " +"mỗi khoảng đều đặn. Một tập tin định kỳ cron (/etc/cron.d/boxbackup-client) " +"có sẵn với gói này và nên được sửa đổi để thích hợp với yêu cầu của bạn." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "Account number for this node on the backup server:" +msgstr "Số thứ tự tài khoản cho nút này trên máy phục vụ sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"The administrator of the BoxBackup server should have assigned this client a " +"hexadecimal account number." +msgstr "" +"Quản trị của máy phục vụ BoxBackup nên đã gán cho ứng dụng khách này một số " +"thứ tự tài khoản kiểu thập lục." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:4001 +msgid "" +"If no account number has been assigned yet, leave this field blank and " +"configure it later by running 'dpkg-reconfigure boxbackup-client' as root." +msgstr "" +"Chưa gán số thứ tự tài khoản thì bạn bỏ trống trường này và cấu hình về sau " +"bằng cách chạy câu lệnh « dpkg-reconfigure boxbackup-client » với quyền " +"người chủ." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "Invalid account number" +msgstr "Số thứ tự tài khoản sai" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:5001 +msgid "The account number must be a hexadecimal number (such as 1F04 or 4500)." +msgstr "Số thứ tự tài khoản phải là một con số thập lục (v.d. 1F04 hay 4500)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "Fully qualified domain name of the backup server:" +msgstr "Tên miền có khả năng đầy đủ của máy phục vụ sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "" +"Please enter the fully qualified domain name of the BoxBackup server which " +"your client will use." +msgstr "" +"Hãy gõ tên miền có khả năng đầy đủ (FQDN) của máy phục vụ Boxbackup cho ứng " +"dụng khách sử dụng." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:6001 +msgid "The client will connect to the server on TCP port 2201." +msgstr "Ứng dụng khách sẽ kết nối tới máy phục vụ trên cổng TCP 2201." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "List of directories to backup:" +msgstr "Danh sách các thư mục cần sao lưu :" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Please give a space-separated list of directories to be backed up onto the " +"remote server." +msgstr "" +"Hãy đưa ra một danh sách định giới bằng dấu cách chứa những thư mục cần sao " +"lưu vào máy phục vụ từ xa." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:7001 +msgid "" +"Those directories should not contain mounted file systems at any level in " +"their subdirectories." +msgstr "" +"Những thư mục này không nên chứa hệ thống tập tin đã lắp ở cấp nào trong các " +"thư mục phụ." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "Invalid path name" +msgstr "Tên đường dẫn sai" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 ../boxbackup-server.templates:4001 +msgid "" +"The path names to the directories must be absolute path names, separated by " +"spaces." +msgstr "" +"Mỗi tên đường dẫn tới thư mục phải là một tên đường dẫn tuyệt đối, định giới " +"bằng dấu cách." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:8001 +msgid "For example: /home/myaccount /etc/" +msgstr "Thí dụ : /home/tài_khoản_mình /etc/" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Interval (in seconds) between directory scans:" +msgstr "Khoảng (theo giây) giữa hai lần quét thư mục:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "" +"BoxBackup regularly scans the selected directories, looking for modified " +"files." +msgstr "" +"Trình BoxBackup quét đều đặn các thư mục đã chọn, tìm tập tin bị sửa đổi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:9001 +msgid "Please choose the scan interval in seconds." +msgstr "Hãy chọn khoảng quét theo giây." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "Minimum time to wait (in seconds) before uploading a file:" +msgstr "Thời gian tối thiểu (theo giây) cần đợi trước khi tải lên tập tin:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"A file will be uploaded to the server only after a certain time after its " +"last modification." +msgstr "" +"Mỗi tập tin sẽ được tải lên máy phục vụ chỉ sau khi đợi một khoảng thời gian " +"nào đó sau lần cuối cùng bị sửa đổi." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:10001 +msgid "" +"Low interval values will trigger frequent uploads to the server and more " +"revisions being created with older revisions being removed earlier." +msgstr "" +"Giá trị khoảng thấp sẽ gây ra nhiều việc tải lên máy phục vụ hơn thì nhiều " +"bản sửa đổi được tạo hơn, và các bản sửa đổi cũ bị gỡ bỏ trước." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "Maximum time to wait (in seconds) before uploading a file:" +msgstr "Thời gian tối đa (theo giây) cần đợi trước khi tải lên tập tin:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Frequently modified files are likely to never get uploaded if they never " +"reach the minimum wait time." +msgstr "" +"Tập tin thường bị sửa đổi thì sẽ không được tải lên nếu chưa tới thời gian " +"đợi tối thiểu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:11001 +msgid "" +"Please enter the maximum time to reach before the upload of a modified file " +"to the server is enforced." +msgstr "" +"Hãy gõ khoảng thời gian tối đa cần tới trước khi ép buộc việc tải lên máy " +"phục vụ tập tin bị sửa đổi." + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Invalid time entered" +msgstr "Sai nhập thời gian" + +#. Type: error +#. Description +#: ../boxbackup-client.templates:12001 +msgid "Please enter an integer value greater null." +msgstr "Hãy gõ một giá trị số nguyên dương (>0)." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "Recipient for alert notifications:" +msgstr "Người nhận thông báo cảnh giác:" + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"The BoxBackup client sends alert notifications when a problem occurs during " +"the backup." +msgstr "" +"Ứng dụng khách BoxBackup gửi thông báo cảnh giác khi gặp vấn đề trong khi " +"sao lưu." + +#. Type: string +#. Description +#: ../boxbackup-client.templates:13001 +msgid "" +"Please enter either a local user name (for example 'root') or an email " +"address (for example 'admin@example.org')." +msgstr "" +"Hãy gõ hoặc một tên người dùng cục bộ (v.d. « root »), hoặc một địa chỉ thư " +"điện tử (v.d. « admin@thí_dụ.org »)." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "Generate the client private key and X.509 certificate request?" +msgstr "Tạo ra khoá riêng ứng dụng khách và yêu cầu chứng nhận X.509 ?" + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"The BoxBackup client needs an RSA private key and the corresponding X.509 " +"certificate to authenticate itself with the server." +msgstr "" +"Ứng dụng khách BoxBackup yêu cầu một khoá riêng kiểu RSA, và chứng nhận " +"X.509 tương ứng, để tự xác thực với máy phục vụ." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"Both can be generated automatically. You will need to send the certificate " +"request to the BoxBackup server administrator who will sign it and send it " +"back to you along with the server's Certification Authority certificate." +msgstr "" +"Cả hai có thể được tự động tạo ra. Bạn cần phải gửi yêu cầu chứng nhận cho " +"quản trị máy phục vụ BoxBackup ký và trả lại cùng với chứng nhận CA (nhà cầm " +"quyền cấp chứng nhận) của máy phục vụ." + +#. Type: boolean +#. Description +#: ../boxbackup-client.templates:14001 +msgid "" +"These files should be copied into BoxBackup's configuration directory. The " +"file names to use are given in the /etc/boxbackup/bbackupd.conf file." +msgstr "" +"Những tập tin này nên được sao chép vào thư mục cấu hình của BoxBackup. Các " +"tên tập tin cần dùng được đưa ra trong tập tin cấu hình « /etc/boxbackup/" +"bbackupd.conf file »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "Should BoxBackup be configured automatically?" +msgstr "BoxBackup có nên được tự động cấu hình không?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The package configuration scripts can create the configuration files for the " +"BoxBackup server." +msgstr "" +"Các văn lệnh cấu hình gói có khả năng tạo những tập tin cấu hình cho trình " +"phục vụ BoxBackup." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"You should choose this option if you are not familiar with BoxBackup's " +"configuration options. The configuration can be done manually with the " +"'raidfile-config' and 'bbstored-config' scripts." +msgstr "" +"Có nên bật tùy chọn này nếu bạn chưa quen với các tùy chọn cấu hình của " +"BoxBackup. Bạn cũng có thể tự cấu hình dùng văn lệnh « raidfile-config » va " +"« bbstored-config »." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:2001 +msgid "" +"The server will not start if it is not configured. In all cases, reading " +"the /usr/share/doc/boxbackup-server/README.Debian is recommended." +msgstr "" +"Chưa cấu hình thì trình phục vụ không khởi chạy. Trong mọi trường hợp, " +"khuyên bạn đọc tài liệu Đọc Đi « /usr/share/doc/boxbackup-server/README." +"Debian »." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Location of the RAID directories:" +msgstr "Vị trí của thư mục RAID:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "Please choose the location for the three RAID file directories." +msgstr "Hãy chọn vị trí cho ba thư mục tập tin RAID." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"To enable RAID, the directory names should be a space-separated list of " +"three partitions, each on different physical hard drives (for example: '/" +"raid/0.0 /raid/0.1 /raid/0.2')." +msgstr "" +"Để hiệu lực chức năng RAID, những tên thư mục nên làm một danh sách định " +"giới bằng dấu cách chứa ba phân vùng, mỗi điều trên một ổ đĩa cứng vật lý " +"riêng (v.d. « /raid/0.0 /raid/0.1 /raid/0.2 »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "" +"If you don't want to enable RAID, just specify the path to one directory " +"where the backups will be stored (for example, /usr/local/lib/boxbackup)." +msgstr "" +"Không muốn hiệu lực RAID thì chỉ cần xác định đường dẫn tới một thư mục " +"trong đó có thể cất giữ các bản sao lưu (v.d. « /usr/local/lib/boxbackup »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:3001 +msgid "These directories will be created if they do not exist." +msgstr "Chưa tồn tại thì tự động tạo những thư mục này." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "Invalid path names" +msgstr "Tên đường dẫn sai" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:4001 +msgid "For example: /raid/0.0 /raid/0.1 /raid/0.2" +msgstr "Thí dụ : /raid/0.0 /raid/0.1 /raid/0.2" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "Block size for the userland RAID system:" +msgstr "Kích cỡ khối cho hệ thống RAID vùng người dùng:" + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "BoxBackup uses userland RAID techniques." +msgstr "BoxBackup sử dụng kỹ thuật RAID kiểu vùng người dùng." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "" +"Please choose the block size to use for the storage. For maximum efficiency, " +"you should choose the block size of the underlying file system (which can be " +"displayed for ext2 filesystems with the 'tune2fs -l' command)." +msgstr "" +"Hãy chọn kích cỡ khối cần dùng cho sức chứa. Để hữu hiệu nhất, bạn nên chọn " +"kích cỡ khối của hệ thống tập tin bên dưới (trên hệ thống tập tin ext2 có " +"thể hiển thị nó dùng câu lệnh « tune2fs -l »)." + +#. Type: string +#. Description +#: ../boxbackup-server.templates:5001 +msgid "This value should be set even if you don't plan to use RAID." +msgstr "Giá trị này nên được lập thậm chí nếu bạn không định sử dụng RAID." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "Generate a server private key and X.509 certificate request?" +msgstr "Tạo ra một khoá riêng máy phục vụ và yêu cầu chứng nhận X.509 ?" + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"The BoxBackup server needs an RSA private key and the corresponding X.509 " +"certificate to perform client-server authentication and communication " +"encryption." +msgstr "" +"Trình phục vụ BoxBackup yêu cầu một khoá riêng kiểu RSA, và chứng nhận X.509 " +"tương ứng, để thực hiện tiến trình xác thực giữa ứng dụng khách và trình " +"phục vụ, và để mật mã hoá giao thông." + +#. Type: boolean +#. Description +#: ../boxbackup-server.templates:6001 +msgid "" +"Both can be generated automatically. You will need to sign the certificate " +"with your root CA (see the boxbackup-server package) and put this signed " +"certificate and the root CA certificate in the configuration folder." +msgstr "" +"Cả hai có thể được tự động tạo ra. Bạn cần phải ký chứng nhận dùng CA gốc " +"(xem gói tiện ích « boxbackup-server »), và để vào thư mục cấu hình chứng " +"nhận đã ký và chứng nhận CA gốc." + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "Invalid block size" +msgstr "Sai lập kích cỡ khối" + +#. Type: error +#. Description +#: ../boxbackup-server.templates:7001 +msgid "The block size must be a power of two (e.g. 1024 or 4096)." +msgstr "Kích cỡ khối phải là hai lũy thừa (v.d. 1024 hay 4096)." diff --git a/rules b/rules new file mode 100755 index 00000000..e5cf5097 --- /dev/null +++ b/rules @@ -0,0 +1,107 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +include /usr/share/dpkg/pkg-info.mk + +# These are used for cross-compiling and for saving the configure script +# from having to guess our platform (since we know it already) +DEB_HOST_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_HOST_GNU_TYPE) +DEB_BUILD_GNU_TYPE ?= $(shell dpkg-architecture -qDEB_BUILD_GNU_TYPE) + +TMP:=$(CURDIR)/debian/tmp + +ifneq (,$(findstring debug,$(DEB_BUILD_OPTIONS))) + CFLAGS += -g +endif +ifeq (,$(findstring nostrip,$(DEB_BUILD_OPTIONS))) + INSTALL_PROGRAM += -s +endif + +configure: configure-stamp +configure-stamp: + dh_testdir + dh_autotools-dev_updateconfig + echo "$(DEB_VERSION_UPSTREAM_REVISION)" > VERSION.txt + echo "boxbackup" >> VERSION.txt + sh -x ./bootstrap + ./configure $(DEB_EXTRA_CONFIG_FLAGS) LDFLAGS="-Wl,--as-needed" + touch configure-stamp + +build-stamp: configure-stamp + dh_testdir + $(MAKE) V=1 +# the testsuite is only really maintained on i386 and amd64 +ifneq (,$(filter $(DEB_HOST_ARCH),i386 amd64)) +ifeq (,$(findstring nocheck,$(DEB_BUILD_OPTIONS))) + ./runtest.pl ALL +endif +endif + touch build-stamp + +docs/docbook/instguide.pdf: + $(MAKE) -C docs instguide + cd docs/docbook && docbook2pdf instguide.xml + +docs/docbook/adminguide.pdf: configure-stamp + $(MAKE) -C docs adminguide + cd docs/docbook && docbook2pdf adminguide.xml + +docs: docs/docbook/instguide.pdf docs/docbook/adminguide.pdf + $(MAKE) -C docs manpages + +build-arch: build-stamp +build-indep: docs + +build: build-arch build-indep + +clean: + dh_testdir + dh_testroot + dh_clean build-stamp configure-stamp + echo "USE_SVN_VERSION" > VERSION.txt + echo "boxbackup" >> VERSION.txt + [ ! -f Makefile ] || make clean + sh debian/clean.sh + dh_autotools-dev_restoreconfig + dh_clean config.log config.status + +install: DH_OPTIONS= +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + + mkdir -p $(TMP)/etc/logcheck/ignore.d.workstation + mkdir -p $(TMP)/etc/logcheck/ignore.d.server + install -m 644 debian/boxbackup-server.logcheck.ignore $(TMP)/etc/logcheck/ignore.d.workstation/boxbackup-server + install -m 644 debian/boxbackup-server.logcheck.ignore $(TMP)/etc/logcheck/ignore.d.server/boxbackup-server + + dh_install + +binary-indep: +# no architecture independant packages are being built + +# Build architecture-dependent files here. +binary-arch: build install + dh_testdir -a + dh_testroot -a + dh_installdebconf -a + dh_installdocs -a -A ExceptionCodes.txt docs/docbook/instguide.pdf docs/docbook/adminguide.pdf + dh_installinit -a + dh_installcron -a + dh_installman + dh_installchangelogs -a + dh_strip -a + dh_compress -a + dh_fixperms -a + dh_installdeb -a + dh_shlibdeps -a + dh_gencontrol -a + dh_md5sums -a + dh_builddeb -a + +binary: binary-arch +.PHONY: build build-arch build-indep clean binary-indep binary-arch binary install docs diff --git a/source/format b/source/format new file mode 100644 index 00000000..163aaf8d --- /dev/null +++ b/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/testing-notes.org b/testing-notes.org new file mode 100644 index 00000000..0a6293e4 --- /dev/null +++ b/testing-notes.org @@ -0,0 +1,404 @@ +#+TITLE: boxbackup testing notes +#+DATE: <2017-06-17 Sat> +#+AUTHOR: Reinhard Tartler +#+EMAIL: siretart@debian.org + +* Installing vagrant + +These are my personal notes on how I've tested the boxbackup +packages. Surely there are many other ways (and even better ones) how +test the packages, but this is a way that works for me. Feel free to fix +typos and makes suggestions. + +I'm using vagrant in KVM mode, which is included in debian/stretch. I've +tested this on a relatively modern Intel I5 CPU with an debian amd64 +installation. Other architectures and non-virtualized environments +should work exactly the same. The basic testing idea is to install the +CA signature server, the boxbackup server and the boxbackup client on +the same machine, and backup to localhost for simplicity. Production +installations will place them all on different host, but that would make +testing unnecessarily hard. + +Let's first start with installing vagrant: + +#+BEGIN_SRC bash +sudo apt install vagrant-libvirt virt-manager +sudo adduser $(whoami) libvirt +newgrp +#+END_SRC + +Now we can get a new box, get it up and login: + +#+BEGIN_SRC bash +vagrant init debian/stretch64 +vagrant up +vagrant ssh +#+END_SRC + +You might get some password prompts, not sure how to avoid those. + +* Testing boxbackup + +First install the debian packages. This assumes that the Vagrant file is +in the same directory that contains the =*.deb= packages to test. + +#+BEGIN_SRC bash +sudo apt install /vagrant/boxbackup*0.12*.deb +#+END_SRC + +First, we need to create a certificat authority. + +#+BEGIN_SRC bash +cd /root +bbstored-certs ca init +#+END_SRC + +Setup the server: + +#+BEGIN_SRC bash +mkdir /boxbackup/ +dpkg-reconfigure -p low boxbackup-server +#+END_SRC + +Output might look like this: + +#+BEGIN_EXAMPLE +Configuring boxbackup-server +---------------------------- + +The package configuration scripts can create the configuration files for the BoxBackup server. + +You should choose this option if you are not familiar with BoxBackup's configuration options. The configuration can be done manually with +the 'raidfile-config' and 'bbstored-config' scripts. + +The server will not start if it is not configured. In all cases, reading the /usr/share/doc/boxbackup-server/README.Debian is recommended. + +Should BoxBackup be configured automatically? [yes/no] + + +Should BoxBackup be configured automatically? [yes/no] yes +yes + + +Please choose the location for the three RAID file directories. + +To enable RAID, the directory names should be a space-separated list of three partitions, each on different physical hard drives (for +example: '/raid/0.0 /raid/0.1 /raid/0.2'). + +If you don't want to enable RAID, just specify the path to one directory where the backups will be stored (for example, +/usr/local/lib/boxbackup). + +These directories will be created if they do not exist. + +Location of the RAID directories: /boxbackup/0 +/boxbackup/0 + + +BoxBackup uses userland RAID techniques. + +Please choose the block size to use for the storage. For maximum efficiency, you should choose the block size of the underlying file +system (which can be displayed for ext2 filesystems with the 'tune2fs -l' command). + +This value should be set even if you don't plan to use RAID. + +Block size for the userland RAID system: 4096 +4096 + + +The BoxBackup server needs an RSA private key and the corresponding X.509 certificate to perform client-server authentication and +communication encryption. + +Both can be generated automatically. You will need to sign the certificate with your root CA (see the boxbackup-server package) and put +this signed certificate and the root CA certificate in the configuration folder. + +Generate a server private key and X.509 certificate request? [yes/no] yes +yes + + +User bbstored already exists. +Creating /boxbackup/0/backup directory... +Generating RSA private key, 2048 bit long modulus +...................+++ +............+++ +e is 65537 (0x010001) +You are about to be asked to enter information that will be incorporated +into your certificate request. +What you are about to enter is what is called a Distinguished Name or a DN. +There are quite a few fields but you can leave some blank +For some fields there will be a default value, +If you enter '.', the field will be left blank. +----- +Country Name (2 letter code) [AU]:State or Province Name (full name) [Some-State]:Locality Name (eg, city) []:Organization Name (eg, company) [Internet Widgits Pty Ltd]:Organizational Unit Name (eg, section) []:Common Name (e.g. server FQDN or YOUR name) []:Email Address []: +Please enter the following 'extra' attributes +to be sent with your certificate request +A challenge password []:An optional company name []:perl: warning: Setting locale failed. + +Creating config file /etc/boxbackup/raidfile.conf with new version + +Creating config file /etc/boxbackup/bbstored.conf with new version + +#+END_EXAMPLE + +Now we need to sign the server certificate: + +#+BEGIN_EXAMPLE +cd /root +bbstored-certs ca sign-server /etc/boxbackup/bbstored/boxbackup-server-cert-req.pem + +This certificate is for backup server + + localhost + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +yes +yes +Signature ok +subject=CN = localhost +Getting CA Private Key + + +Certificate signed. + +Install the files + + ca/servers/localhost-cert.pem + ca/roots/clientCA.pem + +on the server. + +#+END_EXAMPLE + +After this, we need to install them: + +#+BEGIN_SRC bash +cp -v ca/roots/clientCA.pem /etc/boxbackup/bbstored/boxbackup-client-ca-cert.pem +cp -v ca/servers/localhost-cert.pem /etc/boxbackup/bbstored/boxbackup-server-cert.pem +#+END_SRC + +Create a new user: + +#+BEGIN_SRC bash +bbstoreaccounts create 1 0 1G 2G +#+END_SRC + +Now we can start the server: + +#+BEGIN_EXAMPLE +# systemctl restart boxbackup-server +# systemctl status boxbackup-server +● boxbackup-server.service - Box Backup Server + Loaded: loaded (/lib/systemd/system/boxbackup-server.service; disabled; vendor preset: enabled) + Active: active (running) since Sat 2017-06-17 23:59:32 UTC; 2s ago + Main PID: 2574 (bbstored) + Tasks: 2 (limit: 4915) + CGroup: /system.slice/boxbackup-server.service + ├─2574 /usr/sbin/bbstored -F -c /etc/boxbackup/bbstored.conf + └─2575 /usr/sbin/bbstored -F -c /etc/boxbackup/bbstored.conf + +Jun 17 23:59:32 stretch systemd[1]: Started Box Backup Server. +Jun 17 23:59:32 stretch bbstored[2574]: NOTICE: Box Backup Store Server v0.12~gitcf52058f-1, (c) Ben Summers and contributors 2003-2014 +Jun 17 23:59:32 stretch bbstored[2574]: NOTICE: Starting daemon, version: 0.12~gitcf52058f-1 +Jun 17 23:59:32 stretch bbstored[2574]: NOTICE: Starting daemon, version: 0.12~gitcf52058f-1 +Jun 17 23:59:32 stretch bbstored[2574]: NOTICE: Using configuration file: /etc/boxbackup/bbstored.conf +Jun 17 23:59:32 stretch bbstored[2574]: NOTICE: Using configuration file: /etc/boxbackup/bbstored.conf +#+END_EXAMPLE + + +Let's create setup the client: + +#+BEGIN_SRC bash +# dpkg-reconfigure -plow boxbackup-client +dpkg-reconfigure -plow boxbackup-client +debconf: unable to initialize frontend: Dialog +debconf: (Dialog frontend will not work on a dumb terminal, an emacs shell buffer, or without a controlling terminal.) +debconf: falling back to frontend: Readline +Configuring boxbackup-client +---------------------------- + +The package configuration scripts can create the configuration files for the BoxBackup client. + +You should choose this option if you are not familiar with BoxBackup's configuration options. + +Please read the /usr/share/doc/boxbackup-client/README.Debian for details about the configuration of the BoxBackup client. + +Should the BoxBackup client be configured automatically? [yes/no] yes +yes + + +The BoxBackup client supports two modes of backup: + +In the 'lazy' mode, the backup daemon will regularly scan the file system searching for modified files. It will then upload the files +older than a specified age to the backup server. + +In the 'snapshot' mode the backup will be explicitly run at regular intervals. A cron file (/etc/cron.d/boxbackup-client) is provided with +the package and should be adapted to suit your needs. + + 1. lazy 2. snapshot + +Run mode for the BoxBackup client: 2 +2 + + +The administrator of the BoxBackup server should have assigned this client a hexadecimal account number. + +If no account number has been assigned yet, leave this field blank and configure it later by running 'dpkg-reconfigure boxbackup-client' +as root. + +Account number for this node on the backup server: 1 +1 + + +Please enter the fully qualified domain name of the BoxBackup server which your client will use. + +The client will connect to the server on TCP port 2201. + +Fully qualified domain name of the backup server: localhost +localhost + + +Please give a space-separated list of directories to be backed up onto the remote server. + +Those directories should not contain mounted file systems at any level in their subdirectories. + +List of directories to backup: /etc /home +/etc /home + + +The BoxBackup client sends alert notifications when a problem occurs during the backup. + +Please enter either a local user name (for example 'root') or an email address (for example 'admin@example.org'). + +Recipient for alert notifications: root +root + + +The BoxBackup client needs an RSA private key and the corresponding X.509 certificate to authenticate itself with the server. + +Both can be generated automatically. You will need to send the certificate request to the BoxBackup server administrator who will sign it +and send it back to you along with the server's Certification Authority certificate. + +These files should be copied into BoxBackup's configuration directory. The file names to use are given in the /etc/boxbackup/bbackupd.conf +file. + +Generate the client private key and X.509 certificate request? [yes/no] yes +yes +#+END_SRC + +Which we can now sign: + + + +#+BEGIN_EXAMPLE +# bbstored-certs ca sign /etc/boxbackup/bbackupd/boxbackup-client-cert-req.pem + +This certificate is for backup account + + 1 + +Ensure this matches the account number you are expecting. The filename is + + ./bbackupd/boxbackup-client-cert-req.pem + +which should include this account number, and additionally, you should check +that you received it from the right person. + +Signing the wrong certificate compromises the security of your backup system. + +Would you like to sign this certificate? (type 'yes' to confirm) +yes +yes +Signature ok +subject=CN = BACKUP-1 +Getting CA Private Key + + +Certificate signed. + +Send the files + + ca/clients/1-cert.pem + ca/roots/serverCA.pem + +to the client. + +#+END_EXAMPLE + +Now we can install the files: + +#+BEGIN_SRC bash + cp -v ca/clients/1-cert.pem /etc/boxbackup/bbackupd/boxbackup-client-cert.pem + cp -v ca/roots/serverCA.pem /etc/boxbackup/bbackupd/boxbackup-server-ca-cert.pem +#+END_SRC + + +Let's restart the client: + +#+BEGIN_SRC bash +root@stretch:/root# systemctl restart boxbackup-client +root@stretch:/root# systemctl status boxbackup-client +● boxbackup-client.service - Box Backup Client + Loaded: loaded (/lib/systemd/system/boxbackup-client.service; disabled; vendor preset: enabled) + Active: active (running) since Sun 2017-06-18 00:01:20 UTC; 3s ago + Main PID: 2793 (bbackupd) + Tasks: 1 (limit: 4915) + CGroup: /system.slice/boxbackup-client.service + └─2793 /usr/sbin/bbackupd -F -c /etc/boxbackup/bbackupd.conf + +Jun 18 00:01:20 stretch systemd[1]: Started Box Backup Client. +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Box Backup Client v0.12~gitcf52058f-1, (c) Ben Summers and contributors 2003-2014 +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Starting daemon, version: 0.12~gitcf52058f-1 +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Starting daemon, version: 0.12~gitcf52058f-1 +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Using configuration file: /etc/boxbackup/bbackupd.conf +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Using configuration file: /etc/boxbackup/bbackupd.conf +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Store object info file is not enabled. Will download directory listings from store. +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Store object info file is not enabled. Will download directory listings from store. +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Beginning scan of local files +Jun 18 00:01:20 stretch bbackupd[2793]: NOTICE: Beginning scan of local files +#+END_SRC + +And now let's do a backup. This may take a while... + +#+BEGIN_EXAMPLE +bbackupctl sync-and-wait +NOTICE: Using configuration file /etc/boxbackup/bbackupd.conf +INFO: Daemon configuration summary: + AutomaticBackup = false + UpdateStoreInterval = 0 seconds + MinimumFileAge = 0 seconds + MaxUploadWait = 0 seconds +INFO: Sync started... +INFO: Sync finished. +#+END_EXAMPLE + +Let's check the size of the backup store, and the number of files in backup: + +#+BEGIN_EXAMPLE +root@stretch:/tmp# bbackupquery 'list -r' exit | wc -l +1184 +root@stretch:/tmp# du -sh /boxbackup +5.1M /boxbackup +root@stretch:/tmp# +#+END_EXAMPLE + +For automated installation, here are my boxbackup settings: + +#+BEGIN_EXAMPLE +root@stretch:/tmp# debconf-get-selections | grep boxbackup +debconf-get-selections | grep boxbackup +boxbackup-client boxbackup-client/MaxUploadWait string 86400 +boxbackup-client boxbackup-client/notifyMail string root +boxbackup-client boxbackup-client/accountNumber string 1 +boxbackup-client boxbackup-client/UpdateStoreInterval string 3600 +boxbackup-client boxbackup-client/MinimumFileAge string 21600 +boxbackup-server boxbackup-server/generateCertificate boolean true +boxbackup-server boxbackup-server/raidBlockSize string 4096 +boxbackup-server boxbackup-server/debconf boolean true +boxbackup-client boxbackup-client/backupMode select snapshot +boxbackup-client boxbackup-client/backupServer string localhost +boxbackup-client boxbackup-client/backupDirs string /etc /home +boxbackup-server boxbackup-server/raidDirectories string /boxbackup/0 +boxbackup-client boxbackup-client/generateCertificate boolean true +boxbackup-client boxbackup-client/debconf boolean true +#+END_EXAMPLE diff --git a/watch b/watch new file mode 100644 index 00000000..5ec27736 --- /dev/null +++ b/watch @@ -0,0 +1,4 @@ +version=3 +# release candidates are not on sf +# http://sf.net/boxbackup/ boxbackup-(.*)\.tgz +http://boxbackup.org/svn/box/packages/ boxbackup-(.*)\.tgz -- cgit v1.2.3 From 57672a3b4702d2c42120bf20e02e18e3d3fef670 Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Fri, 30 Jun 2017 15:01:19 -0400 Subject: adjust-syslog-facility change default syslog facility from LOG_LOCAL6 to LOG_DAEMON Gbp-Pq: Name 03-adjust-syslog-facility.diff --- docs/docbook/adminguide.xml | 6 ++++++ lib/bbstored/BackupStoreDaemon.cpp | 2 +- lib/common/Logging.cpp | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/docbook/adminguide.xml b/docs/docbook/adminguide.xml index edb0a58c..440c7d4f 100644 --- a/docs/docbook/adminguide.xml +++ b/docs/docbook/adminguide.xml @@ -286,6 +286,12 @@ local5.info /var/log/raidfile Note: Separators must be tabs, otherwise these entries will be ignored. + Note2: The packaged + debian and ubuntu versions of boxbackup do not log to local6, + but to the more standard 'daemon' facility. This means you + should not have anything to do to your syslog configuration, + since it is configured to be logged by default. + touch /var/log/box touch /var/log/raidfile diff --git a/lib/bbstored/BackupStoreDaemon.cpp b/lib/bbstored/BackupStoreDaemon.cpp index 8fddf125..37b0a6f2 100644 --- a/lib/bbstored/BackupStoreDaemon.cpp +++ b/lib/bbstored/BackupStoreDaemon.cpp @@ -203,7 +203,7 @@ void BackupStoreDaemon::Run() SetProcessTitle("housekeeping, idle"); whichSocket = 1; // Change the log name - ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); + ::openlog("bbstored/hk", LOG_PID, LOG_DAEMON); // Log that housekeeping started BOX_INFO("Housekeeping process started"); // Ignore term and hup diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp index a0d1ec8c..acb8f897 100644 --- a/lib/common/Logging.cpp +++ b/lib/common/Logging.cpp @@ -411,7 +411,7 @@ bool Syslog::Log(Log::Level level, const std::string& file, int line, return true; } -Syslog::Syslog() : mFacility(LOG_LOCAL6) +Syslog::Syslog() : mFacility(LOG_DAEMON) { ::openlog("Box Backup", LOG_PID, mFacility); } @@ -454,8 +454,8 @@ int Syslog::GetNamedFacility(const std::string& rFacility) #undef CASE_RETURN BOX_ERROR("Unknown log facility '" << rFacility << "', " - "using default LOCAL6"); - return LOG_LOCAL6; + "using default DAEMON"); + return LOG_DAEMON; } bool FileLogger::Log(Log::Level Level, const std::string& file, int line, -- cgit v1.2.3 From 99a48ff652f89f2b1c3e50b2b6863a4ccaa34e5c Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Fri, 30 Jun 2017 15:01:19 -0400 Subject: dont_use_net_for_docs === modified file 'docs/Makefile' Gbp-Pq: Name 05-dont_use_net_for_docs.diff --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index c4a63671..2fcca31e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,7 +10,7 @@ all: docs -DBPROC_COMMAND = xsltproc +DBPROC_COMMAND = xsltproc --nonet MKDIR_COMMAND = mkdir CP_COMMAND = cp PERL_COMMAND = perl -- cgit v1.2.3 From ab148d97773d2ae623b277fe920466377b7f1698 Mon Sep 17 00:00:00 2001 From: Reinhard Tartler Date: Fri, 30 Jun 2017 15:01:19 -0400 Subject: Fixup bbstored for newer openssl It appears that modern openssl versions slightly changed the formatting for printing the common name of a certificate. I've also dropped the check against filename because I cound't get the filename to match against my local files - the check didn't appear too useful to me. Gbp-Pq: Name 06-fixup-bbstored-certs.diff --- bin/bbstored/bbstored-certs.in | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/bin/bbstored/bbstored-certs.in b/bin/bbstored/bbstored-certs.in index 85560748..0f6b9b27 100755 --- a/bin/bbstored/bbstored-certs.in +++ b/bin/bbstored/bbstored-certs.in @@ -171,12 +171,6 @@ sub cmd_sign my $acc = $1; - # check against filename - if(!($csr =~ m/(\A|\/)([A-Fa-f0-9]+)-/) || $2 ne $acc) - { - die "Certificate request filename does not match name in certificate ($common_name)" - } - print <<__E; This certificate is for backup account @@ -288,7 +282,7 @@ sub get_csr_common_name my $subject; while() { - $subject = $1 if m/Subject:.+?CN=([-\.\w]+)/ + $subject = $1 if m/Subject:.+?CN\s?=\s?([-\.\w]+)/ } close CSRTEXT; -- cgit v1.2.3 From 2c9fd76f2e44711d60826436e74558d6f0f17b20 Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Fri, 30 Jun 2017 15:01:19 -0400 Subject: fix-ftbfs-signed-char Gbp-Pq: Name 07-fix-ftbfs-signed-char.diff --- lib/httpserver/cdecode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/httpserver/cdecode.cpp b/lib/httpserver/cdecode.cpp index e632f182..77e7ea86 100644 --- a/lib/httpserver/cdecode.cpp +++ b/lib/httpserver/cdecode.cpp @@ -12,7 +12,7 @@ extern "C" int base64_decode_value(char value_in) { - static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const signed char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; static const char decoding_size = sizeof(decoding); value_in -= 43; if (value_in < 0 || value_in > decoding_size) return -1; -- cgit v1.2.3 From 1b0a0f835beb3d8d06c3bf4d8336c31066ef413d Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Tue, 20 Feb 2018 21:53:32 -0500 Subject: adjust-syslog-facility change default syslog facility from LOG_LOCAL6 to LOG_DAEMON Gbp-Pq: Name 03-adjust-syslog-facility.diff --- docs/docbook/adminguide.xml | 6 ++++++ lib/bbstored/BackupStoreDaemon.cpp | 2 +- lib/common/Logging.cpp | 6 +++--- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/docbook/adminguide.xml b/docs/docbook/adminguide.xml index edb0a58c..440c7d4f 100644 --- a/docs/docbook/adminguide.xml +++ b/docs/docbook/adminguide.xml @@ -286,6 +286,12 @@ local5.info /var/log/raidfile Note: Separators must be tabs, otherwise these entries will be ignored. + Note2: The packaged + debian and ubuntu versions of boxbackup do not log to local6, + but to the more standard 'daemon' facility. This means you + should not have anything to do to your syslog configuration, + since it is configured to be logged by default. + touch /var/log/box touch /var/log/raidfile diff --git a/lib/bbstored/BackupStoreDaemon.cpp b/lib/bbstored/BackupStoreDaemon.cpp index 8fddf125..37b0a6f2 100644 --- a/lib/bbstored/BackupStoreDaemon.cpp +++ b/lib/bbstored/BackupStoreDaemon.cpp @@ -203,7 +203,7 @@ void BackupStoreDaemon::Run() SetProcessTitle("housekeeping, idle"); whichSocket = 1; // Change the log name - ::openlog("bbstored/hk", LOG_PID, LOG_LOCAL6); + ::openlog("bbstored/hk", LOG_PID, LOG_DAEMON); // Log that housekeeping started BOX_INFO("Housekeeping process started"); // Ignore term and hup diff --git a/lib/common/Logging.cpp b/lib/common/Logging.cpp index 0928a4d4..1cff1762 100644 --- a/lib/common/Logging.cpp +++ b/lib/common/Logging.cpp @@ -411,7 +411,7 @@ bool Syslog::Log(Log::Level level, const std::string& file, int line, return true; } -Syslog::Syslog() : mFacility(LOG_LOCAL6) +Syslog::Syslog() : mFacility(LOG_DAEMON) { ::openlog("Box Backup", LOG_PID, mFacility); } @@ -454,8 +454,8 @@ int Syslog::GetNamedFacility(const std::string& rFacility) #undef CASE_RETURN BOX_ERROR("Unknown log facility '" << rFacility << "', " - "using default LOCAL6"); - return LOG_LOCAL6; + "using default DAEMON"); + return LOG_DAEMON; } bool FileLogger::Log(Log::Level Level, const std::string& file, int line, -- cgit v1.2.3 From d6ea335a924b1e56187d63db92abf753a83ed29e Mon Sep 17 00:00:00 2001 From: Debian QA Group Date: Tue, 20 Feb 2018 21:53:32 -0500 Subject: dont_use_net_for_docs === modified file 'docs/Makefile' Gbp-Pq: Name 05-dont_use_net_for_docs.diff --- docs/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Makefile b/docs/Makefile index c4a63671..2fcca31e 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -10,7 +10,7 @@ all: docs -DBPROC_COMMAND = xsltproc +DBPROC_COMMAND = xsltproc --nonet MKDIR_COMMAND = mkdir CP_COMMAND = cp PERL_COMMAND = perl -- cgit v1.2.3